From c2b65211bded8d721afd4ffeb40a0f024c7bbde2 Mon Sep 17 00:00:00 2001 From: Pyogenics Date: Sat, 28 Sep 2024 17:35:38 +0100 Subject: [PATCH] Add A3D7 --- .../7.0/alternativa/Alternativa3D.as | 14 + .../7.0/alternativa/engine3d/alternativa3d.as | 3 + .../engine3d/containers/AverageZContainer.as | 74 + .../engine3d/containers/ConflictContainer.as | 1485 ++++++++ .../engine3d/containers/DirectionContainer.as | 38 + .../alternativa/engine3d/containers/KDTree.as | 841 +++++ .../alternativa/engine3d/containers/SkyBox.as | 138 + .../engine3d/containers/SplitContainer.as | 45 + .../controllers/SimpleObjectController.as | 452 +++ .../engine3d/core/BackfaceCulling.as | 15 + .../7.0/alternativa/engine3d/core/BoundBox.as | 64 + .../7.0/alternativa/engine3d/core/Camera3D.as | 411 ++ .../7.0/alternativa/engine3d/core/Canvas.as | 101 + .../7.0/alternativa/engine3d/core/Clipping.as | 19 + .../7.0/alternativa/engine3d/core/Debug.as | 18 + .../7.0/alternativa/engine3d/core/Fragment.as | 47 + .../7.0/alternativa/engine3d/core/Geometry.as | 1616 ++++++++ .../7.0/alternativa/engine3d/core/MipMap.as | 70 + .../alternativa/engine3d/core/MipMapping.as | 18 + .../7.0/alternativa/engine3d/core/Object3D.as | 276 ++ .../engine3d/core/Object3DContainer.as | 200 + .../7.0/alternativa/engine3d/core/Sorting.as | 23 + .../engine3d/loaders/BatchTextureLoader.as | 273 ++ .../alternativa/engine3d/loaders/Loader3DS.as | 924 +++++ .../engine3d/loaders/Loader3DSByteArray.as | 812 ++++ .../engine3d/loaders/MaterialParams.as | 15 + .../engine3d/loaders/Parsed3DSData.as | 20 + .../alternativa/engine3d/loaders/Parser3DS.as | 970 +++++ .../engine3d/loaders/TextureFilesData.as | 13 + .../engine3d/loaders/TextureInfo.as | 36 + .../engine3d/loaders/TextureLoader.as | 268 ++ .../events/BatchTextureLoaderErrorEvent.as | 57 + .../engine3d/loaders/events/LoaderEvent.as | 68 + .../loaders/events/LoaderProgressEvent.as | 79 + .../engine3d/objects/AnimSprite.as | 69 + .../7.0/alternativa/engine3d/objects/Axes.as | 120 + .../7.0/alternativa/engine3d/objects/Bone.as | 57 + .../7.0/alternativa/engine3d/objects/LOD.as | 74 + .../7.0/alternativa/engine3d/objects/Mesh.as | 3194 ++++++++++++++++ .../engine3d/objects/MeshReference.as | 110 + .../alternativa/engine3d/objects/Occluder.as | 421 +++ .../alternativa/engine3d/objects/Reference.as | 56 + .../engine3d/objects/SkeletalMesh.as | 146 + .../alternativa/engine3d/objects/Sprite3D.as | 508 +++ .../engine3d/objects/WireBoundBox.as | 90 + .../alternativa/engine3d/objects/WireQuad.as | 45 + .../alternativa/engine3d/primitives/Box.as | 233 ++ .../engine3d/primitives/GeoSphere.as | 244 ++ .../alternativa/engine3d/primitives/Plane.as | 67 + .../7.1/alternativa/Alternativa3D.as | 14 + .../7.1/alternativa/engine3d/alternativa3d.as | 3 + .../engine3d/containers/AverageZContainer.as | 66 + .../engine3d/containers/DirectionContainer.as | 28 + .../alternativa/engine3d/containers/KDTree.as | 1235 ++++++ .../alternativa/engine3d/containers/SkyBox.as | 156 + .../engine3d/containers/SplitContainer.as | 38 + .../controllers/SimpleObjectController.as | 454 +++ .../7.1/alternativa/engine3d/core/BSPNode.as | 83 + .../engine3d/core/BackfaceCulling.as | 23 + .../7.1/alternativa/engine3d/core/BoundBox.as | 67 + .../7.1/alternativa/engine3d/core/Camera3D.as | 479 +++ .../7.1/alternativa/engine3d/core/Canvas.as | 79 + .../7.1/alternativa/engine3d/core/Clipping.as | 19 + .../7.1/alternativa/engine3d/core/Debug.as | 18 + .../7.1/alternativa/engine3d/core/KDNode.as | 23 + .../7.1/alternativa/engine3d/core/MipMap.as | 73 + .../alternativa/engine3d/core/MipMapping.as | 18 + .../7.1/alternativa/engine3d/core/Object3D.as | 276 ++ .../engine3d/core/Object3DContainer.as | 164 + .../7.1/alternativa/engine3d/core/Sorting.as | 19 + .../engine3d/loaders/BatchTextureLoader.as | 275 ++ .../alternativa/engine3d/loaders/Loader3DS.as | 931 +++++ .../engine3d/loaders/Loader3DSByteArray.as | 819 ++++ .../engine3d/loaders/MaterialParams.as | 15 + .../engine3d/loaders/Parsed3DSData.as | 22 + .../alternativa/engine3d/loaders/Parser3DS.as | 977 +++++ .../engine3d/loaders/TextureFilesData.as | 13 + .../engine3d/loaders/TextureInfo.as | 36 + .../engine3d/loaders/TextureLoader.as | 268 ++ .../events/BatchTextureLoaderErrorEvent.as | 57 + .../engine3d/loaders/events/LoaderEvent.as | 68 + .../loaders/events/LoaderProgressEvent.as | 79 + .../engine3d/objects/AnimSprite.as | 48 + .../7.1/alternativa/engine3d/objects/Axes.as | 120 + .../7.1/alternativa/engine3d/objects/Bone.as | 58 + .../alternativa/engine3d/objects/KDObject.as | 1305 +++++++ .../7.1/alternativa/engine3d/objects/LOD.as | 56 + .../7.1/alternativa/engine3d/objects/Mesh.as | 2042 ++++++++++ .../alternativa/engine3d/objects/Occluder.as | 404 ++ .../alternativa/engine3d/objects/Reference.as | 60 + .../engine3d/objects/SkeletalMesh.as | 148 + .../alternativa/engine3d/objects/Sprite3D.as | 470 +++ .../engine3d/objects/WireBoundBox.as | 93 + .../alternativa/engine3d/objects/WireQuad.as | 48 + .../alternativa/engine3d/primitives/Box.as | 246 ++ .../engine3d/primitives/GeoSphere.as | 246 ++ .../alternativa/engine3d/primitives/Plane.as | 70 + .../7.2/alternativa/Alternativa3D.as | 14 + .../7.2/alternativa/engine3d/alternativa3d.as | 3 + .../engine3d/containers/AverageZContainer.as | 77 + .../engine3d/containers/BSPTree.as | 14 + .../engine3d/containers/ConflictContainer.as | 1437 +++++++ .../engine3d/containers/DirectionContainer.as | 38 + .../alternativa/engine3d/containers/KDTree.as | 826 +++++ .../alternativa/engine3d/containers/SkyBox.as | 141 + .../engine3d/containers/SplitContainer.as | 48 + .../controllers/SimpleObjectController.as | 454 +++ .../engine3d/core/BackfaceCulling.as | 15 + .../7.2/alternativa/engine3d/core/BoundBox.as | 67 + .../7.2/alternativa/engine3d/core/Camera3D.as | 414 +++ .../7.2/alternativa/engine3d/core/Canvas.as | 104 + .../7.2/alternativa/engine3d/core/Clipping.as | 19 + .../7.2/alternativa/engine3d/core/Debug.as | 18 + .../7.2/alternativa/engine3d/core/Fragment.as | 53 + .../7.2/alternativa/engine3d/core/Geometry.as | 1564 ++++++++ .../7.2/alternativa/engine3d/core/MipMap.as | 73 + .../alternativa/engine3d/core/MipMapping.as | 18 + .../7.2/alternativa/engine3d/core/Node.as | 27 + .../7.2/alternativa/engine3d/core/Object3D.as | 270 ++ .../engine3d/core/Object3DContainer.as | 203 + .../7.2/alternativa/engine3d/core/Sorting.as | 23 + .../engine3d/loaders/BatchTextureLoader.as | 275 ++ .../alternativa/engine3d/loaders/Loader3DS.as | 927 +++++ .../engine3d/loaders/Loader3DSByteArray.as | 815 ++++ .../engine3d/loaders/MaterialParams.as | 15 + .../engine3d/loaders/Parsed3DSData.as | 22 + .../alternativa/engine3d/loaders/Parser3DS.as | 973 +++++ .../engine3d/loaders/TextureFilesData.as | 13 + .../engine3d/loaders/TextureInfo.as | 36 + .../engine3d/loaders/TextureLoader.as | 268 ++ .../events/BatchTextureLoaderErrorEvent.as | 57 + .../engine3d/loaders/events/LoaderEvent.as | 68 + .../loaders/events/LoaderProgressEvent.as | 79 + .../engine3d/objects/AnimSprite.as | 72 + .../7.2/alternativa/engine3d/objects/Axes.as | 120 + .../7.2/alternativa/engine3d/objects/Bone.as | 60 + .../7.2/alternativa/engine3d/objects/LOD.as | 74 + .../7.2/alternativa/engine3d/objects/Mesh.as | 2897 +++++++++++++++ .../alternativa/engine3d/objects/Occluder.as | 412 +++ .../alternativa/engine3d/objects/Reference.as | 56 + .../engine3d/objects/SkeletalMesh.as | 148 + .../alternativa/engine3d/objects/Sprite3D.as | 506 +++ .../engine3d/objects/WireBoundBox.as | 93 + .../alternativa/engine3d/objects/WireQuad.as | 48 + .../alternativa/engine3d/primitives/Box.as | 233 ++ .../engine3d/primitives/GeoSphere.as | 244 ++ .../alternativa/engine3d/primitives/Plane.as | 67 + .../7.3/alternativa/Alternativa3D.as | 14 + .../7.3/alternativa/engine3d/alternativa3d.as | 3 + .../engine3d/animation/Animation.as | 58 + .../engine3d/animation/ComplexAnimation.as | 58 + .../7.3/alternativa/engine3d/animation/Key.as | 21 + .../engine3d/animation/MatrixAnimation.as | 16 + .../engine3d/animation/MatrixKey.as | 33 + .../engine3d/animation/ObjectAnimation.as | 9 + .../engine3d/animation/PointKey.as | 48 + .../alternativa/engine3d/animation/Track.as | 89 + .../engine3d/animation/TransformAnimation.as | 83 + .../engine3d/animation/ValueKey.as | 34 + .../engine3d/containers/ConflictContainer.as | 1079 ++++++ .../alternativa/engine3d/containers/KDTree.as | 1386 +++++++ .../engine3d/containers/ZSortContainer.as | 64 + .../controllers/SimpleObjectController.as | 461 +++ .../7.3/alternativa/engine3d/core/Camera3D.as | 587 +++ .../7.3/alternativa/engine3d/core/Canvas.as | 108 + .../7.3/alternativa/engine3d/core/Clipping.as | 19 + .../7.3/alternativa/engine3d/core/Debug.as | 190 + .../7.3/alternativa/engine3d/core/Face.as | 179 + .../7.3/alternativa/engine3d/core/Geometry.as | 1233 ++++++ .../alternativa/engine3d/core/MipMapping.as | 27 + .../alternativa/engine3d/core/MouseEvent3D.as | 87 + .../7.3/alternativa/engine3d/core/Object3D.as | 601 +++ .../engine3d/core/Object3DContainer.as | 191 + .../7.3/alternativa/engine3d/core/Sorting.as | 23 + .../7.3/alternativa/engine3d/core/Vertex.as | 78 + .../7.3/alternativa/engine3d/core/View.as | 328 ++ .../7.3/alternativa/engine3d/core/Wrapper.as | 36 + .../engine3d/loaders/MaterialLoader.as | 232 ++ .../alternativa/engine3d/loaders/Parser3DS.as | 780 ++++ .../engine3d/loaders/ParserCollada.as | 509 +++ .../loaders/collada/DaeAlternativa3DObject.as | 226 ++ .../loaders/collada/DaeAnimatedObject.as | 20 + .../engine3d/loaders/collada/DaeArray.as | 41 + .../engine3d/loaders/collada/DaeCamera.as | 55 + .../engine3d/loaders/collada/DaeChannel.as | 185 + .../engine3d/loaders/collada/DaeController.as | 471 +++ .../engine3d/loaders/collada/DaeDocument.as | 294 ++ .../engine3d/loaders/collada/DaeEffect.as | 157 + .../loaders/collada/DaeEffectParam.as | 85 + .../engine3d/loaders/collada/DaeElement.as | 94 + .../engine3d/loaders/collada/DaeGeometry.as | 125 + .../engine3d/loaders/collada/DaeImage.as | 27 + .../engine3d/loaders/collada/DaeInput.as | 53 + .../loaders/collada/DaeInstanceController.as | 55 + .../loaders/collada/DaeInstanceMaterial.as | 39 + .../engine3d/loaders/collada/DaeLogger.as | 53 + .../engine3d/loaders/collada/DaeMaterial.as | 61 + .../engine3d/loaders/collada/DaeNode.as | 456 +++ .../engine3d/loaders/collada/DaeParam.as | 79 + .../engine3d/loaders/collada/DaePrimitive.as | 266 ++ .../engine3d/loaders/collada/DaeSampler.as | 104 + .../engine3d/loaders/collada/DaeSource.as | 153 + .../engine3d/loaders/collada/DaeVertices.as | 58 + .../loaders/collada/DaeVisualScene.as | 33 + .../engine3d/loaders/collada/collada.as | 7 + .../loaders/events/LoaderErrorEvent.as | 48 + .../engine3d/loaders/events/LoaderEvent.as | 73 + .../loaders/events/LoaderProgressEvent.as | 73 + .../engine3d/materials/FillMaterial.as | 80 + .../engine3d/materials/Material.as | 30 + .../engine3d/materials/TextureMaterial.as | 533 +++ .../engine3d/objects/AnimSprite.as | 46 + .../7.3/alternativa/engine3d/objects/Axes.as | 116 + .../7.3/alternativa/engine3d/objects/Bone.as | 57 + .../7.3/alternativa/engine3d/objects/Joint.as | 215 ++ .../7.3/alternativa/engine3d/objects/LOD.as | 71 + .../7.3/alternativa/engine3d/objects/Mesh.as | 3103 ++++++++++++++++ .../alternativa/engine3d/objects/Occluder.as | 499 +++ .../alternativa/engine3d/objects/Reference.as | 45 + .../7.3/alternativa/engine3d/objects/Skin.as | 835 +++++ .../alternativa/engine3d/objects/Sprite3D.as | 547 +++ .../engine3d/objects/VertexBinding.as | 12 + .../alternativa/engine3d/primitives/Box.as | 194 + .../engine3d/primitives/GeoSphere.as | 251 ++ .../alternativa/engine3d/primitives/Plane.as | 66 + .../7.4/alternativa/Alternativa3D.as | 14 + .../7.4/alternativa/engine3d/alternativa3d.as | 3 + .../engine3d/animation/Animation.as | 165 + .../engine3d/animation/AnimationController.as | 176 + .../engine3d/animation/AnimationGroup.as | 266 ++ .../engine3d/animation/AnimationState.as | 282 ++ .../engine3d/animation/AnimationTimer.as | 111 + .../engine3d/animation/MatrixAnimation.as | 89 + .../alternativa/engine3d/animation/Track.as | 124 + .../engine3d/animation/TransformAnimation.as | 241 ++ .../engine3d/animation/keys/Key.as | 40 + .../engine3d/animation/keys/MatrixKey.as | 62 + .../engine3d/animation/keys/PointKey.as | 76 + .../engine3d/animation/keys/ValueKey.as | 55 + .../engine3d/containers/ConflictContainer.as | 1079 ++++++ .../alternativa/engine3d/containers/KDTree.as | 1165 ++++++ .../engine3d/containers/ZSortContainer.as | 64 + .../controllers/SimpleObjectController.as | 461 +++ .../7.4/alternativa/engine3d/core/Camera3D.as | 608 +++ .../7.4/alternativa/engine3d/core/Canvas.as | 108 + .../7.4/alternativa/engine3d/core/Clipping.as | 19 + .../7.4/alternativa/engine3d/core/Debug.as | 190 + .../7.4/alternativa/engine3d/core/Face.as | 199 + .../7.4/alternativa/engine3d/core/Geometry.as | 1207 ++++++ .../7.4/alternativa/engine3d/core/KDNode.as | 28 + .../alternativa/engine3d/core/MipMapping.as | 27 + .../alternativa/engine3d/core/MouseEvent3D.as | 87 + .../7.4/alternativa/engine3d/core/Object3D.as | 625 ++++ .../engine3d/core/Object3DContainer.as | 384 ++ .../7.4/alternativa/engine3d/core/Sorting.as | 23 + .../7.4/alternativa/engine3d/core/Vertex.as | 82 + .../7.4/alternativa/engine3d/core/View.as | 328 ++ .../7.4/alternativa/engine3d/core/Wrapper.as | 40 + .../engine3d/loaders/MaterialLoader.as | 230 ++ .../alternativa/engine3d/loaders/Parser3DS.as | 791 ++++ .../engine3d/loaders/ParserCollada.as | 599 +++ .../loaders/collada/DaeAlternativa3DObject.as | 235 ++ .../loaders/collada/DaeAnimatedObject.as | 20 + .../engine3d/loaders/collada/DaeArray.as | 41 + .../engine3d/loaders/collada/DaeCamera.as | 55 + .../engine3d/loaders/collada/DaeChannel.as | 185 + .../engine3d/loaders/collada/DaeController.as | 449 +++ .../engine3d/loaders/collada/DaeDocument.as | 295 ++ .../engine3d/loaders/collada/DaeEffect.as | 157 + .../loaders/collada/DaeEffectParam.as | 85 + .../engine3d/loaders/collada/DaeElement.as | 94 + .../engine3d/loaders/collada/DaeGeometry.as | 125 + .../engine3d/loaders/collada/DaeImage.as | 27 + .../engine3d/loaders/collada/DaeInput.as | 53 + .../loaders/collada/DaeInstanceController.as | 55 + .../loaders/collada/DaeInstanceMaterial.as | 39 + .../engine3d/loaders/collada/DaeLogger.as | 53 + .../engine3d/loaders/collada/DaeMaterial.as | 61 + .../engine3d/loaders/collada/DaeNode.as | 465 +++ .../engine3d/loaders/collada/DaeParam.as | 79 + .../engine3d/loaders/collada/DaePrimitive.as | 269 ++ .../engine3d/loaders/collada/DaeSampler.as | 104 + .../engine3d/loaders/collada/DaeSource.as | 153 + .../engine3d/loaders/collada/DaeVertices.as | 61 + .../loaders/collada/DaeVisualScene.as | 33 + .../engine3d/loaders/collada/collada.as | 7 + .../loaders/events/LoaderErrorEvent.as | 48 + .../engine3d/loaders/events/LoaderEvent.as | 73 + .../loaders/events/LoaderProgressEvent.as | 73 + .../engine3d/materials/FillMaterial.as | 80 + .../engine3d/materials/Material.as | 30 + .../engine3d/materials/TextureMaterial.as | 560 +++ .../engine3d/objects/AnimSprite.as | 46 + .../7.4/alternativa/engine3d/objects/Axes.as | 116 + .../7.4/alternativa/engine3d/objects/Bone.as | 57 + .../7.4/alternativa/engine3d/objects/Joint.as | 215 ++ .../7.4/alternativa/engine3d/objects/LOD.as | 71 + .../7.4/alternativa/engine3d/objects/Mesh.as | 3294 +++++++++++++++++ .../alternativa/engine3d/objects/Occluder.as | 499 +++ .../alternativa/engine3d/objects/Reference.as | 45 + .../7.4/alternativa/engine3d/objects/Skin.as | 835 +++++ .../alternativa/engine3d/objects/SkyBox.as | 118 + .../alternativa/engine3d/objects/Sprite3D.as | 585 +++ .../engine3d/objects/VertexBinding.as | 12 + .../alternativa/engine3d/primitives/Box.as | 194 + .../engine3d/primitives/GeoSphere.as | 251 ++ .../alternativa/engine3d/primitives/Plane.as | 66 + 307 files changed, 81015 insertions(+) create mode 100644 Alternativa3D7/7.0/alternativa/Alternativa3D.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/alternativa3d.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/AverageZContainer.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/ConflictContainer.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/DirectionContainer.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/KDTree.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/SkyBox.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/containers/SplitContainer.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/controllers/SimpleObjectController.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/BackfaceCulling.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/BoundBox.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Camera3D.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Canvas.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Clipping.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Debug.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Fragment.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Geometry.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/MipMap.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/MipMapping.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Object3D.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Object3DContainer.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/core/Sorting.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/BatchTextureLoader.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DS.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DSByteArray.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/MaterialParams.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/Parsed3DSData.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/Parser3DS.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureFilesData.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureInfo.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureLoader.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderEvent.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderProgressEvent.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/AnimSprite.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Axes.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Bone.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/LOD.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Mesh.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/MeshReference.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Occluder.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Reference.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/SkeletalMesh.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/Sprite3D.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/WireBoundBox.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/objects/WireQuad.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/primitives/Box.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/primitives/GeoSphere.as create mode 100644 Alternativa3D7/7.0/alternativa/engine3d/primitives/Plane.as create mode 100644 Alternativa3D7/7.1/alternativa/Alternativa3D.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/alternativa3d.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/containers/AverageZContainer.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/containers/DirectionContainer.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/containers/KDTree.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/containers/SkyBox.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/containers/SplitContainer.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/controllers/SimpleObjectController.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/BSPNode.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/BackfaceCulling.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/BoundBox.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Camera3D.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Canvas.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Clipping.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Debug.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/KDNode.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/MipMap.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/MipMapping.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Object3D.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Object3DContainer.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/core/Sorting.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/BatchTextureLoader.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DS.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DSByteArray.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/MaterialParams.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/Parsed3DSData.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/Parser3DS.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureFilesData.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureInfo.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureLoader.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderEvent.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderProgressEvent.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/AnimSprite.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Axes.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Bone.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/KDObject.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/LOD.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Mesh.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Occluder.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Reference.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/SkeletalMesh.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/Sprite3D.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/WireBoundBox.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/objects/WireQuad.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/primitives/Box.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/primitives/GeoSphere.as create mode 100644 Alternativa3D7/7.1/alternativa/engine3d/primitives/Plane.as create mode 100644 Alternativa3D7/7.2/alternativa/Alternativa3D.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/alternativa3d.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/AverageZContainer.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/BSPTree.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/ConflictContainer.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/DirectionContainer.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/KDTree.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/SkyBox.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/containers/SplitContainer.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/controllers/SimpleObjectController.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/BackfaceCulling.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/BoundBox.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Camera3D.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Canvas.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Clipping.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Debug.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Fragment.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Geometry.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/MipMap.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/MipMapping.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Node.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Object3D.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Object3DContainer.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/core/Sorting.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/BatchTextureLoader.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DS.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DSByteArray.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/MaterialParams.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/Parsed3DSData.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/Parser3DS.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureFilesData.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureInfo.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureLoader.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderEvent.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderProgressEvent.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/AnimSprite.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Axes.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Bone.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/LOD.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Mesh.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Occluder.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Reference.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/SkeletalMesh.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/Sprite3D.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/WireBoundBox.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/objects/WireQuad.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/primitives/Box.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/primitives/GeoSphere.as create mode 100644 Alternativa3D7/7.2/alternativa/engine3d/primitives/Plane.as create mode 100644 Alternativa3D7/7.3/alternativa/Alternativa3D.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/alternativa3d.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/Animation.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/ComplexAnimation.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/Key.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixAnimation.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixKey.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/ObjectAnimation.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/PointKey.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/Track.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/TransformAnimation.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/animation/ValueKey.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/containers/ConflictContainer.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/containers/KDTree.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/containers/ZSortContainer.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/controllers/SimpleObjectController.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Camera3D.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Canvas.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Clipping.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Debug.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Face.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Geometry.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/MipMapping.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/MouseEvent3D.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Object3D.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Object3DContainer.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Sorting.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Vertex.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/View.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/core/Wrapper.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/MaterialLoader.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/Parser3DS.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/ParserCollada.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeArray.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeCamera.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeChannel.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeController.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeDocument.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffect.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffectParam.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeElement.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeGeometry.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeImage.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInput.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceController.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeLogger.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeMaterial.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeNode.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeParam.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaePrimitive.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSampler.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSource.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVertices.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVisualScene.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/collada.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderErrorEvent.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderEvent.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderProgressEvent.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/materials/FillMaterial.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/materials/Material.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/materials/TextureMaterial.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/AnimSprite.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Axes.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Bone.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Joint.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/LOD.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Mesh.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Occluder.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Reference.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Skin.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/Sprite3D.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/objects/VertexBinding.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/primitives/Box.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/primitives/GeoSphere.as create mode 100644 Alternativa3D7/7.3/alternativa/engine3d/primitives/Plane.as create mode 100644 Alternativa3D7/7.4/alternativa/Alternativa3D.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/alternativa3d.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/Animation.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationController.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationGroup.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationState.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationTimer.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/MatrixAnimation.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/Track.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/TransformAnimation.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/keys/Key.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/keys/MatrixKey.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/keys/PointKey.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/animation/keys/ValueKey.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/containers/ConflictContainer.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/containers/KDTree.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/containers/ZSortContainer.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/controllers/SimpleObjectController.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Camera3D.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Canvas.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Clipping.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Debug.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Face.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Geometry.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/KDNode.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/MipMapping.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/MouseEvent3D.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Object3D.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Object3DContainer.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Sorting.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Vertex.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/View.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/core/Wrapper.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/MaterialLoader.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/Parser3DS.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/ParserCollada.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeArray.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeCamera.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeChannel.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeController.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeDocument.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffect.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffectParam.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeElement.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeGeometry.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeImage.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInput.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceController.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeLogger.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeMaterial.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeNode.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeParam.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaePrimitive.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSampler.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSource.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVertices.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVisualScene.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/collada.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderErrorEvent.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderEvent.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderProgressEvent.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/materials/FillMaterial.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/materials/Material.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/materials/TextureMaterial.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/AnimSprite.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Axes.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Bone.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Joint.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/LOD.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Mesh.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Occluder.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Reference.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Skin.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/SkyBox.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/Sprite3D.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/objects/VertexBinding.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/primitives/Box.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/primitives/GeoSphere.as create mode 100644 Alternativa3D7/7.4/alternativa/engine3d/primitives/Plane.as diff --git a/Alternativa3D7/7.0/alternativa/Alternativa3D.as b/Alternativa3D7/7.0/alternativa/Alternativa3D.as new file mode 100644 index 0000000..b6a7b32 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "7.0.0"; + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/alternativa3d.as b/Alternativa3D7/7.0/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/AverageZContainer.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/AverageZContainer.as new file mode 100644 index 0000000..83bb445 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/AverageZContainer.as @@ -0,0 +1,74 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого отрисовываются по удалённости от камеры + */ + public class AverageZContainer extends Object3DContainer { + + static private const averageZ:Vector. = new Vector.(); + static private const center:Vector. = Vector.([0, 0, 0]); + static private const cameraCenter:Vector. = new Vector.(3, true); + static private const sortingStack:Vector. = new Vector.(); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var j:int; + var l:int = 0; + var r:int = numVisibleChildren - 1; + var child:Object3D; + var sortingStackIndex:int; + var sortingLeft:Number; + var sortingMedian:Number; + var sortingRight:Number; + var sortingChild:Object3D; + // Сортировка + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + child.cameraMatrix.transformVectors(center, cameraCenter); + averageZ[i] = cameraCenter[0]*cameraCenter[0] + cameraCenter[1]*cameraCenter[1] + cameraCenter[2]*cameraCenter[2]; + } + sortingStack[0] = l; + sortingStack[1] = r; + sortingStackIndex = 2; + while (sortingStackIndex > 0) { + j = r = sortingStack[--sortingStackIndex]; + i = l = sortingStack[--sortingStackIndex]; + sortingMedian = averageZ[(r + l) >> 1]; + do { + while ((sortingLeft = averageZ[i]) > sortingMedian) i++; + while ((sortingRight = averageZ[j]) < sortingMedian) j--; + if (i <= j) { + sortingChild = visibleChildren[i]; + visibleChildren[i] = visibleChildren[j]; + visibleChildren[j] = sortingChild; + averageZ[i++] = sortingRight; + averageZ[j--] = sortingLeft; + } + } while (i <= j); + if (l < j) { + sortingStack[sortingStackIndex++] = l; + sortingStack[sortingStackIndex++] = j; + } + if (i < r) { + sortingStack[sortingStackIndex++] = i; + sortingStack[sortingStackIndex++] = r; + } + } + // Отрисовка + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/ConflictContainer.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/ConflictContainer.as new file mode 100644 index 0000000..446e50d --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/ConflictContainer.as @@ -0,0 +1,1485 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class ConflictContainer extends Object3DContainer { + + public var resolveByAABB:Boolean = false; + public var resolveByOOBB:Boolean = false; + + //public var isolateAABBConflicts:Boolean = false; + //public var isolateOOBBConflicts:Boolean = false; + + public var threshold:Number = 0.1; + + // Вспомогательные + static private const sortingFragments:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private var negativeReserve:Fragment = Fragment.create(); + static private var positiveReserve:Fragment = Fragment.create(); + + // Камера в контейнере + static private const coords:Vector. = new Vector.(3); + protected var inverseCameraMatrix:Matrix3D = new Matrix3D(); + protected var cameraX:Number; + protected var cameraY:Number; + protected var cameraZ:Number; + + // Плоскости отсечения камеры в контейнере + static private const cameraPlanes:Vector. = new Vector.(24); + private var nearPlaneX:Number; + private var nearPlaneY:Number; + private var nearPlaneZ:Number; + private var nearPlaneOffset:Number; + private var farPlaneX:Number; + private var farPlaneY:Number; + private var farPlaneZ:Number; + private var farPlaneOffset:Number; + private var leftPlaneX:Number; + private var leftPlaneY:Number; + private var leftPlaneZ:Number; + private var leftPlaneOffset:Number; + private var rightPlaneX:Number; + private var rightPlaneY:Number; + private var rightPlaneZ:Number; + private var rightPlaneOffset:Number; + private var topPlaneX:Number; + private var topPlaneY:Number; + private var topPlaneZ:Number; + private var topPlaneOffset:Number; + private var bottomPlaneX:Number; + private var bottomPlaneY:Number; + private var bottomPlaneZ:Number; + private var bottomPlaneOffset:Number; + + protected var viewAngle:Number; + protected var directionX:Number; + protected var directionY:Number; + protected var directionZ:Number; + + // Перекрытия + static private const edgeOccluder:Vector. = new Vector.(); + private var occluders:Vector.> = new Vector.>(); + protected var numOccluders:int; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + // Если есть видимая геометрия + if (geometry != null) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Если объектов несколько + if (geometry.next != null) { + var current:Geometry; + // Расчёт инверсной матрицы камеры и позицци камеры в контейнере + calculateInverseCameraMatrix(object.cameraMatrix); + // AABB + if (resolveByAABB) { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + inverseCameraMatrix.transformVectors(current.vertices, current.vertices); + current.calculateAABB(); + current = current.next; + } + drawAABBGeometry(camera, object, canvas, geometry); + // OOBB + } else if (resolveByOOBB) { + current = geometry; + while (current != null) { + if (!current.viewAligned) { + current.calculateOOBB(); + } + current = current.next; + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } else { + if (camera.debugMode) geometry.debug(camera, object, canvas, threshold, 0); + geometry.draw(camera, canvas, threshold); + geometry.destroy(); + } + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function calculateInverseCameraMatrix(matrix:Matrix3D):void { + coords[0] = 0; + coords[1] = 0; + coords[2] = 0; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(matrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(coords, coords); + cameraX = coords[0]; + cameraY = coords[1]; + cameraZ = coords[2]; + } + + protected function calculateCameraPlanes(camera:Camera3D, calculateViewAngle:Boolean = false):void { + // Перевод плоскостей камеры в пространство контейнера + cameraPlanes[0] = cameraPlanes[1] = cameraPlanes[2] = cameraPlanes[3] = cameraPlanes[4] = cameraPlanes[6] = cameraPlanes[7] = cameraPlanes[21] = cameraPlanes[22] = 0; + cameraPlanes[5] = camera.nearClipping; + cameraPlanes[8] = camera.farClipping; + cameraPlanes[9] = cameraPlanes[10] = cameraPlanes[13] = cameraPlanes[18] = -1; + cameraPlanes[11] = cameraPlanes[12] = cameraPlanes[14] = cameraPlanes[15] = cameraPlanes[16] = cameraPlanes[17] = cameraPlanes[19] = cameraPlanes[20] = cameraPlanes[23] = 1; + inverseCameraMatrix.transformVectors(cameraPlanes, cameraPlanes); + // Ближняя плоскость + var bax:Number = cameraPlanes[9] - cameraPlanes[12]; + var bay:Number = cameraPlanes[10] - cameraPlanes[13]; + var baz:Number = cameraPlanes[11] - cameraPlanes[14]; + var bcx:Number = cameraPlanes[15] - cameraPlanes[12]; + var bcy:Number = cameraPlanes[16] - cameraPlanes[13]; + var bcz:Number = cameraPlanes[17] - cameraPlanes[14]; + nearPlaneX = bcy*baz - bcz*bay; + nearPlaneY = bcz*bax - bcx*baz; + nearPlaneZ = bcx*bay - bcy*bax; + nearPlaneOffset = cameraPlanes[3]*nearPlaneX + cameraPlanes[4]*nearPlaneY + cameraPlanes[5]*nearPlaneZ; + // Дальняя плоскость + farPlaneX = -nearPlaneX; + farPlaneY = -nearPlaneY; + farPlaneZ = -nearPlaneZ; + farPlaneOffset = cameraPlanes[6]*farPlaneX + cameraPlanes[7]*farPlaneY + cameraPlanes[8]*farPlaneZ; + // Рёбра пирамиды + var ax:Number = cameraPlanes[9] - cameraX; + var ay:Number = cameraPlanes[10] - cameraY; + var az:Number = cameraPlanes[11] - cameraZ; + var bx:Number = cameraPlanes[12] - cameraX; + var by:Number = cameraPlanes[13] - cameraY; + var bz:Number = cameraPlanes[14] - cameraZ; + var cx:Number = cameraPlanes[15] - cameraX; + var cy:Number = cameraPlanes[16] - cameraY; + var cz:Number = cameraPlanes[17] - cameraZ; + var dx:Number = cameraPlanes[18] - cameraX; + var dy:Number = cameraPlanes[19] - cameraY; + var dz:Number = cameraPlanes[20] - cameraZ; + // Левая плоскость + leftPlaneX = dy*az - dz*ay; + leftPlaneY = dz*ax - dx*az; + leftPlaneZ = dx*ay - dy*ax; + leftPlaneOffset = cameraX*leftPlaneX + cameraY*leftPlaneY + cameraZ*leftPlaneZ; + // Правая плоскость + rightPlaneX = by*cz - bz*cy; + rightPlaneY = bz*cx - bx*cz; + rightPlaneZ = bx*cy - by*cx; + rightPlaneOffset = cameraX*rightPlaneX + cameraY*rightPlaneY + cameraZ*rightPlaneZ; + // Верхняя плоскость + topPlaneX = ay*bz - az*by; + topPlaneY = az*bx - ax*bz; + topPlaneZ = ax*by - ay*bx; + topPlaneOffset = cameraX*topPlaneX + cameraY*topPlaneY + cameraZ*topPlaneZ; + // Нижняя плоскость + bottomPlaneX = cy*dz - cz*dy; + bottomPlaneY = cz*dx - cx*dz; + bottomPlaneZ = cx*dy - cy*dx; + bottomPlaneOffset = cameraX*bottomPlaneX + cameraY*bottomPlaneY + cameraZ*bottomPlaneZ; + // Расчёт угла конуса + if (calculateViewAngle) { + var len:Number; + var dot:Number; + // Расчёт направления + directionX = cameraPlanes[21] - cameraX; + directionY = cameraPlanes[22] - cameraY; + directionZ = cameraPlanes[23] - cameraZ; + len = 1/Math.sqrt(directionX*directionX + directionY*directionY + directionZ*directionZ); + directionX *= len; + directionY *= len; + directionZ *= len; + // Нормализация рёбер пирамиды + len = 1/Math.sqrt(ax*ax + ay*ay + az*az); + ax *= len; + ay *= len; + az *= len; + len = 1/Math.sqrt(bx*bx + by*by + bz*bz); + bx *= len; + by *= len; + bz *= len; + len = 1/Math.sqrt(cx*cx + cy*cy + cz*cz); + cx *= len; + cy *= len; + cz *= len; + len = 1/Math.sqrt(dx*dx + dy*dy + dz*dz); + dx *= len; + dy *= len; + dz *= len; + // Угол конуса + viewAngle = ax*directionX + ay*directionY + az*directionZ; + dot = bx*directionX + by*directionY + bz*directionZ; + if (dot < viewAngle) viewAngle = dot; + dot = cx*directionX + cy*directionY + cz*directionZ; + if (dot < viewAngle) viewAngle = dot; + dot = dx*directionX + dy*directionY + dz*directionZ; + if (dot < viewAngle) viewAngle = dot; + viewAngle = Math.sin(Math.acos(viewAngle)); + } + } + + protected function updateOccluders(camera:Camera3D):void { + for (var o:int = numOccluders, occluder:Vector.; o < camera.numOccluders; o++) { + var cameraEdgeOccluder:Vector. = camera.occlusionEdges[o], edgeOccluderLength:int = cameraEdgeOccluder.length; + edgeOccluder.length = edgeOccluderLength; + // Перевод точек рёбер окклюдеров в пространство контейнера + inverseCameraMatrix.transformVectors(cameraEdgeOccluder, edgeOccluder); + // Создание окклюдера в контейнере + if (occluders.length > numOccluders) occluder = occluders[numOccluders++] else occluder = occluders[numOccluders++] = new Vector.(); + // Построение плоскостей отсечения + for (var i:int = 0, ni:int = 0, nx:Number, ny:Number, nz:Number; i < edgeOccluderLength;) { + var ax:Number = edgeOccluder[i++] - cameraX, ay:Number = edgeOccluder[i++] - cameraY, az:Number = edgeOccluder[i++] - cameraZ, bx:Number = edgeOccluder[i++] - cameraX, by:Number = edgeOccluder[i++] - cameraY, bz:Number = edgeOccluder[i++] - cameraZ; + occluder[ni++] = nx = bz*ay - by*az, occluder[ni++] = ny = bx*az - bz*ax, occluder[ni++] = nz = by*ax - bx*ay, occluder[ni++] = cameraX*nx + cameraY*ny + cameraZ*nz; + } + occluder.length = ni; + } + } + + protected function cullingInContainer(camera:Camera3D, boundBox:BoundBox, culling:int):int { + if (camera.occludedAll) return -1; + if (culling > 0) { + // Отсечение по ниар + if (culling & 1) { + if (nearPlaneX >= 0) if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } + } + // Отсечение по фар + if (culling & 2) { + if (farPlaneX >= 0) if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } + } + // Отсечение по левой стороне + if (culling & 4) { + if (leftPlaneX >= 0) if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } + } + // Отсечение по правой стороне + if (culling & 8) { + if (rightPlaneX >= 0) if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } + } + // Отсечение по верхней стороне + if (culling & 16) { + if (topPlaneX >= 0) if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } + } + // Отсечение по нижней стороне + if (culling & 32) { + if (bottomPlaneX >= 0) if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } + } + } + // Отсечение по окклюдерам + for (var o:int = 0; o < numOccluders; o++) { + var occluder:Vector. = occluders[o], occluderLength:int = occluder.length; + for (var ni:int = 0; ni < occluderLength; ni += 4) { + var nx:Number = occluder[ni], ny:Number = occluder[int(ni + 1)], nz:Number = occluder[int(ni + 2)], no:Number = occluder[int(ni + 3)]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } + } + if (ni == occluderLength) return -1; + } + return culling; + } + + protected function occludeGeometry(camera:Camera3D, geometry:Geometry):Boolean { + if (camera.occludedAll) return true; + for (var i:int = geometry.numOccluders; i < numOccluders; i++) { + var occluder:Vector. = occluders[i]; + var occluderLength:int = occluder.length; + var j:int = 0; + for (; j < occluderLength; j++) { + var nx:Number = occluder[j]; j++; + var ny:Number = occluder[j]; j++; + var nz:Number = occluder[j]; j++; + var no:Number = occluder[j]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (geometry.maxX*nx + geometry.maxY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.maxX*nx + geometry.maxY*ny + geometry.minZ*nz > no) break; + } else if (nz >= 0) { + if (geometry.maxX*nx + geometry.minY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.maxX*nx + geometry.minY*ny + geometry.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (geometry.minX*nx + geometry.maxY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.minX*nx + geometry.maxY*ny + geometry.minZ*nz > no) break; + } else if (nz >= 0) { + if (geometry.minX*nx + geometry.minY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.minX*nx + geometry.minY*ny + geometry.minZ*nz > no) break; + } + } + if (j == occluderLength) return true; + } + geometry.numOccluders = numOccluders; + return false; + } + + protected function drawAABBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var coord:Number; + var coordMin:Number; + var coordMax:Number; + var axisX:Boolean; + var axisY:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + // Поиск сплита + while (current != null) { + // Сплиты по оси X + coord = current.minX; + coordMin = coord - threshold; + coordMax = coord + threshold; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxX <= coordMax) { + outside = true; + } else if (compared.minX < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + coord = current.maxX; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minX >= coordMin) { + outside = true; + } else if (compared.maxX > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + // Сплиты по оси Y + coord = current.minY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxY <= coordMax) { + outside = true; + } else if (compared.minY < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + coord = current.maxY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minY >= coordMin) { + outside = true; + } else if (compared.maxY > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + // Сплиты по оси Z + coord = current.minZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxZ <= coordMax) { + outside = true; + } else if (compared.minZ < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + coord = current.maxZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minZ >= coordMin) { + outside = true; + } else if (compared.maxZ > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + while (geometry != null) { + next = geometry.next; + var min:Number = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + var max:Number = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max > coordMax) { + geometry.next = positive; + positive = geometry; + } else if (min < coordMin) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Определение положения камеры + if (axisX && cameraX > coord || axisY && cameraY > coord || !axisX && !axisY && cameraZ > coord) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + positive.draw(camera, canvas, threshold, object.cameraMatrix); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + negative.draw(camera, canvas, threshold, object.cameraMatrix); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + negative.draw(camera, canvas, threshold, object.cameraMatrix); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + positive.draw(camera, canvas, threshold, object.cameraMatrix); + positive.destroy(); + } + } + } + // Если не найден сплит + } else if (resolveByOOBB) { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + object.cameraMatrix.transformVectors(current.vertices, current.vertices); + if (!current.viewAligned) { + current.calculateOOBB(); + } + current = current.next; + } + drawOOBBGeometry(camera, object, canvas, geometry); + } else { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + object.cameraMatrix.transformVectors(current.vertices, current.vertices); + current = current.next; + } + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawOOBBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var j:int; + var x:Number; + var y:Number; + var z:Number; + var o:Number; + var planeX:Number; + var planeY:Number; + var planeZ:Number; + var planeOffset:Number; + var behind:Boolean; + var infront:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + var points:Vector.; + var pointsLength:int; + // Поиск сплита + while (current != null) { + if (current.viewAligned) { + planeOffset = current.vertices[2]; + compared = geometry; + while (compared != null) { + if (!compared.viewAligned) { + behind = false; + infront = false; + // Перебор точек + for (j = 2; j < 24; j += 3) { + o = compared.points[j] - planeOffset; + if (o > 0) { + if (behind) { + break; + } else { + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (j < 24) break; + } + compared = compared.next; + } + // Если не встретилось препятствий + if (compared == null) break; + } else { + // Перебор плоскостей + for (i = 0; i < 24; i++) { + planeX = current.planes[i]; i++; + planeY = current.planes[i]; i++; + planeZ = current.planes[i]; i++; + planeOffset = current.planes[i]; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + behind = false; + infront = false; + if (compared.viewAligned) { + points = compared.vertices; + pointsLength = compared.verticesLength; + } else { + points = compared.points; + pointsLength = 24; + } + // Перебор точек + for (j = 0; j < pointsLength; j++) { + x = points[j]; j++; + y = points[j]; j++; + z = points[j]; + o = x*planeX + y*planeY + z*planeZ - planeOffset; + if (o >= -threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (j < pointsLength) break; + } + compared = compared.next; + } + // Если не встретилось препятствий и есть объекты по обе стороны + if (compared == null && outside) break; + } + // Если найдена разделяющая плоскость + if (i < 24) break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (current.viewAligned) { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + o = geometry.vertices[2] - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else { + for (j = 2; j < 24; j += 3) { + o = geometry.points[j] - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + break; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + break; + } + } + if (j == 24) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } else { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + points = geometry.vertices; + pointsLength = geometry.verticesLength; + } else { + points = geometry.points; + pointsLength = 24; + } + for (j = 0; j < pointsLength; j++) { + x = points[j]; j++; + y = points[j]; j++; + z = points[j]; + o = x*planeX + y*planeY + z*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (j == pointsLength) { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + } + // Определение положения камеры + if (current.viewAligned || planeOffset < 0) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 2); + positive.draw(camera, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 2); + middle.draw(camera, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 2); + negative.draw(camera, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 2); + negative.draw(camera, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 2); + middle.draw(camera, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 2); + positive.draw(camera, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawConflictGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var j:int; + var next:Geometry; + var fragment:Fragment; + // Геометрия с сортировкой предрасчитанное BSP + var bspGeometry:Geometry; + // Геометрия, которая присутствует в конфликте + var conflict:Geometry; + // Фрагменты с сортировкой динамическое BSP + var dynamicBSPFirst:Fragment; + var dynamicBSPLast:Fragment; + // Фрагменты с сортировкой по средним Z + var averageZFirst:Fragment; + var averageZLast:Fragment; + // Перебор геометрических объектов + while (geometry != null) { + next = geometry.next; + // Сортировка по предрасчитанному BSP + if (geometry.sorting == 3) { + geometry.next = bspGeometry; + bspGeometry = geometry; + } else { + // Сортировка по динамическому BSP + if (geometry.sorting == 2) { + if (dynamicBSPFirst != null) { + dynamicBSPLast.next = geometry.fragment; + } else { + dynamicBSPFirst = geometry.fragment; + dynamicBSPLast = dynamicBSPFirst; + dynamicBSPLast.geometry = geometry; + } + while (dynamicBSPLast.next != null) { + dynamicBSPLast = dynamicBSPLast.next; + dynamicBSPLast.geometry = geometry; + } + // Сортировка по средним Z + } else { + if (averageZFirst != null) { + averageZLast.next = geometry.fragment; + } else { + averageZFirst = geometry.fragment; + averageZLast = averageZFirst; + averageZLast.geometry = geometry; + } + while (averageZLast.next != null) { + averageZLast = averageZLast.next; + averageZLast.geometry = geometry; + } + } + geometry.fragment = null; + geometry.next = conflict; + conflict = geometry; + } + geometry = next; + } + // Соединение списков + if (conflict != null) { + geometry = conflict; + while (geometry.next != null) { + geometry = geometry.next; + } + geometry.next = bspGeometry; + } else { + conflict = bspGeometry; + } + // Разрешение конфликта + if (conflict != null) { + // Сбор первоначальной кучи фрагментов + var result:Fragment; + if (dynamicBSPFirst != null) { + result = dynamicBSPFirst; + if (averageZFirst != null) { + dynamicBSPLast.next = averageZFirst; + } + } else { + if (averageZFirst != null) { + result = averageZFirst; + } + } + // Если есть статические BSP + if (bspGeometry != null) { + // Встройка кучи в первый bsp с внутренней сортировкой + result = collectNode(bspGeometry.fragment, result, bspGeometry, true); + result.positive = null; + bspGeometry.fragment = null; + bspGeometry = bspGeometry.next; + // Встройка кучи в остальные bsp без внутренней сортировки + while (bspGeometry != null) { + result = collectNode(bspGeometry.fragment, result, bspGeometry, false); + result.positive = null; + bspGeometry.fragment = null; + bspGeometry = bspGeometry.next; + } + // Если есть динамические BSP + } else if (dynamicBSPFirst != null) { + result = result.next; + dynamicBSPFirst.next = null; + result = collectNode(dynamicBSPFirst, result, null, true); + result.positive = null; + // Если есть сортировка по средним Z + } else if (averageZFirst != null) { + result = sortFragments(result); + result.positive = null; + } + // Проецирование + geometry = conflict; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + geometry.uvts.length = geometry.verticesLength; + geometry.projectedVertices.length = geometry.numVertices << 1; + Utils3D.projectVectors(camera.projectionMatrix, geometry.vertices, geometry.projectedVertices, geometry.uvts); + geometry = geometry.next; + } + // Сбор отрисовочных вызовов + geometry = result.geometry; + fragment = result; + while (fragment.next != null) { + if (fragment.next.geometry != geometry) { + fragment.next.negative = result; + result = fragment.next; + fragment.next = null; + geometry = result.geometry; + fragment = result; + } else { + fragment = fragment.next; + } + } + // Дебаг + if (camera.debugMode) { + var debugCanvas:Canvas = canvas.getChildCanvas(true, false); + debugCanvas.gfx.lineStyle(0, 0xFF0000); + fragment = result; + while (fragment != null) { + fragment.geometry.debugPart(camera, debugCanvas, fragment); + fragment = fragment.negative; + } + } + // Отрисовка + fragment = result; + do { + result = fragment; + fragment = result.negative; + result.negative = null; + result.geometry.drawPart(camera, canvas, result); + } while (fragment != null); + // Зачистка + while (conflict != null) { + next = conflict.next; + conflict.destroy(); + conflict = next; + } + } + } + + // На выходе список, positive первого элемента которого указывает на последний элемент + private function collectNode(splitter:Fragment, source:Fragment, geometry:Geometry, sort:Boolean):Fragment { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var nodeFirst:Fragment; + var nodeLast:Fragment; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + // Сбор фрагментов ноды + if (geometry == null) { + nodeFirst = splitter; + nodeLast = splitter; + } else if (splitter.num > 0) { + nodeFirst = splitter; + nodeLast = splitter; + nodeLast.geometry = geometry; + while (nodeLast.next != null) { + nodeLast = nodeLast.next; + nodeLast.geometry = geometry; + } + } + // Перебор входной последовательности + while (source != null) { + next = source.next; + var sourceGeometry:Geometry = source.geometry; + var vertices:Vector. = sourceGeometry.vertices; + var uvts:Vector. = sourceGeometry.uvts; + var v:int = sourceGeometry.numVertices; + var vi:int = sourceGeometry.verticesLength; + var indices:Vector. = source.indices; + var num:int = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + var a:int = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (var i:int = 0; i < num; i++) { + // Вторая точка ребра + var b:int = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + vertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + vertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; vi++; + negativeIndices[negativeNum] = v; + negativeNum++; + positiveIndices[positiveNum] = v; + positiveNum++; + v++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + sourceGeometry.numVertices = v; + sourceGeometry.verticesLength = vi; + negative.num = negativeNum; + positive.num = positiveNum; + negative.geometry = sourceGeometry; + positive.geometry = sourceGeometry; + if (sourceGeometry.sorting == 2) { + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + } + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.geometry = null; + source.next = Fragment.collector; + Fragment.collector = source; + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = null; + if (nodeFirst != null) { + nodeLast.next = source; + } else { + nodeFirst = source; + } + nodeLast = source; + } + source = next; + } + // Сбор задней части + if (splitter.negative != null) { + negativeFirst = collectNode(splitter.negative, negativeFirst, geometry, sort); + negativeLast = negativeFirst.positive; + negativeFirst.positive = null; + splitter.negative = null; + } else if (sort && negativeFirst != negativeLast) { + if (negativeFirst.geometry.sorting == 2) { + next = negativeFirst.next; + negativeFirst.next = null; + negativeFirst = collectNode(negativeFirst, next, null, sort); + } else { + negativeFirst = sortFragments(negativeFirst); + } + negativeLast = negativeFirst.positive; + negativeFirst.positive = null; + } + // Сбор передней части + if (splitter.positive != null) { + positiveFirst = collectNode(splitter.positive, positiveFirst, geometry, sort); + positiveLast = positiveFirst.positive; + positiveFirst.positive = null; + splitter.positive = null; + } else if (sort && positiveFirst != positiveLast) { + if (positiveFirst.geometry.sorting == 2) { + next = positiveFirst.next; + positiveFirst.next = null; + positiveFirst = collectNode(positiveFirst, next, null, sort); + } else { + positiveFirst = sortFragments(positiveFirst); + } + positiveLast = positiveFirst.positive; + positiveFirst.positive = null; + } + // Если сплиттер не встроился в последовательность + if (splitter.num == 0) { + splitter.next = Fragment.collector; + Fragment.collector = splitter; + } + // Если камера спереди + if (splitter.offset < 0) { + if (negativeFirst != null) { + if (nodeFirst != null) { + negativeLast.next = nodeFirst; + if (positiveFirst != null) { + nodeLast.next = positiveFirst; + negativeFirst.positive = positiveLast; + } else { + negativeFirst.positive = nodeLast; + } + } else { + if (positiveFirst != null) { + negativeLast.next = positiveFirst; + negativeFirst.positive = positiveLast; + } else { + negativeFirst.positive = negativeLast; + } + } + return negativeFirst; + } else if (nodeFirst != null) { + if (positiveFirst != null) { + nodeLast.next = positiveFirst; + nodeFirst.positive = positiveLast; + } else { + nodeFirst.positive = nodeLast; + } + return nodeFirst; + } else { + positiveFirst.positive = positiveLast; + return positiveFirst; + } + } else { + if (positiveFirst != null) { + if (nodeFirst != null) { + positiveLast.next = nodeFirst; + if (negativeFirst != null) { + nodeLast.next = negativeFirst; + positiveFirst.positive = negativeLast; + } else { + positiveFirst.positive = nodeLast; + } + } else { + if (negativeFirst != null) { + positiveLast.next = negativeFirst; + positiveFirst.positive = negativeLast; + } else { + positiveFirst.positive = positiveLast; + } + } + return positiveFirst; + } else if (nodeFirst != null) { + if (negativeFirst != null) { + nodeLast.next = negativeFirst; + nodeFirst.positive = negativeLast; + } else { + nodeFirst.positive = nodeLast; + } + return nodeFirst; + } else { + negativeFirst.positive = negativeLast; + return negativeFirst; + } + } + } + + // На выходе список, positive первого элемента которого указывает на последний элемент + private function sortFragments(source:Fragment):Fragment { + var i:int; + var j:int; + var next:Fragment; + var first:Fragment; + var last:Fragment; + var fragments:Vector. = sortingFragments; + var fragmentsLength:int = 0; + // Заполнение вектора + while (source != null) { + next = source.next; + source.next = null; + var vertices:Vector. = source.geometry.vertices; + var indices:Vector. = source.indices; + var num:int = source.num; + var sum:Number = 0; + for (i = 0; i < num; i++) { + var vi:int = indices[i]*3 + 2; + sum += vertices[vi]; + } + source.offset = sum/num; + fragments[fragmentsLength] = source; + fragmentsLength++; + source = next; + } + // Сортировка + var stack:Vector. = sortingStack; + stack[0] = 0; + stack[1] = fragmentsLength - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + var k:int = r + l; + var t:int = k >> 1; + next = fragments[t]; + var median:Number = next.offset; + while (i <= j) { + var left:Fragment = fragments[i]; + while (left.offset > median) { + i++; + left = fragments[i]; + } + var right:Fragment = fragments[j]; + while (right.offset < median) { + j--; + right = fragments[j]; + } + if (i <= j) { + fragments[i] = right; + fragments[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Сбор + for (i = 0; i < fragmentsLength; i++) { + next = fragments[i]; + if (first != null) { + last.next = next; + } else { + first = next; + } + last = next; + fragments[i] = null; + } + first.positive = last; + return first; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/DirectionContainer.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/DirectionContainer.as new file mode 100644 index 0000000..0a1d15e --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/DirectionContainer.as @@ -0,0 +1,38 @@ +package alternativa.engine3d.containers { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class DirectionContainer extends Object3DContainer { + + public var direction:Vector3D = new Vector3D(0, 0, -1); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var child:Object3D; + if (Vector3D.Z_AXIS.dotProduct(object.cameraMatrix.deltaTransformVector(direction)) < 0) { + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } else { + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/KDTree.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/KDTree.as new file mode 100644 index 0000000..79e9918 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/KDTree.as @@ -0,0 +1,841 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Occluder; + import alternativa.engine3d.objects.Reference; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого помещены в бинарную древовидную структуру. + * Для построения дерева нужно добавить статические дочерние объекты + * с помощью addStaticChild(), затем вызвать createTree(). По баундам статических объектов + * построится ориентированная по осям бинарная древовидная структура (KD - частный случай BSP). + * Динамические объекты, можно в любое время добавлять addDynamicChild() и удалять removeDynamicChild(). + * Объекты, добавленные с помощью addChild() будут отрисовываться поверх всего в порядке добавления. + */ + public class KDTree extends ConflictContainer { + + public var debugAlphaFade:Number = 0.8; + + private var root:KDNode; + + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0 || root != null; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Если есть корневая нода + if (root != null) { + // Расчёт инверсной матрицы камеры и позицци камеры в контейнере + calculateInverseCameraMatrix(object.cameraMatrix); + // Расчёт плоскостей камеры в контейнере + calculateCameraPlanes(camera); + // Проверка на видимость рутовой ноды + var culling:int = cullingInContainer(camera, root.boundBox, object.culling); + if (culling >= 0) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Окклюдеры + numOccluders = 0; + if (camera.numOccluders > 0) { + updateOccluders(camera); + } + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + var current:Geometry = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + inverseCameraMatrix.transformVectors(current.vertices, current.vertices); + current.calculateAABB(); + current = current.next; + } + // Отрисовка дерева + drawNode(root, culling, camera, object, canvas, geometry); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } else { + super.draw(camera, object, parentCanvas); + } + } else { + super.draw(camera, object, parentCanvas); + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + // Ноды + if (debugResult & Debug.NODES) { + if (root != null) { + var culling:int = cullingInContainer(camera, root.boundBox, object.culling); + if (culling >= 0) { + debugNode(root, culling, camera, object, canvas, 1); + } + } + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + private function drawNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + var negativePart:Geometry; + var positivePart:Geometry; + if (camera.occludedAll) { + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + return; + } + var nodeObjects:Vector. = node.objects; + var nodeNumObjects:int = node.numObjects; + var nodeNumNonOccluders:int = node.numNonOccluders; + var staticChild:Object3D; + // Узловая нода + if (node.negative != null) { + var negativeCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.negative.boundBox, culling) : 0; + var positiveCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.positive.boundBox, culling) : 0; + var axisX:Boolean = node.axis == 0; + var axisY:Boolean = node.axis == 1; + var min:Number; + var max:Number; + // Если видны обе дочерние ноды + if (negativeCulling >= 0 && positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max <= node.maxCoord) { + if (min < node.minCoord) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + negativePart = geometry.create(); + positivePart = geometry.create(); + geometry.split(axisX, axisY, node.coord, threshold, negativePart, positivePart); + geometry.destroy(); + // Если негативный не пустой + if (negativePart.fragment != null) { + negativePart.next = negative; + negative = negativePart; + } else { + negativePart.destroy(); + } + // Если позитивный не пустой + if (positivePart.fragment != null) { + positivePart.next = positive; + positive = positivePart; + } else { + positivePart.destroy(); + } + } + } + geometry = next; + } + // Отрисовка дочерних нод и объектов в плоскости + if (axisX && cameraX > node.coord || axisY && cameraY > node.coord || !axisX && !axisY && cameraZ > node.coord) { + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) { + updateOccluders(camera); + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + } else { + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + } + middle.destroy(); + middle = next; + } + // Отрисовка статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) { + updateOccluders(camera); + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + } + // Если видна только негативная + } else if (negativeCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max <= node.maxCoord) { + geometry.next = negative; + negative = geometry; + } else if (min >= node.minCoord) { + geometry.destroy(); + } else { + negativePart = geometry.create(); + geometry.split(axisX, axisY, node.coord, threshold, negativePart, null); + geometry.destroy(); + // Если негативный не пустой + if (negativePart.fragment != null) { + negativePart.next = negative; + negative = negativePart; + } else { + negativePart.destroy(); + } + } + } + geometry = next; + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Если видна только позитивная + } else if (positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + + if (max <= node.maxCoord) { + geometry.destroy(); + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + positivePart = geometry.create(); + geometry.split(axisX, axisY, node.coord, threshold, null, positivePart); + geometry.destroy(); + // Если позитивный не пустой + if (positivePart.fragment != null) { + positivePart.next = positive; + positive = positivePart; + } else { + positivePart.destroy(); + } + } + } + geometry = next; + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Если обе ноды не видны + } else { + // Уничтожение динамиков + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + } + // Конечная нода + } else { + // Если есть статические объекты, не считая окклюдеры + if (nodeNumNonOccluders > 0) { + // Если есть конфликт + if (nodeNumNonOccluders > 1 || geometry != null) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Превращение статиков в геометрию + for (i = 0; i < nodeNumNonOccluders; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + geometry = staticChild.getGeometry(camera, staticChild); + while (geometry != null) { + next = geometry.next; + geometry.next = middle; + middle = geometry; + geometry = next; + } + } + } + // Разруливаем конфликт + if (middle != null) { + if (middle.next != null) { + drawConflictGeometry(camera, object, canvas, middle); + } else { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1); + middle.draw(camera, canvas, threshold); + middle.destroy(); + } + } + } else { + // Если только один статик + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + staticChild.culling = culling; + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Если нет статических объектов + } else { + // Если есть динамические объекты + if (geometry != null) { + // Если динамических объектов несколько + if (geometry.next != null) { + // Если есть окклюдеры + if (numOccluders > 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Если остались объекты + if (middle != null) { + if (middle.next != null) { + // Разруливание + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + if (!geometry.viewAligned) { + geometry.calculateOOBB(); + } + geometry = geometry.next; + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry = geometry.next; + } + drawConflictGeometry(camera, object, canvas, middle); + } + } else { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + } + } + } else { + // Разруливание + middle = geometry; + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + if (!geometry.viewAligned) { + geometry.calculateOOBB(); + } + geometry = geometry.next; + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry = geometry.next; + } + drawConflictGeometry(camera, object, canvas, middle); + } + } + } else { + // Проверка с окклюдерами и отрисовка + if (geometry.numOccluders >= numOccluders || !occludeGeometry(camera, geometry)) { + if (camera.debugMode) geometry.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + geometry.draw(camera, canvas, threshold, object.cameraMatrix); + } + geometry.destroy(); + } + } + } + // Если в ноде есть окклюдеры + if (nodeNumObjects > nodeNumNonOccluders) { + for (i = nodeNumNonOccluders; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + updateOccluders(camera); + } + } + } + + static private const nodeVertices:Vector. = new Vector.(12, true); + static private const nodeProjectedVertices:Vector. = new Vector.(8, true); + static private const nodeUVTs:Vector. = new Vector.(12, true); + + private function debugNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, alpha:Number):void { + if (node.negative != null) { + var negativeCulling:int = (culling > 0) ? cullingInContainer(camera, node.negative.boundBox, culling) : 0; + var positiveCulling:int = (culling > 0) ? cullingInContainer(camera, node.positive.boundBox, culling) : 0; + if (negativeCulling >= 0) { + debugNode(node.negative, negativeCulling, camera, object, canvas, alpha*debugAlphaFade); + } + if (positiveCulling >= 0) { + debugNode(node.positive, positiveCulling, camera, object, canvas, alpha*debugAlphaFade); + } + + if (node.axis == 0) { + nodeVertices[0] = node.coord; + nodeVertices[1] = node.boundBox.minY; + nodeVertices[2] = node.boundBox.maxZ; + + nodeVertices[3] = node.coord; + nodeVertices[4] = node.boundBox.maxY; + nodeVertices[5] = node.boundBox.maxZ; + + nodeVertices[6] = node.coord; + nodeVertices[7] = node.boundBox.maxY; + nodeVertices[8] = node.boundBox.minZ; + + nodeVertices[9] = node.coord; + nodeVertices[10] = node.boundBox.minY; + nodeVertices[11] = node.boundBox.minZ; + } else if (node.axis == 1) { + nodeVertices[0] = node.boundBox.maxX; + nodeVertices[1] = node.coord; + nodeVertices[2] = node.boundBox.maxZ; + + nodeVertices[3] = node.boundBox.minX; + nodeVertices[4] = node.coord; + nodeVertices[5] = node.boundBox.maxZ; + + nodeVertices[6] = node.boundBox.minX; + nodeVertices[7] = node.coord; + nodeVertices[8] = node.boundBox.minZ; + + nodeVertices[9] = node.boundBox.maxX; + nodeVertices[10] = node.coord; + nodeVertices[11] = node.boundBox.minZ; + } else { + nodeVertices[0] = node.boundBox.minX; + nodeVertices[1] = node.boundBox.minY; + nodeVertices[2] = node.coord; + + nodeVertices[3] = node.boundBox.maxX; + nodeVertices[4] = node.boundBox.minY; + nodeVertices[5] = node.coord; + + nodeVertices[6] = node.boundBox.maxX; + nodeVertices[7] = node.boundBox.maxY; + nodeVertices[8] = node.coord; + + nodeVertices[9] = node.boundBox.minX; + nodeVertices[10] = node.boundBox.maxY; + nodeVertices[11] = node.coord; + } + object.cameraMatrix.transformVectors(nodeVertices, nodeVertices); + var i:int; + for (i = 0; i < 12; i += 3) { + if (nodeVertices[int(i + 2)] <= 0) break; + } + if (i == 12) { + Utils3D.projectVectors(camera.projectionMatrix, nodeVertices, nodeProjectedVertices, nodeUVTs); + canvas.gfx.lineStyle(0, (node.axis == 0) ? 0xFF0000 : ((node.axis == 1) ? 0x00FF00 : 0x0000FF)); + canvas.gfx.moveTo(nodeProjectedVertices[0], nodeProjectedVertices[1]); + canvas.gfx.lineTo(nodeProjectedVertices[2], nodeProjectedVertices[3]); + canvas.gfx.lineTo(nodeProjectedVertices[4], nodeProjectedVertices[5]); + canvas.gfx.lineTo(nodeProjectedVertices[6], nodeProjectedVertices[7]); + canvas.gfx.lineTo(nodeProjectedVertices[0], nodeProjectedVertices[1]); + } + } + } + + public function createTree(staticObjects:Vector., boundBox:BoundBox = null):void { + var numStaticChildren:int = staticObjects.length; + if (numStaticChildren > 0) { + // Создаём корневую ноду + root = new KDNode(); + root.objects = new Vector.(); + root.bounds = new Vector.(); + root.numObjects = 0; + // Расчитываем баунды объектов и рутовой ноды + root.boundBox = (boundBox != null) ? boundBox : new BoundBox(); + // Сначала добавляем не окклюдеры + var staticOccluders:Vector. = new Vector.(); + var staticOccludersLength:int = 0; + var object:Object3D; + var objectBoundBox:BoundBox; + for (var i:int = 0; i < numStaticChildren; i++) { + object = staticObjects[i]; + // Поиск оригинального объекта + var source:Object3D = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + // Если окклюдер + if (source is Occluder) { + staticOccluders[staticOccludersLength++] = object; + } else { + objectBoundBox = object.calculateBoundBox(object.matrix); + root.objects[root.numObjects] = object; + root.bounds[root.numObjects++] = objectBoundBox; + root.boundBox.addBoundBox(objectBoundBox); + } + } + // Добавляем окклюдеры + for (i = 0; i < staticOccludersLength; i++) { + object = staticOccluders[i]; + objectBoundBox = object.calculateBoundBox(object.matrix); + root.objects[root.numObjects] = object; + root.bounds[root.numObjects++] = objectBoundBox; + root.boundBox.addBoundBox(objectBoundBox); + } + // Разделяем рутовую ноду + splitNode(root); + } + } + + private var splitAxis:int; + private var splitCoord:Number; + private var splitCost:Number; + static private const nodeBoundBoxThreshold:BoundBox = new BoundBox(); + static private const splitCoordsX:Vector. = new Vector.(); + static private const splitCoordsY:Vector. = new Vector.(); + static private const splitCoordsZ:Vector. = new Vector.(); + private function splitNode(node:KDNode):void { + + var object:Object3D, boundBox:BoundBox, i:int, j:int, k:int, c1:Number, c2:Number, coordMin:Number, coordMax:Number, area:Number, areaNegative:Number, areaPositive:Number, numNegative:int, numPositive:int, conflict:Boolean, cost:Number; + var nodeBoundBox:BoundBox = node.boundBox; + + // Подготовка баунда с погрешностями + nodeBoundBoxThreshold.minX = nodeBoundBox.minX + threshold; + nodeBoundBoxThreshold.minY = nodeBoundBox.minY + threshold; + nodeBoundBoxThreshold.minZ = nodeBoundBox.minZ + threshold; + nodeBoundBoxThreshold.maxX = nodeBoundBox.maxX - threshold; + nodeBoundBoxThreshold.maxY = nodeBoundBox.maxY - threshold; + nodeBoundBoxThreshold.maxZ = nodeBoundBox.maxZ - threshold; + var doubleThreshold:Number = threshold + threshold; + + // Собираем опорные координаты + var numSplitCoordsX:int = 0, numSplitCoordsY:int = 0, numSplitCoordsZ:int = 0; + for (i = 0; i < node.numObjects; i++) { + boundBox = node.bounds[i]; + if (boundBox.maxX - boundBox.minX <= doubleThreshold) { + if (boundBox.minX <= nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.minX; + else if (boundBox.maxX >= nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.maxX; + else splitCoordsX[numSplitCoordsX++] = (boundBox.minX + boundBox.maxX)*0.5; + } else { + if (boundBox.minX > nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = boundBox.minX; + if (boundBox.maxX < nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = boundBox.maxX; + } + if (boundBox.maxY - boundBox.minY <= doubleThreshold) { + if (boundBox.minY <= nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.minY; + else if (boundBox.maxY >= nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.maxY; + else splitCoordsY[numSplitCoordsY++] = (boundBox.minY + boundBox.maxY)*0.5; + } else { + if (boundBox.minY > nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = boundBox.minY; + if (boundBox.maxY < nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = boundBox.maxY; + } + if (boundBox.maxZ - boundBox.minZ <= doubleThreshold) { + if (boundBox.minZ <= nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.minZ; + else if (boundBox.maxZ >= nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.maxZ; + else splitCoordsZ[numSplitCoordsZ++] = (boundBox.minZ + boundBox.maxZ)*0.5; + } else { + if (boundBox.minZ > nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.minZ; + if (boundBox.maxZ < nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.maxZ; + } + } + + // Убираем дубликаты координат, ищем наилучший сплит + splitAxis = -1; splitCost = Number.MAX_VALUE; + i = 0; area = (nodeBoundBox.maxY - nodeBoundBox.minY)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsX) { + if (!isNaN(c1 = splitCoordsX[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minX); + areaPositive = area*(nodeBoundBox.maxX - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxX <= coordMax) { + if (boundBox.minX < coordMin) numNegative++; + } else { + if (boundBox.minX >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 0; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsX) if ((c2 = splitCoordsX[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsX[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsY) { + if (!isNaN(c1 = splitCoordsY[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minY); + areaPositive = area*(nodeBoundBox.maxY - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxY <= coordMax) { + if (boundBox.minY < coordMin) numNegative++; + } else { + if (boundBox.minY >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 1; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsY) if ((c2 = splitCoordsY[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsY[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxY - nodeBoundBox.minY); + while (i < numSplitCoordsZ) { + if (!isNaN(c1 = splitCoordsZ[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minZ); + areaPositive = area*(nodeBoundBox.maxZ - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxZ <= coordMax) { + if (boundBox.minZ < coordMin) numNegative++; + } else { + if (boundBox.minZ >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 2; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsZ) if ((c2 = splitCoordsZ[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsZ[j] = NaN; + } + } + + // Если сплит не найден, выходим + if (splitAxis < 0) { + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + return; + } + + // Разделяем ноду + var axisX:Boolean = splitAxis == 0, axisY:Boolean = splitAxis == 1; + node.axis = splitAxis; + node.coord = splitCoord; + node.minCoord = coordMin = splitCoord - threshold; + node.maxCoord = coordMax = splitCoord + threshold; + + // Создаём дочерние ноды + node.negative = new KDNode(); + node.positive = new KDNode(); + node.negative.boundBox = nodeBoundBox.clone(); + node.positive.boundBox = nodeBoundBox.clone(); + node.negative.numObjects = 0; + node.positive.numObjects = 0; + if (axisX) node.negative.boundBox.maxX = node.positive.boundBox.minX = splitCoord; + else if (axisY) node.negative.boundBox.maxY = node.positive.boundBox.minY = splitCoord; + else node.negative.boundBox.maxZ = node.positive.boundBox.minZ = splitCoord; + + // Распределяем объекты по дочерним нодам + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + boundBox = node.bounds[i]; + var min:Number = axisX ? boundBox.minX : (axisY ? boundBox.minY : boundBox.minZ); + var max:Number = axisX ? boundBox.maxX : (axisY ? boundBox.maxY : boundBox.maxZ); + if (max <= coordMax) { + if (min < coordMin) { + // Объект в негативной стороне + if (node.negative.objects == null) node.negative.objects = new Vector.(), node.negative.bounds = new Vector.(); + node.negative.objects[node.negative.numObjects] = object, node.negative.bounds[node.negative.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Остаётся в ноде + } + } else { + if (min >= coordMin) { + // Объект в положительной стороне + if (node.positive.objects == null) node.positive.objects = new Vector.(), node.positive.bounds = new Vector.(); + node.positive.objects[node.positive.numObjects] = object, node.positive.bounds[node.positive.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Распилился + } + } + } + + // Очистка списка объектов + for (i = 0, j = 0; i < node.numObjects; i++) if (node.objects[i] != null) node.objects[j] = node.objects[i], node.bounds[j++] = node.bounds[i]; + if (j > 0) { + node.numObjects = node.objects.length = node.bounds.length = j; + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + } else { + node.numObjects = node.numNonOccluders = 0, node.objects = null, node.bounds = null; + } + + // Разделение дочерних нод + if (node.negative.objects != null) splitNode(node.negative); + if (node.positive.objects != null) splitNode(node.positive); + } + + } +} + +import alternativa.engine3d.core.BoundBox; +import alternativa.engine3d.core.Object3D; + +class KDNode { + + public var positive:KDNode; + public var negative:KDNode; + + public var axis:int; + public var coord:Number; + public var minCoord:Number; + public var maxCoord:Number; + + public var boundBox:BoundBox; + + public var objects:Vector.; + public var bounds:Vector.; + + public var numObjects:int = 0; + public var numNonOccluders:int = 0; + +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/SkyBox.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/SkyBox.as new file mode 100644 index 0000000..66c8427 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/SkyBox.as @@ -0,0 +1,138 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BitmapData; + + public class SkyBox extends Object3DContainer { + + private var backPlane:Mesh; + private var frontPlane:Mesh; + private var topPlane:Mesh; + private var bottomPlane:Mesh; + private var leftPlane:Mesh; + private var rightPlane:Mesh; + + public function SkyBox(size:Number = 1000):void { + backPlane = new Mesh(); + frontPlane = new Mesh(); + topPlane = new Mesh(); + bottomPlane = new Mesh(); + leftPlane = new Mesh(); + rightPlane = new Mesh(); + + backPlane.clipping = 2; + frontPlane.clipping = 2; + topPlane.clipping = 2; + bottomPlane.clipping = 2; + leftPlane.clipping = 2; + rightPlane.clipping = 2; + + backPlane.vertices = Vector.([ + -size, -size, -size, + -size, -size, size, + size, -size, size, + size, -size, -size, + ]); + frontPlane.vertices = Vector.([ + size, size, -size, + size, size, size, + -size, size, size, + -size, size, -size, + ]); + topPlane.vertices = Vector.([ + size, size, size, + size, -size, size, + -size, -size, size, + -size, size, size, + ]); + bottomPlane.vertices = Vector.([ + size, -size, -size, + size, size, -size, + -size, size, -size, + -size, -size, -size, + ]); + leftPlane.vertices = Vector.([ + -size, size, -size, + -size, size, size, + -size, -size, size, + -size, -size, -size, + ]); + rightPlane.vertices = Vector.([ + size, -size, -size, + size, -size, size, + size, size, size, + size, size, -size, + ]); + + var uvts:Vector. = Vector.([1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]); + backPlane.uvts = uvts; + frontPlane.uvts = uvts; + topPlane.uvts = uvts; + bottomPlane.uvts = uvts; + leftPlane.uvts = uvts; + rightPlane.uvts = uvts; + + var indices:Vector. = Vector.([4, 0, 1, 2, 3]); + backPlane.indices = indices; + frontPlane.indices = indices; + topPlane.indices = indices; + bottomPlane.indices = indices; + leftPlane.indices = indices; + rightPlane.indices = indices; + + backPlane.numVertices = 4; + frontPlane.numVertices = 4; + topPlane.numVertices = 4; + bottomPlane.numVertices = 4; + leftPlane.numVertices = 4; + rightPlane.numVertices = 4; + + backPlane.numFaces = 1; + frontPlane.numFaces = 1; + topPlane.numFaces = 1; + bottomPlane.numFaces = 1; + leftPlane.numFaces = 1; + rightPlane.numFaces = 1; + + backPlane.poly = true; + frontPlane.poly = true; + topPlane.poly = true; + bottomPlane.poly = true; + leftPlane.poly = true; + rightPlane.poly = true; + + addChild(backPlane); + addChild(frontPlane); + addChild(topPlane); + addChild(bottomPlane); + addChild(leftPlane); + addChild(rightPlane); + } + + public function set backTexture(value:BitmapData):void { + backPlane.texture = value; + } + + public function set frontTexture(value:BitmapData):void { + frontPlane.texture = value; + } + + public function set topTexture(value:BitmapData):void { + topPlane.texture = value; + } + + public function set bottomTexture(value:BitmapData):void { + bottomPlane.texture = value; + } + + public function set leftTexture(value:BitmapData):void { + leftPlane.texture = value; + } + + public function set rightTexture(value:BitmapData):void { + rightPlane.texture = value; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/containers/SplitContainer.as b/Alternativa3D7/7.0/alternativa/engine3d/containers/SplitContainer.as new file mode 100644 index 0000000..0d20d23 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/containers/SplitContainer.as @@ -0,0 +1,45 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SplitContainer extends Object3DContainer { + + public var splitPlane:Vector3D = new Vector3D(0, 0, 1, 0); + static private const invertCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraPosition:Vector. = Vector.([0, 0, 0]); + static private const invertCameraPosition:Vector. = new Vector.(3, true); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var child:Object3D; + invertCameraMatrix.identity(); + invertCameraMatrix.prepend(object.cameraMatrix); + invertCameraMatrix.invert(); + invertCameraMatrix.transformVectors(cameraPosition, invertCameraPosition); + if (invertCameraPosition[0]*splitPlane.x + invertCameraPosition[1]*splitPlane.y + invertCameraPosition[2]*splitPlane.z < splitPlane.w) { + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } else { + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/controllers/SimpleObjectController.as b/Alternativa3D7/7.0/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..8f13cfc --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,452 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + + import flash.display.InteractiveObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.ui.Keyboard; + import flash.utils.getTimer; + + /** + * + */ + public class SimpleObjectController { + + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + + public var speed:Number; + public var speedMultiplier:Number; + public var mouseSensitivity:Number; + public var maxPitch:Number = Number.MAX_VALUE; + public var minPitch:Number = -Number.MAX_VALUE; + + private var eventSource:InteractiveObject; + private var _object:Object3D; + + private var _up:Boolean; + private var _down:Boolean; + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _accelerate:Boolean; + + private var displacement:Vector3D = new Vector3D(); + private var mousePoint:Point = new Point(); + private var mouseLook:Boolean; + private var objectTransform:Vector.; + + private var time:int; + + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + private var actionBindings:Object = {}; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Object = {}; + + /** + * + * @param eventSource источник событий для контроллера + * @param speed скорость поступательного перемещения объекта + * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши + */ + public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { + this.eventSource = eventSource; + this.object = object; + this.speed = speed; + this.speedMultiplier = speedMultiplier; + this.mouseSensitivity = mouseSensitivity; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_ACCELERATE] = accelerate; + + setDefaultBindings(); + + enable(); + } + + /** + * Активирует контроллер. + */ + public function enable():void { + eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Дективирует контроллер. + */ + public function disable():void { + eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + stopMouseLook(); + } + + /** + * + */ + private function onMouseDown(e:MouseEvent):void { + startMouseLook(); + } + + /** + * + */ + private function onMouseUp(e:MouseEvent):void { + stopMouseLook(); + } + + /** + * Включает режим взгляда мышью. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Отключает режим взгляда мышью. + */ + public function stopMouseLook():void { + mouseLook = false; + } + + /** + * + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + + /** + * Управляемый объект. + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * + */ + public function get object():Object3D { + return _object; + } + + /** + * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.matrix.decompose(); + } + + /** + * Вычисляет новое положение объекта, используя внутренний счётчик времени. + */ + public function update():void { + if (_object == null) return; + + var frameTime:Number = time; + time = getTimer(); + frameTime = 0.001*(time - frameTime); + if (frameTime > 0.1) frameTime = 0.1; + + var moved:Boolean = false; + + if (mouseLook) { + var dx:Number = eventSource.mouseX - mousePoint.x; + var dy:Number = eventSource.mouseY - mousePoint.y; + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + var v:Vector3D = objectTransform[1]; + v.x -= dy*Math.PI/180*mouseSensitivity; + if (v.x > maxPitch) v.x = maxPitch; + if (v.x < minPitch) v.x = minPitch; + v.z -= dx*Math.PI/180*mouseSensitivity; + moved = true; + } + + displacement.x = _right ? 1 : (_left ? -1 : 0); + displacement.y = _forward ? 1 : (_back ? -1 : 0); + displacement.z = _up ? 1 : (_down ? -1 : 0); + if (displacement.lengthSquared > 0) { + if (_object is Camera3D) { + var tmp:Number = displacement.z; + displacement.z = displacement.y; + displacement.y = -tmp; + } + deltaTransformVector(displacement); + if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); + else displacement.scaleBy(speed*frameTime/displacement.length); + (objectTransform[0] as Vector3D).incrementBy(displacement); + moved = true; + } + + if (moved) _object.matrix.recompose(objectTransform); + } + + /** + * + * @param pos + */ + public function setObjectPos(pos:Vector3D):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = pos.x; + v.y = pos.y; + v.z = pos.z; + } + } + + /** + * + * @param pos + */ + public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = x; + v.y = y; + v.z = z; + } + } + + /** + * + * @param point + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * + * @param x + * @param y + * @param z + */ + public function lookAtXYZ(x:Number, y:Number, z:Number):void { + if (_object == null) return; + var v:Vector3D = objectTransform[0]; + var dx:Number = x - v.x; + var dy:Number = y - v.y; + var dz:Number = z - v.z; + v = objectTransform[1]; + v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); + if (_object is Camera3D) v.x -= 0.5*Math.PI; + v.y = 0; + v.z = -Math.atan2(dx, dy); + _object.matrix.recompose(objectTransform); + } + + private var _vin:Vector. = new Vector.(3); + private var _vout:Vector. = new Vector.(3); + + private function deltaTransformVector(v:Vector3D):void { + _vin[0] = v.x; + _vin[1] = v.y; + _vin[2] = v.z; + _object.matrix.transformVectors(_vin, _vout); + var c:Vector3D = objectTransform[0]; + v.x = _vout[0] - c.x; + v.y = _vout[1] - c.y; + v.z = _vout[2] - c.z; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:* in keyBindings) delete keyBindings[key]; + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + bindKey(87, ACTION_FORWARD); + bindKey(83, ACTION_BACK); + bindKey(65, ACTION_LEFT); + bindKey(68, ACTION_RIGHT); + bindKey(69, ACTION_UP); + bindKey(67, ACTION_DOWN); + bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); + + bindKey(Keyboard.UP, ACTION_FORWARD); + bindKey(Keyboard.DOWN, ACTION_BACK); + bindKey(Keyboard.LEFT, ACTION_LEFT); + bindKey(Keyboard.RIGHT, ACTION_RIGHT); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/BackfaceCulling.as b/Alternativa3D7/7.0/alternativa/engine3d/core/BackfaceCulling.as new file mode 100644 index 0000000..f67b960 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/BackfaceCulling.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.core { + + public class BackfaceCulling { + + /** + * Отсечение по динамически расчитываемым нормалям. + */ + static public const DYNAMIC_NORMALS:int = 0; + /** + * Отсечение по предрасчитанным нормалям. + */ + static public const STATIC_NORMALS:int = 1; + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/BoundBox.as b/Alternativa3D7/7.0/alternativa/engine3d/core/BoundBox.as new file mode 100644 index 0000000..3b1bf6f --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/BoundBox.as @@ -0,0 +1,64 @@ +package alternativa.engine3d.core { + + public class BoundBox { + + public var minX:Number = Number.MAX_VALUE; + public var minY:Number = Number.MAX_VALUE; + public var minZ:Number = Number.MAX_VALUE; + public var maxX:Number = -Number.MAX_VALUE; + public var maxY:Number = -Number.MAX_VALUE; + public var maxZ:Number = -Number.MAX_VALUE; + + public function setSize(minX:Number, minY:Number, minZ:Number, maxX:Number, maxY:Number, maxZ:Number):void { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public function addBoundBox(boundBox:BoundBox):void { + minX = (boundBox.minX < minX) ? boundBox.minX : minX; + minY = (boundBox.minY < minY) ? boundBox.minY : minY; + minZ = (boundBox.minZ < minZ) ? boundBox.minZ : minZ; + maxX = (boundBox.maxX > maxX) ? boundBox.maxX : maxX; + maxY = (boundBox.maxY > maxY) ? boundBox.maxY : maxY; + maxZ = (boundBox.maxZ > maxZ) ? boundBox.maxZ : maxZ; + } + + public function addPoint(x:Number, y:Number, z:Number):void { + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + if (z < minZ) minZ = z; + if (z > maxZ) maxZ = z; + } + + public function infinity():void { + minX = minY = minZ = Number.MAX_VALUE; + maxX = maxY = maxZ = -Number.MAX_VALUE; + } + + public function copyFrom(boundBox:BoundBox):void { + minX = boundBox.minX; + minY = boundBox.minY; + minZ = boundBox.minZ; + maxX = boundBox.maxX; + maxY = boundBox.maxY; + maxZ = boundBox.maxZ; + } + + public function clone():BoundBox { + var clone:BoundBox = new BoundBox(); + clone.copyFrom(this); + return clone; + } + + public function toString():String { + return "BoundBox [" + minX.toFixed(2) + ", " + minY.toFixed(2) + ", " + minZ.toFixed(2) + " - " + maxX.toFixed(2) + ", " + maxY.toFixed(2) + ", " + maxZ.toFixed(2) + "]"; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Camera3D.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..5815f4a --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,411 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.events.Event; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.System; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + import flash.utils.getTimer; + + use namespace alternativa3d; + + public class Camera3D extends Object3D { + + /** + * Вьюпорт камеры. + */ + public var canvas:Canvas; + public var fov:Number = Math.PI/2; + /** + * Ширина вьюпорта. + */ + public var width:Number = 500; + /** + * Высота вьюпорта. + */ + public var height:Number = 500; + + public var nearClipping:Number = 0; + public var farClipping:Number = 5000; + public var farFalloff:Number = 4000; + + // Матрица проецирования + alternativa3d var projectionMatrix:Matrix3D; + alternativa3d var projectionMatrixData:Vector. = new Vector.(16, true); + // Параметры перспективы + alternativa3d var viewSize:Number; + alternativa3d var viewSizeX:Number; + alternativa3d var viewSizeY:Number; + alternativa3d var perspectiveScaleX:Number; + alternativa3d var perspectiveScaleY:Number; + alternativa3d var focalLength:Number; + // Перекрытия + alternativa3d var occlusionPlanes:Vector.> = new Vector.>(); + alternativa3d var occlusionEdges:Vector.> = new Vector.>(); + alternativa3d var numOccluders:int; + alternativa3d var occludedAll:Boolean; + + public function Camera3D() { + updateProjection(); + } + + override alternativa3d function get canDraw():Boolean { + return false; + } + + /** + * Отрисовка иерархии объектов, в которой находится камера. + * Перед render(), если менялись параметры камеры, нужно вызвать updateProjection(). + */ + public function render():void { + // Расчёт матрицы перевода из рута в камеру + cameraMatrix.identity(); + var object:Object3D = this; + while (object._parent != null) { + cameraMatrix.append(object.matrix); + object = object._parent; + } + cameraMatrix.invert(); + cameraMatrix.appendScale(perspectiveScaleX, perspectiveScaleY, 1); + + numOccluders = 0; + occludedAll = false; + + numTriangles = 0; + + // Отрисовка + if (object.visible && object.canDraw) { + object.cameraMatrix.identity(); + object.cameraMatrix.prepend(cameraMatrix); + object.cameraMatrix.prepend(object.matrix); + if (object.cullingInCamera(this, 63) >= 0) { + // Отрисовка объекта + canvas.numDraws = 0; + if (debugMode) object.debug(this, object, canvas); + object.draw(this, object, canvas); + // Зачистка ненужных канвасов + canvas.removeChildren(canvas.numDraws); + } else { + // Если отсеклось, зачищаем рутовый канвас + canvas.removeChildren(0); + } + } + } + + /** + * После изменения параметров fov, width, height нужно вызвать этот метод. + */ + public function updateProjection():void { + // Расчёт параметров перспективы + viewSize = Math.sqrt(width*width + height*height)*0.5; + focalLength = viewSize/Math.tan(fov*0.5); + viewSizeX = width*0.5; + viewSizeY = height*0.5; + perspectiveScaleX = focalLength/viewSizeX; + perspectiveScaleY = focalLength/viewSizeY; + // Подготовка матрицы проецирования + projectionMatrixData[0] = viewSizeX; + projectionMatrixData[5] = viewSizeY; + projectionMatrixData[10] = 1; + projectionMatrixData[11] = 1; + projectionMatrix = new Matrix3D(projectionMatrixData); + } + + private static var _tmpv:Vector. = new Vector.(3); + + /** + * @param v + * @param result + */ + public function projectGlobal(v:Vector3D, result:Vector3D):void { + _tmpv[0] = v.x; _tmpv[1] = v.y; _tmpv[2] = v.z; + cameraMatrix.transformVectors(_tmpv, _tmpv); + projectionMatrix.transformVectors(_tmpv, _tmpv); + result.z = _tmpv[2]; + result.x = _tmpv[0]/result.z; + result.y = _tmpv[1]/result.z; + } + + // DEBUG + + // Режим отладки + public var debugMode:Boolean = false; + + // Список объектов дебага + private var debugSet:Object = new Object(); + + // Добавить в дебаг + public function addToDebug(debug:int, ... rest):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + for (var i:int = 0; i < rest.length;) debugSet[debug][rest[i++]] = true; + } + + // Убрать из дебага + public function removeFromDebug(debug:int, ... rest):void { + if (debugSet[debug]) { + for (var i:int = 0; i < rest.length;) delete debugSet[debug][rest[i++]]; + for (var key:* in debugSet[debug]); + if (!key) delete debugSet[debug]; + } + } + + // Проверка, находится ли объект или один из классов, от которых он нследован, в дебаге + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 256; debug = debug << 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + public var diagram:Sprite = createDiagram(); + + private var fpsTextField:TextField; + private var memoryTextField:TextField; + private var trianglesTextField:TextField; + private var timerTextField:TextField; + private var graph:Bitmap; + private var rect:Rectangle; + + private var _diagramAlign:String = "TR"; + private var _diagramHorizontalMargin:Number = 2; + private var _diagramVerticalMargin:Number = 2; + + public var fpsUpdatePeriod:int = 10; + private var fpsUpdateCounter:int; + private var previousFrameTime:int; + private var previousPeriodTime:int; + + private var maxMemory:int; + alternativa3d var numTriangles:int; + + public var timerUpdatePeriod:int = 10; + private var timerUpdateCounter:int; + private var timeSum:int; + private var timeCount:int; + private var timer:int; + + private function createDiagram():Sprite { + var diagram:Sprite = new Sprite(); + diagram.mouseEnabled = false; + diagram.mouseChildren = false; + // Инициализация диаграммы + diagram.addEventListener(Event.ADDED_TO_STAGE, function():void { + // FPS + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.LEFT; + fpsTextField.text = "FPS: " + Number(diagram.stage.frameRate).toFixed(2); + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + diagram.addChild(fpsTextField); + // Память + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.LEFT; + memoryTextField.text = "MEM: " + bytesToString(System.totalMemory); + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 4; + diagram.addChild(memoryTextField); + // Треугольники + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.LEFT; + trianglesTextField.text = "TRI: " + 0; + trianglesTextField.selectable = false; + trianglesTextField.x = -2; + trianglesTextField.y = 13; + diagram.addChild(trianglesTextField); + // Время выполнения метода + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.LEFT; + timerTextField.text = "TMR:"; + timerTextField.selectable = false; + timerTextField.x = -2; + timerTextField.y = 13 + 9; + diagram.addChild(timerTextField); + // График + graph = new Bitmap(new BitmapData(60, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40) + graph.x = 0; + graph.y = 27 + 9; + diagram.addChild(graph); + // Сброс параметров + previousFrameTime = previousPeriodTime = getTimer(); + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + // Подписка + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Деинициализация диаграммы + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void { + // Обнуление + diagram.removeChild(fpsTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(graph); + fpsTextField = null; + memoryTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Отписка + diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram); + diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram); + }); + return diagram; + } + + private function resizeDiagram(e:Event = null):void { + if (diagram.stage != null) { + var coord:Point = diagram.parent.globalToLocal(new Point()); + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) { + diagram.x = Math.round(coord.x + _diagramHorizontalMargin); + } + if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth/2 - graph.width/2); + } + if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width); + } + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) { + diagram.y = Math.round(coord.y + _diagramVerticalMargin); + } + if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight/2 - (graph.y + graph.height)/2); + } + if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height); + } + } + } + + private function updateDiagram(e:Event):void { + var fps:Number; + var mod:int; + var time:int = getTimer(); + var stageFrameRate:int = diagram.stage.frameRate; + + // FPS текст + if (++fpsUpdateCounter == fpsUpdatePeriod) { + fps = 1000*fpsUpdatePeriod/(time - previousPeriodTime); + if (fps > stageFrameRate) fps = stageFrameRate; + mod = fps*100 % 100; + fpsTextField.text = "FPS: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS график + fps = 1000/(time - previousFrameTime); + if (fps > stageFrameRate) fps = stageFrameRate; + graph.bitmapData.scroll(1, 0); + graph.bitmapData.fillRect(rect, 0x20FFFFFF); + graph.bitmapData.setPixel32(0, 40*(1 - fps/stageFrameRate), 0xFFCCCCCC); + previousFrameTime = time; + + // Память текст + var memory:int = System.totalMemory; + memoryTextField.text = "MEM: " + bytesToString(memory); + // Память график + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00); + + // Треугольники текст + trianglesTextField.text = "TRI: " + numTriangles; + + // Время текст + if (++timerUpdateCounter == timerUpdatePeriod) { + if (timeCount > 0) { + fps = timeSum/timeCount; + mod = fps*100 % 100; + timerTextField.text = "TMR: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = "TMR:"; + } + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + } + } + + public function startTimer():void { + timer = getTimer(); + } + + public function stopTimer():void { + timeSum += getTimer() - timer; + timeCount++; + } + + public function get diagramAlign():String { + return _diagramAlign; + } + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + public function get diagramVerticalMargin():Number { + return _diagramVerticalMargin; + } + public function set diagramVerticalMargin(value:Number):void { + _diagramVerticalMargin = value; + resizeDiagram(); + } + + private function bytesToString(bytes:int):String { + if (bytes < 1024) return bytes + "b"; + else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb"; + else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb"; + else if (bytes < 1048576) return (bytes >> 10) + "kb"; + else if (bytes < 10485760) return (bytes/1048576).toFixed(2) + "mb"; + else if (bytes < 104857600) return (bytes/1048576).toFixed(1) + "mb"; + else return (bytes >> 20) + "mb"; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Canvas.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Canvas.as new file mode 100644 index 0000000..ff75280 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Canvas.as @@ -0,0 +1,101 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Canvas extends Sprite { + + static private const defaultColorTransform:ColorTransform = new ColorTransform(); + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + alternativa3d var gfx:Graphics = graphics; + + private var modifiedGraphics:Boolean; + private var modifiedAlpha:Boolean; + private var modifiedBlendMode:Boolean; + private var modifiedColorTransform:Boolean; + private var modifiedFilters:Boolean; + + alternativa3d var _numChildren:int = 0; + alternativa3d var numDraws:int = 0; + + public function Canvas() { + mouseEnabled = false; + mouseChildren = false; + } + + alternativa3d function getChildCanvas(useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas; + var displayObject:DisplayObject; + // Зачистка не канвасов + while (_numChildren > numDraws && !((displayObject = getChildAt(_numChildren - 1 - numDraws)) is Canvas)) { + removeChild(displayObject); + _numChildren--; + } + // Получение канваса + if (_numChildren > numDraws++) { + canvas = displayObject as Canvas; + // Зачистка + canvas.gfx.clear(); + if (canvas._numChildren > 0 && !useChildren) { + canvas.removeChildren(0); + } + } else { + canvas = (collectorLength > 0) ? collector[--collectorLength] : new Canvas(); + addChildAt(canvas, 0); + _numChildren++; + } + // Пометка о том, что в graphics будет что-то нарисовано + canvas.modifiedGraphics = useGraphics; + // Установка свойств + if (alpha != 1) { + canvas.alpha = alpha; + canvas.modifiedAlpha = true; + } else if (canvas.modifiedAlpha) { + canvas.alpha = 1; + canvas.modifiedAlpha = false; + } + if (blendMode != "normal") { + canvas.blendMode = blendMode; + canvas.modifiedBlendMode = true; + } else if (canvas.modifiedBlendMode) { + canvas.blendMode = "normal"; + canvas.modifiedBlendMode = false; + } + if (colorTransform != null) { + colorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = colorTransform; + canvas.modifiedColorTransform = true; + } else if (canvas.modifiedColorTransform) { + defaultColorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = defaultColorTransform; + canvas.modifiedColorTransform = false; + } + if (filters != null) { + canvas.filters = filters; + canvas.modifiedFilters = true; + } else if (canvas.modifiedFilters) { + canvas.filters = null; + canvas.modifiedFilters = false; + } + return canvas; + } + + alternativa3d function removeChildren(keep:int):void { + for (var canvas:Canvas; _numChildren > keep; _numChildren--) { + if ((canvas = removeChildAt(0) as Canvas) != null) { + if (canvas.modifiedGraphics) canvas.gfx.clear(); + if (canvas._numChildren > 0) canvas.removeChildren(0); + collector[collectorLength++] = canvas; + } + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Clipping.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Clipping.as new file mode 100644 index 0000000..90a0db1 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Clipping.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Clipping { + + /** + * Объект отсекается целиком, если он полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const BOUND_CULLING:int = 0; + /** + * Грань отсекается целиком, если она полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const FACE_CULLING:int = 1; + /** + * Грань подрезается пирамидой видимости камеры. + */ + static public const FACE_CLIPPING:int = 2; + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Debug.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..20e427c --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Debug.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class Debug { + + static public const NAMES:int = 1; + static public const AXES:int = 2; + static public const CENTERS:int = 4; + static public const BOUNDS:int = 8; + + static public const EDGES:int = 16; + static public const VERTICES:int = 32; + static public const NORMALS:int = 64; + + static public const NODES:int = 128; + static public const SPLITS:int = 256; + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Fragment.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Fragment.as new file mode 100644 index 0000000..e5ab215 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Fragment.as @@ -0,0 +1,47 @@ +package alternativa.engine3d.core { + + public class Fragment { + + static public var collector:Fragment; + + public var next:Fragment; + + public var negative:Fragment; + public var positive:Fragment; + + public var geometry:Geometry; + + public var indices:Vector. = new Vector.(); + public var num:int = 0; + + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + static public function create():Fragment { + if (collector != null) { + var res:Fragment = collector; + collector = collector.next; + res.next = null; + res.num = 0; + return res; + } else { + return new Fragment(); + } + } + + public function create():Fragment { + if (collector != null) { + var res:Fragment = collector; + collector = collector.next; + res.next = null; + res.num = 0; + return res; + } else { + return new Fragment(); + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Geometry.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Geometry.as new file mode 100644 index 0000000..fabd821 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Geometry.as @@ -0,0 +1,1616 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class Geometry { + + static private var collector:Geometry; + + // Вспомогательные + static private const sortingFragments:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private const verticesMap:Vector. = new Vector.(); + static private var negativeReserve:Fragment = Fragment.create(); + static private var positiveReserve:Fragment = Fragment.create(); + + public var next:Geometry; + + public var vertices:Vector. = new Vector.(); + public var uvts:Vector. = new Vector.(); + public var verticesLength:int = 0; + public var numVertices:int = 0; + + public var projectedVertices:Vector. = new Vector.(); + private var drawIndices:Vector. = new Vector.(); + private var drawIndicesLength:int = 0; + + public var fragment:Fragment; + + public var numOccluders:int; + + // Передаваемые при сплите свойства + + public var cameraMatrix:Matrix3D = new Matrix3D(); + public var alpha:Number; + public var blendMode:String; + public var colorTransform:ColorTransform; + public var filters:Array; + + public var sorting:int; + + public var texture:BitmapData; + public var smooth:Boolean; + public var repeatTexture:Boolean; + + public var debugResult:int = 0; + + public var viewAligned:Boolean = false; + public var textureMatrix:Matrix = new Matrix(); + public var projectionX:Number; + public var projectionY:Number; + + // AABB + public var minX:Number; + public var minY:Number; + public var minZ:Number; + public var maxX:Number; + public var maxY:Number; + public var maxZ:Number; + + // OOBB + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const localVertices:Vector. = new Vector.(); + public var points:Vector. = new Vector.(24, true); + public var planes:Vector. = new Vector.(24, true); + + static public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function destroy():void { + if (fragment != null) { + destroyFragments(fragment); + fragment = null; + } + numVertices = 0; + verticesLength = 0; + viewAligned = false; + colorTransform = null; + filters = null; + numOccluders = 0; + debugResult = 0; + next = collector; + collector = this; + } + + private function destroyFragments(fragment:Fragment):void { + if (fragment.negative != null) { + destroyFragments(fragment.negative); + fragment.negative = null; + } + if (fragment.positive != null) { + destroyFragments(fragment.positive); + fragment.positive = null; + } + var last:Fragment = fragment; + while (last.next != null) { + last = last.next; + } + last.next = Fragment.collector; + Fragment.collector = fragment; + } + + public function calculateAABB():void { + var verts:Vector. = vertices; + minX = Number.MAX_VALUE; + minY = Number.MAX_VALUE; + minZ = Number.MAX_VALUE; + maxX = -Number.MAX_VALUE; + maxY = -Number.MAX_VALUE; + maxZ = -Number.MAX_VALUE; + for (var i:int = 0, c:Number; i < verticesLength;) { + c = verts[i]; i++; + if (c < minX) minX = c; + if (c > maxX) maxX = c; + c = verts[i]; i++; + if (c < minY) minY = c; + if (c > maxY) maxY = c; + c = verts[i]; i++; + if (c < minZ) minZ = c; + if (c > maxZ) maxZ = c; + } + } + + public function calculateOOBB():void { + var verts:Vector. = localVertices; + vertices.length = verticesLength; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(cameraMatrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(vertices, verts); + minX = Number.MAX_VALUE; + minY = Number.MAX_VALUE; + minZ = Number.MAX_VALUE; + maxX = -Number.MAX_VALUE; + maxY = -Number.MAX_VALUE; + maxZ = -Number.MAX_VALUE; + for (var i:int = 0, c:Number; i < verticesLength;) { + c = verts[i]; i++; + if (c < minX) minX = c; + if (c > maxX) maxX = c; + c = verts[i]; i++; + if (c < minY) minY = c; + if (c > maxY) maxY = c; + c = verts[i]; i++; + if (c < minZ) minZ = c; + if (c > maxZ) maxZ = c; + } + // Костыль + if (maxX - minX < 1) { + maxX = minX + 1; + } + if (maxY - minY < 1) { + maxY = minY + 1; + } + if (maxZ - minZ < 1) { + maxZ = minZ + 1; + } + // A + points[0] = minX; + points[1] = minY; + points[2] = minZ; + // B + points[3] = maxX; + points[4] = minY; + points[5] = minZ; + // C + points[6] = minX; + points[7] = maxY; + points[8] = minZ; + // D + points[9] = maxX; + points[10] = maxY; + points[11] = minZ; + // E + points[12] = minX; + points[13] = minY; + points[14] = maxZ; + // F + points[15] = maxX; + points[16] = minY; + points[17] = maxZ; + // G + points[18] = minX; + points[19] = maxY; + points[20] = maxZ; + // H + points[21] = maxX; + points[22] = maxY; + points[23] = maxZ; + // Перевод вершин баунда из локальных координат в камеру + cameraMatrix.transformVectors(points, points); + // Front + var ax:Number = points[0]; + var ay:Number = points[1]; + var az:Number = points[2]; + var abx:Number = points[3] - ax; + var aby:Number = points[4] - ay; + var abz:Number = points[5] - az; + var acx:Number = points[12] - ax; + var acy:Number = points[13] - ay; + var acz:Number = points[14] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[0] = nx; + planes[1] = ny; + planes[2] = nz; + planes[3] = ax*nx + ay*ny + az*nz; + // Back + ax = points[6]; + ay = points[7]; + az = points[8]; + abx = points[18] - ax; + aby = points[19] - ay; + abz = points[20] - az; + acx = points[9] - ax; + acy = points[10] - ay; + acz = points[11] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[4] = nx; + planes[5] = ny; + planes[6] = nz; + planes[7] = ax*nx + ay*ny + az*nz; + // Left + ax = points[0]; + ay = points[1]; + az = points[2]; + abx = points[12] - ax; + aby = points[13] - ay; + abz = points[14] - az; + acx = points[6] - ax; + acy = points[7] - ay; + acz = points[8] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[8] = nx; + planes[9] = ny; + planes[10] = nz; + planes[11] = ax*nx + ay*ny + az*nz; + // Right + ax = points[3]; + ay = points[4]; + az = points[5]; + abx = points[9] - ax; + aby = points[10] - ay; + abz = points[11] - az; + acx = points[15] - ax; + acy = points[16] - ay; + acz = points[17] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[12] = nx; + planes[13] = ny; + planes[14] = nz; + planes[15] = ax*nx + ay*ny + az*nz; + // Top + ax = points[12]; + ay = points[13]; + az = points[14]; + abx = points[15] - ax; + aby = points[16] - ay; + abz = points[17] - az; + acx = points[18] - ax; + acy = points[19] - ay; + acz = points[20] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[16] = nx; + planes[17] = ny; + planes[18] = nz; + planes[19] = ax*nx + ay*ny + az*nz; + // Bottom + ax = points[0]; + ay = points[1]; + az = points[2]; + abx = points[6] - ax; + aby = points[7] - ay; + abz = points[8] - az; + acx = points[3] - ax; + acy = points[4] - ay; + acz = points[5] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[20] = nx; + planes[21] = ny; + planes[22] = nz; + planes[23] = ax*nx + ay*ny + az*nz; + } + + public function split(axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, negative:Geometry, positive:Geometry):void { + var i:int; + var c:Number; + var verts:Vector.; + var vertsLen:int; + // Сброс карты + for (i = 0; i < numVertices; i++) verticesMap[i] = -1; + // Разбиение + splitFragments(fragment, negative, positive, axisX, axisY, coord, coord - threshold, coord + threshold); + // Копирование свойств + if (negative != null && fragment.negative != null) { + negative.fragment = fragment.negative; + fragment.negative = null; + negative.texture = texture; + negative.smooth = smooth; + negative.repeatTexture = repeatTexture; + negative.cameraMatrix.identity(); + negative.cameraMatrix.prepend(cameraMatrix); + negative.alpha = alpha; + negative.blendMode = blendMode; + negative.colorTransform = colorTransform; + negative.filters = filters; + negative.sorting = sorting; + negative.debugResult = debugResult; + negative.viewAligned = viewAligned; + if (viewAligned) { + negative.textureMatrix.a = textureMatrix.a; + negative.textureMatrix.b = textureMatrix.b; + negative.textureMatrix.c = textureMatrix.c; + negative.textureMatrix.d = textureMatrix.d; + negative.textureMatrix.tx = textureMatrix.tx; + negative.textureMatrix.ty = textureMatrix.ty; + negative.projectionX = projectionX; + negative.projectionY = projectionY; + } + // Обновление баунда + verts = negative.vertices; + vertsLen = negative.verticesLength; + if (axisX) { + negative.minX = minX; + negative.maxX = coord; + negative.minY = maxY; + negative.maxY = minY; + negative.minZ = maxZ; + negative.maxZ = minZ; + for (i = 0; i < vertsLen;) { + i++; + c = verts[i]; i++; + if (c < negative.minY) negative.minY = c; + if (c > negative.maxY) negative.maxY = c; + c = verts[i]; i++; + if (c < negative.minZ) negative.minZ = c; + if (c > negative.maxZ) negative.maxZ = c; + } + } else if (axisY) { + negative.minX = maxX; + negative.maxX = minX; + negative.minY = minY; + negative.maxY = coord; + negative.minZ = maxZ; + negative.maxZ = minZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < negative.minX) negative.minX = c; + if (c > negative.maxX) negative.maxX = c; + i++; + c = verts[i]; i++; + if (c < negative.minZ) negative.minZ = c; + if (c > negative.maxZ) negative.maxZ = c; + } + } else { + negative.minX = maxX; + negative.minY = maxY; + negative.minZ = minZ; + negative.maxX = minX; + negative.maxY = minY; + negative.maxZ = coord; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < negative.minX) negative.minX = c; + if (c > negative.maxX) negative.maxX = c; + c = verts[i]; i++; + if (c < negative.minY) negative.minY = c; + if (c > negative.maxY) negative.maxY = c; + i++; + } + } + } + if (positive != null && fragment.positive != null) { + positive.fragment = fragment.positive; + fragment.positive = null; + positive.texture = texture; + positive.smooth = smooth; + positive.repeatTexture = repeatTexture; + positive.cameraMatrix.identity(); + positive.cameraMatrix.prepend(cameraMatrix); + positive.alpha = alpha; + positive.blendMode = blendMode; + positive.colorTransform = colorTransform; + positive.filters = filters; + positive.sorting = sorting; + positive.debugResult = debugResult; + positive.viewAligned = viewAligned; + if (viewAligned) { + positive.textureMatrix.a = textureMatrix.a; + positive.textureMatrix.b = textureMatrix.b; + positive.textureMatrix.c = textureMatrix.c; + positive.textureMatrix.d = textureMatrix.d; + positive.textureMatrix.tx = textureMatrix.tx; + positive.textureMatrix.ty = textureMatrix.ty; + positive.projectionX = projectionX; + positive.projectionY = projectionY; + } + // Обновление баунда + verts = positive.vertices; + vertsLen = positive.verticesLength; + if (axisX) { + positive.minX = coord; + positive.maxX = maxX; + positive.minY = maxY; + positive.maxY = minY; + positive.minZ = maxZ; + positive.maxZ = minZ; + for (i = 0; i < vertsLen;) { + i++; + c = verts[i]; i++; + if (c < positive.minY) positive.minY = c; + if (c > positive.maxY) positive.maxY = c; + c = verts[i]; i++; + if (c < positive.minZ) positive.minZ = c; + if (c > positive.maxZ) positive.maxZ = c; + } + } else if (axisY) { + positive.minX = maxX; + positive.maxX = minX; + positive.minY = coord; + positive.maxY = maxY; + positive.minZ = maxZ; + positive.maxZ = minZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < positive.minX) positive.minX = c; + if (c > positive.maxX) positive.maxX = c; + i++; + c = verts[i]; i++; + if (c < positive.minZ) positive.minZ = c; + if (c > positive.maxZ) positive.maxZ = c; + } + } else { + positive.minX = maxX; + positive.maxX = minX; + positive.minY = maxY; + positive.maxY = minY; + positive.minZ = coord; + positive.maxZ = maxZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < positive.minX) positive.minX = c; + if (c > positive.maxX) positive.maxX = c; + c = verts[i]; i++; + if (c < positive.minY) positive.minY = c; + if (c > positive.maxY) positive.maxY = c; + i++; + } + } + } + fragment.next = Fragment.collector; + Fragment.collector = fragment; + fragment = null; + } + + private function splitFragments(fragment:Fragment, negativeGeometry:Geometry, positiveGeometry:Geometry, axisX:Boolean, axisY:Boolean, coord:Number, minCoord:Number, maxCoord:Number):void { + var result:Fragment = fragment; + var innegative:Boolean = negativeGeometry != null; + var inpositive:Boolean = positiveGeometry != null; + var split:Boolean = innegative && inpositive; + var crop:Boolean = !split; + var negativeNegative:Fragment; + var negativePositive:Fragment; + var positiveNegative:Fragment; + var positivePositive:Fragment; + // Проход по дочерним нодам + if (result.negative != null) { + splitFragments(result.negative, negativeGeometry, positiveGeometry, axisX, axisY, coord, minCoord, maxCoord); + negativeNegative = result.negative.negative; + negativePositive = result.negative.positive; + result.negative.negative = null; + result.negative.positive = null; + result.negative.next = Fragment.collector; + Fragment.collector = result.negative; + result.negative = null; + } + if (result.positive != null) { + splitFragments(result.positive, negativeGeometry, positiveGeometry, axisX, axisY, coord, minCoord, maxCoord); + positiveNegative = result.positive.negative; + positivePositive = result.positive.positive; + result.positive.negative = null; + result.positive.positive = null; + result.positive.next = Fragment.collector; + Fragment.collector = result.positive; + result.positive = null; + } + // Разделение + var negativeFirst:Fragment; + var negativeLast:Fragment; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var negativeVertices:Vector.; + var negativeUVTs:Vector.; + var positiveVertices:Vector.; + var positiveUVTs:Vector.; + var negV:int; + var negVi:int; + var posV:int; + var posVi:int; + if (innegative) { + negativeVertices = negativeGeometry.vertices; + negativeUVTs = negativeGeometry.uvts; + negV = negativeGeometry.numVertices; + negVi = negativeGeometry.verticesLength; + } + if (inpositive) { + positiveVertices = positiveGeometry.vertices; + positiveUVTs = positiveGeometry.uvts; + posV = positiveGeometry.numVertices; + posVi = positiveGeometry.verticesLength; + } + while (fragment != null) { + var next:Fragment = fragment.next; + if (fragment.num > 0) { + var indices:Vector. = fragment.indices; + var num:int = fragment.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + var a:int = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ac:Number = axisX ? ax : (axisY ? ay : az); + for (var i:int = 0; i < num; i++) { + // Вторая точка ребра + var b:int = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bc:Number = axisX ? bx : (axisY ? by : bz); + // Рассечение ребра + if (split && (bc > maxCoord && ac < minCoord || bc < minCoord && ac > maxCoord) || crop && (innegative && (ac < coord && bc >= coord || bc < coord && ac >= coord) || inpositive && (ac <= coord && bc > coord || bc <= coord && ac > coord))) { + var t:Number = (ac - coord)/(ac - bc); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + var x:Number = ax + (bx - ax)*t; + var y:Number = ay + (by - ay)*t; + var z:Number = az + (bz - az)*t; + var u:Number = au + (bu - au)*t; + var v:Number = av + (bv - av)*t; + if (innegative) { + negativeVertices[negVi] = x; + negativeUVTs[negVi] = u; negVi++; + negativeVertices[negVi] = y; + negativeUVTs[negVi] = v; negVi++; + negativeVertices[negVi] = z; + negativeUVTs[negVi] = 0; negVi++; + negativeIndices[negativeNum] = negV; negativeNum++; negV++; + } + if (inpositive) { + positiveVertices[posVi] = x; + positiveUVTs[posVi] = u; posVi++; + positiveVertices[posVi] = y; + positiveUVTs[posVi] = v; posVi++; + positiveVertices[posVi] = z; + positiveUVTs[posVi] = 0; posVi++; + positiveIndices[positiveNum] = posV; positiveNum++; posV++; + } + } + // Добавление точки + if (split && bc < minCoord || crop && innegative && bc < coord) { + if (verticesMap[b] < 0) { + negativeVertices[negVi] = bx; + negativeUVTs[negVi] = uvts[bi]; negVi++; + negativeVertices[negVi] = by; n = bi + 1; + negativeUVTs[negVi] = uvts[n]; negVi++; + negativeVertices[negVi] = bz; + negativeUVTs[negVi] = 0; negVi++; + negativeIndices[negativeNum] = negV; negativeNum++; + verticesMap[b] = negV; negV++; + } else { + negativeIndices[negativeNum] = verticesMap[b]; negativeNum++; + } + behind = true; + } else if (split && bc > maxCoord || crop && inpositive && bc > coord) { + if (verticesMap[b] < 0) { + positiveVertices[posVi] = bx; + positiveUVTs[posVi] = uvts[bi]; posVi++; + positiveVertices[posVi] = by; n = bi + 1; + positiveUVTs[posVi] = uvts[n]; posVi++; + positiveVertices[posVi] = bz; + positiveUVTs[posVi] = 0; posVi++; + positiveIndices[positiveNum] = posV; positiveNum++; + verticesMap[b] = posV; posV++; + } else { + positiveIndices[positiveNum] = verticesMap[b]; positiveNum++; + } + infront = true; + } else if (split) { + negativeVertices[negVi] = bx; + positiveVertices[posVi] = bx; + negativeUVTs[negVi] = uvts[bi]; negVi++; + positiveUVTs[posVi] = uvts[bi]; posVi++; + negativeVertices[negVi] = by; + positiveVertices[posVi] = by; n = bi + 1; + negativeUVTs[negVi] = uvts[n]; negVi++; + positiveUVTs[posVi] = uvts[n]; posVi++; + negativeVertices[negVi] = bz; + positiveVertices[posVi] = bz; + negativeUVTs[negVi] = 0; negVi++; + positiveUVTs[posVi] = 0; posVi++; + negativeIndices[negativeNum] = negV; negativeNum++; + positiveIndices[positiveNum] = posV; positiveNum++; + verticesMap[b] = negV; negV++; + verticesMap[b] = posV; posV++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ac = bc; + } + // Анализ разбиения + if (behind) { + negativeGeometry.numVertices = negV; + negativeGeometry.verticesLength = negVi; + negative.num = negativeNum; + if (sorting == 2) { + negative.normalX = fragment.normalX; + negative.normalY = fragment.normalY; + negative.normalZ = fragment.normalZ; + negative.offset = fragment.offset; + } + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + negativeReserve = fragment.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + } else if (negativeNum > 0) { + negV = negativeGeometry.numVertices; + negVi = negativeGeometry.verticesLength; + } + if (infront || split && !behind && !infront) { + positiveGeometry.numVertices = posV; + positiveGeometry.verticesLength = posVi; + positive.num = positiveNum; + if (sorting == 2) { + positive.normalX = fragment.normalX; + positive.normalY = fragment.normalY; + positive.normalZ = fragment.normalZ; + positive.offset = fragment.offset; + } + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + positiveReserve = fragment.create(); + positive = positiveReserve; + positiveIndices = positive.indices; + } else if (positiveNum > 0) { + posV = positiveGeometry.numVertices; + posVi = positiveGeometry.verticesLength; + } + } + if (fragment != result) { + fragment.next = Fragment.collector; + Fragment.collector = fragment; + } + fragment = next; + } + // Если сзади от сплита есть фрагменты или обе дочерние ноды + if (negativeFirst != null || negativeNegative != null && positiveNegative != null) { + if (negativeFirst == null) { + negativeFirst = result.create(); + } + if (sorting == 3) { + negativeFirst.normalX = result.normalX; + negativeFirst.normalY = result.normalY; + negativeFirst.normalZ = result.normalZ; + negativeFirst.offset = result.offset; + } + result.negative = negativeFirst; + negativeFirst.negative = negativeNegative; + negativeFirst.positive = positiveNegative; + } else { + result.negative = (negativeNegative != null) ? negativeNegative : positiveNegative; + } + // Если сзади от сплита есть фрагменты или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + if (positiveFirst == null) { + positiveFirst = result.create(); + } + if (sorting == 3) { + positiveFirst.normalX = result.normalX; + positiveFirst.normalY = result.normalY; + positiveFirst.normalZ = result.normalZ; + positiveFirst.offset = result.offset; + } + result.positive = positiveFirst; + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + } else { + result.positive = (negativePositive != null) ? negativePositive : positivePositive; + } + } + + public function draw(camera:Camera3D, parentCanvas:Canvas, threshold:Number, matrix:Matrix3D = null):void { + var i:int; + var j:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + var current:Fragment; + var last:Fragment; + // Перевод в камеру + if (matrix != null) { + vertices.length = verticesLength; + matrix.transformVectors(vertices, vertices); + } + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + if (viewAligned) { + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + var x:Number = vertices[0]*projectionX; + var y:Number = vertices[1]*projectionY; + canvas.gfx.moveTo(x, y); + for (i = 3; i < verticesLength; i++) { + x = vertices[i]*projectionX; i++; + y = vertices[i]*projectionY; i++; + canvas.gfx.lineTo(x, y); + } + fragment.next = Fragment.collector; + Fragment.collector = fragment; + } else { + // Сброс + drawIndicesLength = 0; + // Без сортировки + if (sorting == 0) { + current = fragment; + do { + indices = current.indices; + num = current.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + last = current; + current = current.next; + } while (current != null); + last.next = Fragment.collector; + Fragment.collector = fragment; + // Сортировка по средним Z + } else if (sorting == 1) { + var fragments:Vector. = sortingFragments; + var fragmentsLength:int = 0; + // Заполнение вектора + current = fragment; + do { + indices = current.indices; + num = current.num; + var sum:Number = 0; + for (i = 0; i < num; i++) { + var vi:int = indices[i]*3 + 2; + sum += vertices[vi]; + } + current.offset = sum/num; + fragments[fragmentsLength] = current; + fragmentsLength++; + last = current; + current = current.next; + } while (current != null); + // Сортировка + var stack:Vector. = sortingStack; + stack[0] = 0; + stack[1] = fragmentsLength - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + var k:int = r + l; + var t:int = k >> 1; + current = fragments[t]; + var median:Number = current.offset; + while (i <= j) { + var left:Fragment = fragments[i]; + while (left.offset > median) { + i++; + left = fragments[i]; + } + var right:Fragment = fragments[j]; + while (right.offset < median) { + j--; + right = fragments[j]; + } + if (i <= j) { + fragments[i] = right; + fragments[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Сбор + for (i = 0; i < fragmentsLength; i++) { + current = fragments[i]; + indices = current.indices; + num = current.num; + a = indices[0]; + b = indices[1]; + for (j = 2; j < num; j++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[j]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + fragments[i] = null; + } + last.next = Fragment.collector; + Fragment.collector = fragment; + // Динамическое BSP + } else if (sorting == 2) { + current = fragment.next; + fragment.next = null; + drawDynamicNode(fragment, current, threshold); + // Статическое BSP + } else { + drawStaticNode(fragment); + } + // Проецирование + vertices.length = verticesLength; + uvts.length = verticesLength; + projectedVertices.length = numVertices << 1; + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "none"); + } + fragment = null; + } + + public function drawPart(camera:Camera3D, parentCanvas:Canvas, fragment:Fragment):void { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + var i:int; + var a:int; + var b:int; + var c:int; + var current:Fragment; + var last:Fragment; + var indices:Vector.; + var num:int; + if (viewAligned) { + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + current = fragment; + do { + indices = current.indices; + num = current.num; + a = indices[0]*3; + var x:Number = vertices[a]*projectionX; a++; + var y:Number = vertices[a]*projectionY; + canvas.gfx.moveTo(x, y); + for (i = 1; i < num; i++) { + a = indices[i]*3; + x = vertices[a]*projectionX; a++; + y = vertices[a]*projectionY; + canvas.gfx.lineTo(x, y); + } + current.geometry = null; + last = current; + current = current.next; + } while (current != null); + } else { + // Сброс + drawIndicesLength = 0; + // Перебор + current = fragment; + do { + indices = current.indices; + num = current.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + current.geometry = null; + last = current; + current = current.next; + } while (current != null); + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "none"); + } + last.next = Fragment.collector; + Fragment.collector = fragment; + } + + private function drawStaticNode(splitter:Fragment):void { + var negative:Fragment = splitter.negative; + var positive:Fragment = splitter.positive; + splitter.negative = null; + splitter.positive = null; + if (splitter.offset < 0) { + if (negative != null) { + drawStaticNode(negative); + } + if (splitter.num > 0) { + var current:Fragment = splitter; + var last:Fragment; + do { + var indices:Vector. = current.indices; + var num:int = current.num; + var a:int = indices[0]; + var b:int = indices[1]; + for (var i:int = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + var c:int = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + last = current; + current = current.next; + } while (current != null); + last.next = Fragment.collector; + Fragment.collector = splitter; + } else { + splitter.next = Fragment.collector; + Fragment.collector = splitter; + } + if (positive != null) { + drawStaticNode(positive); + } + } else { + if (positive != null) { + drawStaticNode(positive); + } + splitter.next = Fragment.collector; + Fragment.collector = splitter; + if (negative != null) { + drawStaticNode(negative); + } + } + } + + private function drawDynamicNode(splitter:Fragment, source:Fragment, threshold:Number):void { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var splitterLast:Fragment = splitter; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + var i:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + // Перебор входной последовательности + while (source != null) { + next = source.next; + indices = source.indices; + num = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + a = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (i = 0; i < num; i++) { + // Вторая точка ребра + b = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[verticesLength] = ax + (bx - ax)*t; + uvts[verticesLength] = au + (bu - au)*t; verticesLength++; + vertices[verticesLength] = ay + (by - ay)*t; + uvts[verticesLength] = av + (bv - av)*t; verticesLength++; + vertices[verticesLength] = az + (bz - az)*t; + uvts[verticesLength] = 0; verticesLength++; + negativeIndices[negativeNum] = numVertices; + negativeNum++; + positiveIndices[positiveNum] = numVertices; + positiveNum++; + numVertices++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + negative.num = negativeNum; + positive.num = positiveNum; + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.next = Fragment.collector; + Fragment.collector = source; + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = null; + splitterLast.next = source; + splitterLast = source; + } + source = next; + } + // Сбор задней части + if (negativeFirst != negativeLast) { + next = negativeFirst.next; + negativeFirst.next = null; + drawDynamicNode(negativeFirst, next, threshold); + } else if (negativeFirst != null) { + negativeFirst.next = splitter; + splitter = negativeFirst; + } + // Если в передней части только один фрагмент + if (positiveFirst != null && positiveFirst == positiveLast) { + splitterLast.next = positiveFirst; + splitterLast = positiveFirst; + positiveFirst = null; + } + // Отрисовка + next = splitter; + do { + indices = next.indices; + num = next.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + next = next.next; + } while (next != null); + splitterLast.next = Fragment.collector; + Fragment.collector = splitter; + // Сбор передней части + if (positiveFirst != null) { + next = positiveFirst.next; + positiveFirst.next = null; + drawDynamicNode(positiveFirst, next, threshold); + } + } + + public function debug(camera:Camera3D, container:Object3D, parentCanvas:Canvas, threshold:Number, bb:int, matrix:Matrix3D = null):void { + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + if (debugResult & Debug.EDGES) { + if (matrix != null) { + matrix = matrix.clone(); + } else { + matrix = new Matrix3D(); + } + var i:int; + var k:int; + var vi:int; + var num:int; + var x:Number; + var y:Number; + var current:Fragment; + // Перевод в камеру + vertices.length = verticesLength; + matrix.transformVectors(vertices, vertices); + if (viewAligned && (debugResult & Debug.BOUNDS) && bb == 2) { + canvas.gfx.lineStyle(0, 0xFF9900); + } else { + canvas.gfx.lineStyle(0, 0xFFFFFF); + } + // Сброс + drawIndicesLength = 0; + // Динамическое BSP + if (sorting == 2 && !viewAligned) { + // Клонирование списка + var first:Fragment; + var last:Fragment; + current = fragment; + while (current != null) { + if (first != null) { + last.next = last.create(); + last = last.next; + } else { + first = Fragment.create(); + last = first; + } + for (i = 0; i < current.num; i++) { + last.indices[i] = current.indices[i]; + } + last.num = current.num; + last.normalX = current.normalX; + last.normalY = current.normalY; + last.normalZ = current.normalZ; + last.offset = current.offset; + current = current.next; + } + var numVerts:int = numVertices; + // Сбор + current = first.next; + first.next = null; + debugDynamicNode(first, current, threshold, canvas); + // Проецирование + vertices.length = verticesLength; + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Отрисовка + for (i = 0, k = 0; i < drawIndicesLength; i++) { + if (i == k) { + num = drawIndices[i]; + i++; + k += num; + vi = drawIndices[k] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + k++; + } + vi = drawIndices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + numVertices = numVerts; + verticesLength = numVerts*3; + } else { + // Проецирование + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Без сортировки + if (sorting == 0 || sorting == 1 || viewAligned) { + current = fragment; + while (current != null) { + k = current.num - 1; + vi = current.indices[k] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (i = 0; i < current.num; i++) { + vi = current.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + current = current.next; + } + // Статическое BSP + } else { + debugStaticNode(fragment, canvas); + } + } + matrix.invert(); + matrix.transformVectors(vertices, vertices); + } + if (debugResult & Debug.BOUNDS) { + if (bb > 0) { + var containerBoundBox:BoundBox = container._boundBox; + container._boundBox = new BoundBox(); + if (bb == 1) { + container._boundBox.minX = minX; + container._boundBox.minY = minY; + container._boundBox.minZ = minZ; + container._boundBox.maxX = maxX; + container._boundBox.maxY = maxY; + container._boundBox.maxZ = maxZ; + container.drawBoundBox(camera, canvas, 0x99FF00); + } else if (bb == 2 && !viewAligned) { + var containerCameraMatrix:Matrix3D = container.cameraMatrix; + container.cameraMatrix = cameraMatrix; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(cameraMatrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(points, points); + container._boundBox.infinity(); + for (i = 0; i < 24;) { + var c:Number = points[i]; i++; + if (c < container._boundBox.minX) container._boundBox.minX = c; + if (c > container._boundBox.maxX) container._boundBox.maxX = c; + c = points[i]; i++; + if (c < container._boundBox.minY) container._boundBox.minY = c; + if (c > container._boundBox.maxY) container._boundBox.maxY = c; + c = points[i]; i++; + if (c < container._boundBox.minZ) container._boundBox.minZ = c; + if (c > container._boundBox.maxZ) container._boundBox.maxZ = c; + } + container.drawBoundBox(camera, canvas, 0xFF9900); + container.cameraMatrix = containerCameraMatrix; + } + container._boundBox = containerBoundBox; + } + } + } + + public function debugPart(camera:Camera3D, canvas:Canvas, fragment:Fragment):void { + if (debugResult & Debug.EDGES) { + while (fragment != null) { + var k:int = fragment.num - 1; + var vi:int = fragment.indices[k] << 1; + var x:Number = projectedVertices[vi]; vi++; + var y:Number = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (var i:int = 0; i < fragment.num; i++) { + vi = fragment.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + fragment = fragment.next; + } + } + } + + private function debugStaticNode(splitter:Fragment, canvas:Canvas):void { + if (splitter.negative != null) { + debugStaticNode(splitter.negative, canvas); + } + if (splitter.positive != null) { + debugStaticNode(splitter.positive, canvas); + } + if (splitter.num > 0) { + var current:Fragment = splitter; + while (current != null) { + var k:int = current.num - 1; + var vi:int = current.indices[k] << 1; + var x:Number = projectedVertices[vi]; vi++; + var y:Number = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (var i:int = 0; i < current.num; i++) { + vi = current.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + current = current.next; + } + } + } + + private function debugDynamicNode(splitter:Fragment, source:Fragment, threshold:Number, canvas:Canvas):void { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var splitterLast:Fragment = splitter; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + var i:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + // Перебор входной последовательности + while (source != null) { + next = source.next; + indices = source.indices; + num = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + a = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (i = 0; i < num; i++) { + // Вторая точка ребра + b = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[verticesLength] = ax + (bx - ax)*t; + uvts[verticesLength] = au + (bu - au)*t; verticesLength++; + vertices[verticesLength] = ay + (by - ay)*t; + uvts[verticesLength] = av + (bv - av)*t; verticesLength++; + vertices[verticesLength] = az + (bz - az)*t; + uvts[verticesLength] = 0; verticesLength++; + negativeIndices[negativeNum] = numVertices; + negativeNum++; + positiveIndices[positiveNum] = numVertices; + positiveNum++; + numVertices++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + negative.num = negativeNum; + positive.num = positiveNum; + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.next = Fragment.collector; + Fragment.collector = source; + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = null; + splitterLast.next = source; + splitterLast = source; + } + source = next; + } + // Сбор задней части + if (negativeFirst != negativeLast) { + next = negativeFirst.next; + negativeFirst.next = null; + debugDynamicNode(negativeFirst, next, threshold, canvas); + } else if (negativeFirst != null) { + negativeFirst.next = splitter; + splitter = negativeFirst; + } + // Если в передней части только один фрагмент + if (positiveFirst != null && positiveFirst == positiveLast) { + splitterLast.next = positiveFirst; + splitterLast = positiveFirst; + positiveFirst = null; + } + // Отрисовка + next = splitter; + do { + indices = next.indices; + num = next.num; + drawIndices[drawIndicesLength] = num; + drawIndicesLength++; + for (i = 0; i < num; i++) { + drawIndices[drawIndicesLength] = indices[i]; + drawIndicesLength++; + } + next = next.next; + } while (next != null); + splitterLast.next = Fragment.collector; + Fragment.collector = splitter; + // Сбор передней части + if (positiveFirst != null) { + next = positiveFirst.next; + positiveFirst.next = null; + debugDynamicNode(positiveFirst, next, threshold, canvas); + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/MipMap.as b/Alternativa3D7/7.0/alternativa/engine3d/core/MipMap.as new file mode 100644 index 0000000..612055c --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/MipMap.as @@ -0,0 +1,70 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Объект, представляющий текстуру в виде последовательности её уменьшенных копий. + * Каждая следующая в два раза меньше предыдущей. Последняя имеет размер 1х1 пиксел. + * Чем дальше от камеры отрисовываемый объект, тем меньшая текстура выбирается. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class MipMap { + + /** + * Мип-текстуры + */ + public var textures:Vector. = new Vector.(); + /** + * Количество мип-текстур + */ + public var num:int; + /** + * Отношение размера пиксела текстуры к единице измерения трёхмерного пространства + */ + public var resolution:Number; + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + static private const point:Point = new Point(); + static private const matrix:Matrix = new Matrix(); + static private const rect:Rectangle = new Rectangle(); + public function MipMap(texture:BitmapData, resolution:Number = 1) { + var bmp:BitmapData = new BitmapData(texture.width, texture.height, texture.transparent); + var current:BitmapData = textures[num++] = texture; + filter.preserveAlpha = !texture.transparent; + var w:Number = rect.width = texture.width, h:Number = rect.height = texture.height; + while (w > 1 && h > 1 && rect.width > 1 && rect.height > 1) { + bmp.applyFilter(current, rect, point, filter); + rect.width = w >> 1; + rect.height = h >> 1; + matrix.a = rect.width/w; + matrix.d = rect.height/h; + w *= 0.5; + h *= 0.5; + current = new BitmapData(rect.width, rect.height, texture.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + textures[num++] = current; + } + bmp.dispose(); + this.resolution = resolution; + } + + /** + * Получение мип-уровня в зависимости от удалённости объекта от камеры + * @param distance Z-координата объекта в пространстве камеры + * @param camera Камера + * @return Индекс в списке мип-текстур textures + */ + public function getLevel(distance:Number, camera:Camera3D):int { + var level:int = Math.log(distance/(camera.focalLength*resolution))*1.442695040888963387; + if (level < 0) return 0; else if (level >= num) return num - 1; + return level; + } + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/MipMapping.as b/Alternativa3D7/7.0/alternativa/engine3d/core/MipMapping.as new file mode 100644 index 0000000..f322221 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/MipMapping.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class MipMapping { + + /** + * Нет мипмаппинга. + */ + static public const NONE:int = 0; + /** + * Мипмаппинг по удалённости объекта от камеры. + */ + static public const PER_OBJECT:int = 1; + + //static public const PER_POLYGON:int = 2; + //static public const PER_PIXEL:int = 3; + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Object3D.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..732a63f --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,276 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Базовый трёхмерный объект + */ + public class Object3D { + + public var name:String; + /** + * Матрица трансформации. Управлять трансформацией объекта можно только через это свойство + * путём назначения новой матрицы или с помощью методов матрицы. + */ + public var matrix:Matrix3D = new Matrix3D(); + public var visible:Boolean = true; + + public var alpha:Number = 1; + public var blendMode:String = "normal"; + public var colorTransform:ColorTransform = null; + public var filters:Array = null; + + alternativa3d var _parent:Object3DContainer; + + alternativa3d var _boundBox:BoundBox; + + alternativa3d var culling:int = 0; + + alternativa3d var cameraMatrix:Matrix3D = new Matrix3D(); + + static private const boundBoxVertices:Vector. = new Vector.(24, true); + + public function removeFromParent():void { + if (_parent != null) + _parent.removeChild(this); + } + + alternativa3d function get canDraw():Boolean { + return true; + } + + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void {} + + alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return null; + } + + alternativa3d function split(normalX:Number, normalY:Number, normalZ:Number, offset:Number, threshold:Number):Vector. { + return new Vector.(2); + } + + public function get boundBox():BoundBox { + return _boundBox; + } + + public function set boundBox(value:BoundBox):void { + _boundBox = value; + } + + /** + * Расчёт баунда + * @param matrix Трансформация пространства, в системе которого расчитывается баунд. + * Если этот параметр не указан, баунд расчитается в локальных координатах объекта. + * @param boundBox Баунд, в который записывается результат. + * Если этот параметр не указан, создаётся новый экземпляр. + * @return Расчитанный баунд. + */ + public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return null; + } + + public function get parent():Object3DContainer { + return _parent; + } + + /** + * @private + */ + alternativa3d function cullingInCamera(camera:Camera3D, parentCulling:int):int { + if (camera.occludedAll) return -1; + culling = parentCulling; + var i:int, infront:Boolean, behind:Boolean, boundBox:BoundBox = this.boundBox, numOccluders:int = camera.numOccluders, cull:Boolean = culling > 0 && boundBox != null, occlude:Boolean = numOccluders > 0 && boundBox != null; + // Расчёт точек баунда в координатах камеры + if (cull || occlude) boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX, boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY, boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ, boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX, boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY, boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ, cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + // Куллинг + if (cull) { + if (culling & 1) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] > camera.nearClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + // TODO: проверка не нужна + if (infront) culling &= 62; + } + } + if (culling & 2) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] < camera.farClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 61; + } + } + if (culling & 4) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 59; + } + } + if (culling & 8) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 55; + } + } + if (culling & 16) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 47; + } + } + if (culling & 32) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 31; + } + } + } + // Окклюдинг + if (occlude) { + for (var o:int = 0; o < numOccluders; o++) { + var planeOccluder:Vector. = camera.occlusionPlanes[o], planeOccluderLength:int = planeOccluder.length; + for (var ni:int = 0; ni < planeOccluderLength; ni += 3) { + var nx:Number = planeOccluder[ni], ny:Number = planeOccluder[int(ni + 1)], nz:Number = planeOccluder[int(ni + 2)]; + for (i = 0; i < 24; i += 3) if (nx*boundBoxVertices[i] + ny*boundBoxVertices[int(i + 1)] + nz*boundBoxVertices[int(i + 2)] >= 0) break; + if (i < 24) break; + } + if (ni == planeOccluderLength) return -1; + } + } + return culling; + } + + alternativa3d function drawAxes(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawCenter(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawName(camera:Camera3D, canvas:Canvas):void { + + } + + static private const boundBoxProjectedVertices:Vector. = new Vector.(16, true); + static private const boundBoxUVTs:Vector. = new Vector.(24, true); + alternativa3d function drawBoundBox(camera:Camera3D, canvas:Canvas, color:int = -1):void { + + var boundBox:BoundBox = this.boundBox; + if (boundBox == null) return; + + boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX; + boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY; + boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ; + + boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX; + boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY; + boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ; + + cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + for (var i:int = 0; i < 8; i++) { + if (boundBoxVertices[int(i*3 +2)] <= 0) return; + } + Utils3D.projectVectors(camera.projectionMatrix, boundBoxVertices, boundBoxProjectedVertices, boundBoxUVTs); + canvas.gfx.endFill(); + canvas.gfx.lineStyle(0, (color < 0) ? ((culling > 0) ? 0xFFFF00 : 0x00FF00) : color); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.moveTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.moveTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.moveTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + } + + public function toString():String { + var className:String = getQualifiedClassName(this); + return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]"; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Object3DContainer.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Object3DContainer.as new file mode 100644 index 0000000..8d6af33 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Object3DContainer.as @@ -0,0 +1,200 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Базовый контейнер трёхмерных объектов. + * Логика контейнеров и child-parent-отношений идентична логике + * displayObject'ов во Flash. + */ + public class Object3DContainer extends Object3D { + + protected var children:Vector. = new Vector.(); + protected var _numChildren:int = 0; + + protected var visibleChildren:Vector. = new Vector.(); + protected var numVisibleChildren:int = 0; + + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сбор видимых объектов + numVisibleChildren = 0; + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + if (child.cullingInCamera(camera, object.culling) >= 0) { + visibleChildren[numVisibleChildren] = child; + numVisibleChildren++; + } + } + } + // Если есть видимые объекты + if (numVisibleChildren > 0) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, canvas); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + for (var i:int = numVisibleChildren - 1; i >= 0; i--) { + var child:Object3D = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var i:int; + var first:Geometry; + var last:Geometry; + var geometry:Geometry; + for (i = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + if (child.cullingInCamera(camera, object.culling) >= 0) { + geometry = child.getGeometry(camera, child); + if (geometry != null) { + if (first != null) { + last.next = geometry; + } else { + first = geometry; + last = geometry; + } + while (last.next != null) { + last = last.next; + } + } + } + } + } + if (object.alpha != 1) { + geometry = first; + while (geometry != null) { + geometry.alpha *= object.alpha; + geometry = geometry.next; + } + } + if (object.blendMode != "normal") { + geometry = first; + while (geometry != null) { + if (geometry.blendMode == "normal") { + geometry.blendMode = object.blendMode; + } + geometry = geometry.next; + } + } + if (object.colorTransform != null) { + geometry = first; + while (geometry != null) { + if (geometry.colorTransform != null) { + var ct:ColorTransform = new ColorTransform(object.colorTransform.redMultiplier, object.colorTransform.greenMultiplier, object.colorTransform.blueMultiplier, object.colorTransform.alphaMultiplier, object.colorTransform.redOffset, object.colorTransform.greenOffset, object.colorTransform.blueOffset, object.colorTransform.alphaOffset); + ct.concat(geometry.colorTransform); + geometry.colorTransform = ct; + } else { + geometry.colorTransform = object.colorTransform; + } + geometry = geometry.next; + } + } + if (object.filters != null) { + geometry = first; + while (geometry != null) { + if (geometry.filters != null) { + var fs:Array = new Array(); + var fsLength:int = 0; + var num:int = geometry.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = geometry.filters[i]; fsLength++; + } + num = object.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = object.filters[i]; fsLength++; + } + geometry.filters = fs; + } else { + geometry.filters = object.filters; + } + geometry = geometry.next; + } + } + return first; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var m:Matrix3D = matrix != null ? matrix.clone() : new Matrix3D(); + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчитываем баунды объектов + var childBoundBox:BoundBox = new BoundBox(); + var childMatrix:Matrix3D = new Matrix3D(); + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + childMatrix.identity(); + childMatrix.prepend(m); + childMatrix.prepend(child.matrix); + child.calculateBoundBox(childMatrix, childBoundBox); + boundBox.minX = (childBoundBox.minX < boundBox.minX) ? childBoundBox.minX : boundBox.minX; + boundBox.minY = (childBoundBox.minY < boundBox.minY) ? childBoundBox.minY : boundBox.minY; + boundBox.minZ = (childBoundBox.minZ < boundBox.minZ) ? childBoundBox.minZ : boundBox.minZ; + boundBox.maxX = (childBoundBox.maxX > boundBox.maxX) ? childBoundBox.maxX : boundBox.maxX; + boundBox.maxY = (childBoundBox.maxY > boundBox.maxY) ? childBoundBox.maxY : boundBox.maxY; + boundBox.maxZ = (childBoundBox.maxZ > boundBox.maxZ) ? childBoundBox.maxZ : boundBox.maxZ; + } + return boundBox; + } + + public function addChild(child:Object3D):void { + children[_numChildren++] = child; + child._parent = this; + } + + public function removeChild(child:Object3D):void { + var i:int = children.indexOf(child); + if (i < 0) throw new ArgumentError("Child not found"); + _numChildren--; + for (; i < _numChildren; i++) children[i] = children[int(i + 1)]; + children.length = _numChildren; + child._parent = null; + } + + public function hasChild(child:Object3D):Boolean { + return children.indexOf(child) > -1; + } + + public function getChildAt(index:uint):Object3D { + return children[index]; + } + + public function get numChildren():uint { + return _numChildren; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/core/Sorting.as b/Alternativa3D7/7.0/alternativa/engine3d/core/Sorting.as new file mode 100644 index 0000000..7535811 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/core/Sorting.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + public class Sorting { + + /** + * Грани не сортируются. + */ + static public const NONE:int = 0; + /** + * Грани сортируются по средним Z. + */ + static public const AVERAGE_Z:int = 1; + /** + * Грани при отрисовке образуют BSP-дерево. + */ + static public const DYNAMIC_BSP:int = 2; + /** + * Грани находятся в BSP-дереве. + */ + static public const STATIC_BSP:int = 3; + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/BatchTextureLoader.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/BatchTextureLoader.as new file mode 100644 index 0000000..8bf6df0 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/BatchTextureLoader.as @@ -0,0 +1,273 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка пакета. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка пакета успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent.LOADER_ERROR + */ + [Event (name="loaderError", type="alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной текстуры. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной текстуры успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * @private + * Пакетный загрузчик текстур. + * + * При возникновении ошибки во время загрузки очередной текстуры, пакетный загрузчик заменяет соответствующую текстуру изображением-заглушкой и + * генерирует событие ошибки пакетного загрузчика. Пользователь пакетного загрузчика в обработчике ошибки может решить, прерывать ли процесс + * загрузки вызовом метода close() или нет. + */ + public class BatchTextureLoader extends EventDispatcher { + /** + * Текстура-заглушка для замены незагруженных текстур. + */ + private static var stubBitmapData:BitmapData; + + private static const IDLE:int = 0; + private static const LOADING:int = 1; + + // Состояние загрузчика + private var state:int = IDLE; + + // Загрузчик текстур + private var textureLoader:TextureLoader; + // Контекст безопасности загрузчика + private var loaderContext:LoaderContext; + // Базовый URL файлов текстур + private var baseURL:String; + // Пакет с описанием текстур материалов (textureName => TextureInfo) + private var batch:Object; + // Список имён текстур в пакете + private var textureNames:Vector.; + // Индекс текущего материала. + private var textureIndex:int; + // Общее количество загружаемых текстур + private var numTextures:int; + // Результирующая таблица (textureName => BitmapData) + private var _textures:Object; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function BatchTextureLoader() { + } + + /** + * Результирующая таблица битмапов. Ключами являются имена текстур, значениями -- объекты класса BitmapData. + */ + public function get textures():Object { + return _textures; + } + + /** + * Прекращает текущую загрузку. + */ + public function close():void { + if (state == LOADING) { + textureLoader.close(); + cleanup(); + _textures = null; + state = IDLE; + } + } + + /** + * Очищает ссылку на загруженный список текстур материалов. + */ + public function unload():void { + _textures = null; + } + + /** + * Запускает загрузку. + * + * @param baseURL базовый URL файлов текстур + * @param batch описание пакета текстур -- таблица textureName => TextureInfo + * @param loaderContext LoaderContext для загрузки + */ + public function load(baseURL:String, batch:Object, loaderContext:LoaderContext = null):void { + if (baseURL == null) { + throw ArgumentError("Parameter baseURL cannot be null"); + } + if (batch == null) { + throw ArgumentError("Parameter batch cannot be null"); + } + + this.baseURL = baseURL; + this.batch = batch; + this.loaderContext = loaderContext; + + if (textureLoader == null) { + textureLoader = new TextureLoader(); + } else { + close(); + } + textureLoader.addEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.addEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.addEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + + // Получение массива имён текстур + textureNames = new Vector.(); + for (var textureName:String in batch) { + textureNames.push(textureName); + } + numTextures = textureNames.length; + // Старт загрузки + textureIndex = 0; + _textures = {}; + + if (hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + + state = LOADING; + loadNextTexture(); + } + + /** + * Запускает загрузку очередной текстуры. + */ + private function loadNextTexture():void { + var info:TextureInfo = batch[textureNames[textureIndex]]; + var opacityMapFileUrl:String = info.opacityMapFileName == null || info.opacityMapFileName == "" ? null : baseURL + info.opacityMapFileName; + textureLoader.load(baseURL + info.diffuseMapFileName, opacityMapFileUrl, loaderContext); + } + + /** + * + */ + private function onTextureLoadingStart(e:Event):void { + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, numTextures, textureIndex)); + } + } + + /** + * + */ + private function onProgress(e:LoaderProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var totalProgress:Number = (textureIndex + e.totalProgress)/numTextures; + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, numTextures, textureIndex, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * Обрабатывает завершение загрузки текстуры. + */ + private function onTextureLoadingComplete(e:Event):void { + _textures[textureNames[textureIndex]] = textureLoader.bitmapData; + tryNextTexure(); + } + + /** + * Обрабатывает ошибку при загрузке текстуры. Незагруженная текстура заменяется изображением-заглушкой и + * генерируется событие ошибки пакетного загрузчика. + */ + private function onLoadingError(e:ErrorEvent):void { + var textureName:String = textureNames[textureIndex]; + _textures[textureName] = getStubBitmapData(); + dispatchEvent(new BatchTextureLoaderErrorEvent(BatchTextureLoaderErrorEvent.LOADER_ERROR, textureName, e.text)); + tryNextTexure(); + } + + /** + * + */ + private function tryNextTexure():void { + // Проверка состояния необходима, т.к. оно могло измениться в результате вызова метода close() в обработчике события ошибки загрузки + if (state == IDLE) return; + + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, numTextures, textureIndex)); + } + if (++textureIndex == numTextures) { + // Загружены все текстуры, отправляется сообщение о завершении + cleanup(); + removeEventListeners(); + state = IDLE; + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } else { + loadNextTexture(); + } + } + + /** + * + */ + private function removeEventListeners():void { + textureLoader.removeEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.removeEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.removeEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.removeEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + } + + /** + * Очищает внутренние ссылки на объекты. + */ + private function cleanup():void { + loaderContext = null; + textureNames = null; + } + + /** + * Метод для получения текстуры-заглушки. + * + * @return текстура-заглушка для замещения незагруженных текстур + */ + private function getStubBitmapData():BitmapData { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stubBitmapData.setPixel((i%2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stubBitmapData; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..095feb9 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,924 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.objects.Mesh; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + [Event (name="complete", type="flash.events.Event")] + /** + * + */ + public class Loader3DS extends EventDispatcher { + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_MODEL:int = 0; + private static const STATE_LOADING_TEXTURES:int = 1; + + private static var stubBitmapData:BitmapData; + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + private var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + private var loaderState:int = STATE_IDLE; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_MODEL) { + modelLoader.close(); + } + if (loaderState == STATE_LOADING_TEXTURES) { + textureLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + } + } + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + version = 0; + clean(); + + if (modelLoader == null) { + modelLoader = new URLLoader(); + modelLoader.dataFormat = URLLoaderDataFormat.BINARY; + modelLoader.addEventListener(Event.COMPLETE, on3DSLoad); + modelLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + modelLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + loaderState = STATE_LOADING_MODEL; + modelLoader.load(new URLRequest(url)); + } + + private function on3DSLoad(e:Event):void { + loaderState = STATE_IDLE; + data = modelLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + private function on3DSError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + private function loadBitmaps():void { + if (textureLoader == null) { + textureLoader = new TextureLoader(); + textureLoader.addEventListener(Event.COMPLETE, loadNextBitmap); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + } + + // Имена материалов с диффузными текстурами собираются в массив textureMaterialNames + bitmaps = new Array(); + textureMaterialNames = new Array(); + for each (var materialData:MaterialData in materialDatas) { + if (materialData.diffuseMap != null) { + textureMaterialNames.push(materialData.name); + } + } + + loaderState = STATE_LOADING_TEXTURES; + loadNextBitmap(); + } + + private function loadNextBitmap(e:Event = null):void { + if (e != null) { + if (!(e is IOErrorEvent)) { + bitmaps[textureMaterialNames[counter]] = textureLoader.bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[textureMaterialNames[counter]] = stubBitmapData; + } + } else { + counter = -1; + } + counter++; + if (counter < textureMaterialNames.length) { + var materialData:MaterialData = materialDatas[textureMaterialNames[counter]]; + textureLoader.load(path + materialData.diffuseMap.filename, materialData.opacityMap == null ? null : path + materialData.opacityMap.filename, context); + } else { + loaderState = STATE_IDLE; + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + object.name = animationData.objectName; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + } + } + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.objects.Mesh; + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Point; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DSByteArray.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DSByteArray.as new file mode 100644 index 0000000..38557d2 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Loader3DSByteArray.as @@ -0,0 +1,812 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + public class Loader3DSByteArray extends EventDispatcher { + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + alternativa3d var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function parseByteArray(data:ByteArray):void { + this.data = data; + this.data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, this.data.bytesAvailable); + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + /*if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + }*/ + } + + alternativa3d function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + //loadBitmaps(); + buildContent(); // Без подгрузки битмап + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(data.readFloat(), -data.readFloat(), data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.objects.Mesh; + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Point; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/MaterialParams.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/MaterialParams.as new file mode 100644 index 0000000..df75e1a --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/MaterialParams.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.loaders { + + public class MaterialParams { + + public var color:uint; + public var opacity:Number; + public var diffuseMap:String; + public var opacityMap:String; + + public function toString():String { + return "[MaterialParams color=" + color + ", opacity=" + opacity + ", diffuseMap=" + diffuseMap + ", opacityMap=" + opacityMap + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parsed3DSData.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parsed3DSData.as new file mode 100644 index 0000000..6db88ef --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parsed3DSData.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.core.Object3D; + + public class Parsed3DSData { + + /** + * Список объектов в порядке их появления в 3DS-данных. + **/ + public var objects:Vector.; + /** + * Список материалов каждого объекта. Если для объекта нет назначенных материалов, соответствующий элемент списка равен null. + **/ + public var objectMaterials:Vector.>; + /** + * Список материалов 3DS-файла (materialName => MaterialParams). + */ + public var materials:Object; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..cfae190 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,970 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.utils.ByteArray; + import flash.utils.Endian; + + /** + * + */ + public class Parser3DS extends EventDispatcher { + + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Vector.; + private var materialDatas:Object; + + private var modelLoader:URLLoader; + private var data:ByteArray; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + + /** + * + * @param data + */ + public function Parser3DS() { + } + + /** + * + * @param data + */ + public function parse(data:ByteArray):Parsed3DSData { + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + return buildContent(); + } + + /** + * + */ + private function clean():void { + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + /** + * + */ + private function buildContent():Parsed3DSData { + var result:Parsed3DSData = new Parsed3DSData(); + var i:uint; + var length:uint; + + // Формируем связи объектов + result.objects = new Vector.(); + result.objectMaterials = new Vector.>(); + result.materials = {}; + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + + var mat:MaterialParams = new MaterialParams(); + mat.color = materialData.color; + mat.opacity = 1 - 0.01*materialData.transparency; + if (materialData.diffuseMap != null) { + mat.diffuseMap = materialData.diffuseMap.filename; + } + if (materialData.opacityMap != null) { + mat.opacityMap = materialData.opacityMap.filename; + } + result.materials[materialName] = mat; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData, result); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null, result); + } + } + } + + clean(); + return result; + } + + /** + * + * @param animationData + */ + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x*units, animationData.position.y*units, animationData.position.z*units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); +// trace("transform[2]", transform[2]); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + /** + * + * @param mesh + * @param objectData + * @param animationData + */ + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, parsedData:Parsed3DSData):void { + mesh.name = objectData.name; + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + parsedData.objects.push(mesh); + + // Добавляем поверхности + if (objectData.surfaces != null) { + var meshMaterials:Vector. = new Vector.(); + for (var surfaceId:String in objectData.surfaces) { + meshMaterials.push(surfaceId); +// var materialData:MaterialData = materialDatas[surfaceId]; +// if (materialData.diffuseMap != null) { +// mesh.texture = bitmaps[materialData.name]; +// } + } + parsedData.objectMaterials.push(meshMaterials); + } else { + parsedData.objectMaterials.push(null); + } +// trace("mesh.matrix.decompose()", mesh.matrix.decompose()); + } + + /** + * + * @param index + * @param length + */ + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + * @param length + */ + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + */ + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + /** + * + * @param index + * @param length + */ + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + * @param length + */ + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + */ + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = {}; + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + /** + * + * @param materialName + * @param map + * @param index + * @param length + */ + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + /** + * + * @param index + * @param length + */ + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Vector.(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + * @param length + */ + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Считывает строку, заканчивающуюся на нулевой байт. + * + * @param index + * @return + */ + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + /** + * + * @param angle + * @param x + * @param z + * @param y + * @return + */ + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = 0.5*angle; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -0.5*Math.PI; + res.x = 0; + return res; + } + if (k <= -1) { + half = 0.5*angle; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = 0.5*Math.PI; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(k); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.objects.Mesh; + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Point; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureFilesData.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureFilesData.as new file mode 100644 index 0000000..265859b --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureFilesData.as @@ -0,0 +1,13 @@ +package alternativa.engine3d.loaders { + public class TextureFilesData { + + public var diffuseMap:String; + public var opacityMap:String; + + public function TextureFilesData(diffuseMap:String, opacityMap:String) { + this.diffuseMap = diffuseMap; + this.opacityMap = opacityMap; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureInfo.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureInfo.as new file mode 100644 index 0000000..babb394 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureInfo.as @@ -0,0 +1,36 @@ +package alternativa.engine3d.loaders { + + /** + * Структура для хранения имён файла диффузной текстуры и файла карты прозрачности. + */ + public class TextureInfo { + /** + * Имя файла диффузной текстуры. + */ + public var diffuseMapFileName:String; + /** + * Имя файла карты прозрачности. + */ + public var opacityMapFileName:String; + + /** + * Создаёт новый экземпляр. + * + * @param diffuseMapFileName имя файла диффузной текстуры + * @param opacityMapFileName имя файла карты прозрачности + */ + public function TextureInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) { + this.diffuseMapFileName = diffuseMapFileName; + this.opacityMapFileName = opacityMapFileName; + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[TextureInfo diffuseMapFileName=" + diffuseMapFileName + ", opacityMapFileName=" + opacityMapFileName + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureLoader.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureLoader.as new file mode 100644 index 0000000..3cc7171 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/TextureLoader.as @@ -0,0 +1,268 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка ресурса. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка ресурса успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной части ресурса. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной части ресурса успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * Загрузчик текстуры, состоящей из одного или двух файлов. В случае, если указан второй файл, он используется для заполнения альфа-канала + * получаемой текстуры. + */ + public class TextureLoader extends EventDispatcher { + + private static const IDLE:int = -1; + private static const LOADING_DIFFUSE_MAP:int = 0; + private static const LOADING_ALPHA_MAP:int = 1; + + private var state:int = IDLE; + private var bitmapLoader:Loader; + private var loaderContext:LoaderContext; + private var alphaTextureUrl:String; + private var _bitmapData:BitmapData; + + /** + * Создаёт новый экземпляр. Если указан URL диффузной части текстуры, то сразу начинается загрузка. + * + * @param diffuseTextureUrl URL диффузной части текстуры + * @param alphaTextureUrl URL карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function TextureLoader() { + } + + /** + * Загруженная текстура. + */ + public function get bitmapData():BitmapData { + return _bitmapData; + } + + /** + * Загрузка текстурных карт. При успешной загрузке посылается сообщение Event.COMPLETE. + * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void { + unload(); + this.alphaTextureUrl = alphaTextureUrl == "" ? null : alphaTextureUrl; + this.loaderContext = loaderContext; + + loadPart(LOADING_DIFFUSE_MAP, diffuseTextureUrl); + } + + /** + * Прекращвет текущую загрузку. Если нет активных загрузок, не происходит ничего. + */ + public function close():void { + if (state == IDLE) return; + state = IDLE; + bitmapLoader.unload(); + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Очищает внутренние ссылки на загруженные объекты, чтобы сборщик мусора смог их удалить. + */ + public function unload():void { + close(); + _bitmapData = null; + } + + /** + * Очищает временные внутренние ссылки. + */ + private function cleanup():void { + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Запускает загрузку части текстуры. + * + * @param state фаза загрузки + * @param url URL загружаемого файла + */ + private function loadPart(state:int, url:String):void { + this.state = state; + createLoader(); + bitmapLoader.load(new URLRequest(url), loaderContext); + } + + /** + * Обрабатывает начало загрузки очередной части текстуры. + */ + private function onPartLoadingOpen(e:Event):void { + if (_bitmapData == null && hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, 2, state == LOADING_DIFFUSE_MAP ? 0 : 1)); + } + } + + /** + * + */ + private function onPartLoadingProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var partNumber:int = state == LOADING_DIFFUSE_MAP ? 0 : 1; + var totalProgress:Number = 0.5*(partNumber + e.bytesLoaded/e.bytesTotal); + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, 2, partNumber, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * + */ + private function onPartLoadingComplete(e:Event):void { + switch (state) { + case LOADING_DIFFUSE_MAP: { + // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности. + _bitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + dispatchPartComplete(0); + if (alphaTextureUrl != null) { + loadPart(LOADING_ALPHA_MAP, alphaTextureUrl); + } else { + complete(); + } + break; + } + case LOADING_ALPHA_MAP: { + // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры. + var pt:Point = new Point(); + var tmpBmd:BitmapData = _bitmapData; + _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height); + _bitmapData.copyPixels(tmpBmd, tmpBmd.rect, pt); + + var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) { + tmpBmd.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = tmpBmd; + } else { + tmpBmd.dispose(); + } + _bitmapData.copyChannel(alpha, alpha.rect, pt, BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + dispatchPartComplete(1); + complete(); + break; + } + } + } + + /** + * Создаёт событие завершения загрузки части текстуры. + * + * @param partnNumber номер загруженной части текстуры + */ + private function dispatchPartComplete(partNumber:int):void { + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, 2, partNumber)); + } + } + + /** + * + */ + private function onLoadError(e:Event):void { + state = IDLE; + cleanup(); + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + state = IDLE; + cleanup(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * + */ + private function createLoader():void { + bitmapLoader = new Loader(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.addEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.addEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.addEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } + + /** + * + */ + private function destroyLoader():void { + if (bitmapLoader == null) return; + bitmapLoader.unload(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.removeEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.removeEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.removeEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); + bitmapLoader = null; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as new file mode 100644 index 0000000..0d8da26 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.loaders.events { + import flash.events.ErrorEvent; + import flash.events.Event; + + /** + * Класс представляет событие ошибки, генерируемое пакетным загрузчиком текстур. + */ + public class BatchTextureLoaderErrorEvent extends ErrorEvent { + + /** + * + */ + public static const LOADER_ERROR:String = "loaderError"; + + // Имя текстуры, с которой произошла проблема + private var _textureName:String; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param textureName имя текстуры, с которой произошла проблема + * @param text описание ошибки + */ + public function BatchTextureLoaderErrorEvent(type:String, textureName:String, text:String) { + super(type); + this.text = text; + _textureName = textureName; + } + + /** + * Имя текстуры, с которой произошла проблема. + */ + public function get textureName():String { + return _textureName; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new BatchTextureLoaderErrorEvent(type, _textureName, text); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[BatchTextureLoaderErrorEvent textureName=" + _textureName + ", text=" + text + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..57a739d --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,68 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Событие загрузчиков ресурсов, состоящих из нескольких частей. + */ + public class LoaderEvent extends Event { + /** + * Событие начала загрузки очередной части ресурса. + */ + public static const PART_OPEN:String = "partOpen"; + /** + * Событие окончания загрузки очередной части ресурса. + */ + public static const PART_COMPLETE:String = "partComplete"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер части, к которой относится событие. Нумерация начинается с нуля + */ + public function LoaderEvent(type:String, partsTotal:int, currentPart:int) { + super(type); + _partsTotal = partsTotal; + _currentPart= currentPart; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderEvent(type, _partsTotal, _currentPart); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..273740e --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Событие прогресса загрузки ресурсов, состоящих из нескольких частей. + */ + public class LoaderProgressEvent extends ProgressEvent { + + /** + * Событие прогресса загрузки очередной части ресурса. + */ + public static const LOADER_PROGRESS:String = "loaderProgress"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + // Общий прогресс загрузки, выраженный числом в интервале [0, 1] + private var _totalProgress:Number = 0; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер загружаемой в настоящий момент части. Нумерация начинается с нуля + * @param totalProgress общий прогресс загрузки, выраженный числом в интервале [0, 1] + * @param bytesLoaded количество загруженных байт текущей части + * @param bytesTotal объём текущей части + */ + public function LoaderProgressEvent(type:String, partsTotal:int, currentPart:int, totalProgress:Number = 0, bytesLoaded:uint=0, bytesTotal:uint=0) { + super(type, false, false, bytesLoaded, bytesTotal); + _partsTotal = partsTotal; + _currentPart= currentPart; + _totalProgress = totalProgress; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Общий прогресс загрузки, выраженный числом в интервале [0, 1]. + */ + public function get totalProgress():Number { + return _totalProgress; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _partsTotal, _currentPart, _totalProgress, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", totalProgress=" + _totalProgress.toFixed(2) + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/AnimSprite.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..e1d3bec --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,69 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Анимированный спрайт. + * Анимация осуществляется путём переключения изображений, + * хранящихся в списке textures + */ + public class AnimSprite extends Sprite3D { + + /** + * Список кадров изображений + */ + public var textures:Vector.; + public var mipMaps:Vector.; + /** + * Устанавливаемый кадр + */ + public var frame:uint = 0; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + super.draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + super.debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + return super.getGeometry(camera, object); + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + return super.calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Axes.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Axes.as new file mode 100644 index 0000000..ae1e36f --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Axes.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.display.Graphics; + import flash.geom.Utils3D; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, иллюстрирующий систему коордщинат + */ + public class Axes extends Object3D { + + public var axisLength:Number; + public var lineThickness:Number; + public var dotRadius:Number; + public var textSize:Number; + + public function Axes(axisLength:Number = 30, lineThickness:Number = 0, dotRadius:Number = 2, textSize:Number = 10):void { + this.axisLength = axisLength; + this.lineThickness = lineThickness; + this.dotRadius = dotRadius; + this.textSize = textSize; + _boundBox = new BoundBox(); + _boundBox.setSize(-dotRadius, -dotRadius, -dotRadius, axisLength, axisLength, axisLength); + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + var p:Vector. = Vector.([0, 0, 0, axisLength, 0, 0, 0, axisLength, 0, 0, 0, axisLength]); + var d:Vector. = new Vector.(8); + object.cameraMatrix.transformVectors(p, p); + + // Центр за камерой + if (p[2] < camera.nearClipping) return; + + Utils3D.projectVectors(camera.projectionMatrix, p, d, new Vector.()); + var size:Number = camera.viewSize/p[2]; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + var gfx:Graphics = canvas.gfx; + var text:TextField; + + // Ось X + if (p[5] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0xFF0000); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[2], d[3]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[2]; + text.y = d[3]; + text.text = "X"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0xFF0000)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Y + if (p[8] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x00FF00); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[4], d[5]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[4]; + text.y = d[5]; + text.text = "Y"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x00FF00)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Z + if (p[11] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x0000FF); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[6], d[7]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[6]; + text.y = d[7]; + text.text = "Z"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x0000FF)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Начало координат + gfx.lineStyle(); + gfx.beginFill(0xFFFFFF); + gfx.drawCircle(d[0], d[1], dotRadius*size); + + //debugDrawBoundRaduis(camera, object, canvas); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Bone.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Bone.as new file mode 100644 index 0000000..a7a2262 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Bone.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class Bone extends Mesh { + + public var localTransform:Vector.; + /** + * @private + */ + alternativa3d var localMatrix:Matrix3D; + + /** + * @private + */ + alternativa3d var length:Number; + /** + * @private + */ + alternativa3d var distance:Number; + /** + * @private + */ + alternativa3d var _numChildren:uint = 0; + /** + * @private + */ + alternativa3d var children:Vector. = new Vector.(); + + public function Bone(length:Number, distance:Number) { + this.length = length; + this.distance = distance; + } + + public function addChild(child:Bone):void { + children[_numChildren++] = child; + child.localTransform = child.matrix.decompose(); + child.localMatrix = new Matrix3D(); + } + + public function calculateMatrix():void { + for (var i:int = 0; i < _numChildren; i++) { + var child:Bone = children[i]; + child.matrix.identity(); + child.matrix.prepend(matrix); + child.localMatrix.recompose(child.localTransform); + child.matrix.prepend(child.localMatrix); + child.calculateMatrix(); + } + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/LOD.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..ec06539 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,74 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект, имеющий набор объектов с разной детализацией. + * При отрисовке, он выбирает в зависимости от расстояния от камеры + * объект с нужной детализацией и отрисовывает его вместо себя. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class LOD extends Object3D { + + /** + * Объекты с разной детализацией + */ + public var lodObjects:Vector.; + /** + * Расстояния до камеры соответствующие объектам с разной детализацией + */ + public var lodDistances:Vector.; + + /** + * @private + */ + private function getLODObject(object:Object3D):Object3D { + var cameraDistance:Number = object.cameraMatrix.position.length; + // Поиск ближайшего лода + var min:Number = Infinity; + var length:uint = lodObjects.length; + var lod:Object3D; + for (var i:int = 0; i < length; i++) { + var d:Number = Math.abs(cameraDistance - lodDistances[i]); + if (d < min) { + min = d; + lod = lodObjects[i]; + } + } + return lod; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return getLODObject(object).getGeometry(camera, object); + } + + override public function get boundBox():BoundBox { + return (lodObjects[0] as Object3D).boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return (lodObjects[0] as Object3D).calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Mesh.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..e1ca82f --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,3194 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Полигональный объект + */ + public class Mesh extends Object3D { + + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; + + /** + * Вершины в виде x, y, z + */ + public var vertices:Vector.; + /** + * UV-координаты в виде u, v, t + */ + public var uvts:Vector.; + /** + * Количество вершин + */ + public var numVertices:int = 0; + /** + * Индексы вершин + */ + public var indices:Vector.; + /** + * Количество граней + */ + public var numFaces:int = 0; + + public var texture:BitmapData; + public var smooth:Boolean = false; + public var repeatTexture:Boolean = true; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 1 - по граням + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; + /** + * Режим отсечения граней по направлению к камере + * 0 - отсечение по динамически расчитываемым временным нормалям + * 1 - отсечение по предрасчитанным нормалям. Для расчёта нормалей нужен calculateNormals() + */ + public var backfaceCulling:int = 0; + /** + * Режим сортировки полигонов + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - построение динамического BSP при отрисовке + * 3 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + */ + public var sorting:int = 0; + public var bsp:BSPNode; + /** + * Применение мипмаппинга + * 0 - без мипмаппинга + * 1 - мипмаппинг по удалённости от камеры. Требуется установка свойства mipMap + */ + public var mipMapping:int = 0; + public var mipMap:MipMap; + /** + * Нормали в виде: x, y, z, offset + */ + public var normals:Vector.; + /** + * Геометрическая погрешность при расчёте BSP-дерева + */ + public var threshold:Number = 0.1; + + private var cameraVertices:Vector. = new Vector.(); + private var projectedVertices:Vector. = new Vector.(); + + // Вспомогательные вектора + static private const polygon:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private const sortingMap:Vector. = new Vector.(); + static private const sortingAverageZ:Vector. = new Vector.(); + static private const indices1:Vector. = new Vector.(); + static private const indices2:Vector. = new Vector.(); + static private const fragments:Vector. = new Vector.(); + static private const dynamicNormals:Vector. = new Vector.(); + static private const verticesMap:Vector. = new Vector.(); + static private var fragmentsRealLength:int = 0; + static private var fragmentsLength:int; + protected var sourceIndices:Vector.; + protected var sourceIndicesLength:int; + protected var resultIndices:Vector.; + protected var resultIndicesLength:int; + + public function createEmptyGeometry(numVertices:uint, numFaces:uint):void { + this.numVertices = numVertices; + this.numFaces = numFaces; + vertices = new Vector.(numVertices*3); + indices = new Vector.(numFaces*3); + uvts = new Vector.(numVertices*3); + } + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return (texture != null || mipMap != null) && numFaces > 0; + } + + static alternativa3d const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraCenter:Vector. = new Vector.(3, true); + alternativa3d var cameraX:Number; + alternativa3d var cameraY:Number; + alternativa3d var cameraZ:Number; + alternativa3d function calculateInverseCameraMatrix(matrix:Matrix3D):void { + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(matrix); + inverseCameraMatrix.invert(); + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + inverseCameraMatrix.transformVectors(cameraCenter, cameraCenter); + cameraX = cameraCenter[0], cameraY = cameraCenter[1], cameraZ = cameraCenter[2]; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Выход по объектному клиппингу + if (clipping == 0 && (object.culling & 1)) return; + // Подготовка к отсечению по предрасчитанным нормалям + if (backfaceCulling == 1 || sorting == 3) calculateInverseCameraMatrix(object.cameraMatrix); + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Текущий тип клиппинга: 0 - целиком, 1 - по граням, 2 - подрезка + var clippingType:int = (object.culling == 0 || clipping == 0) ? 0 : clipping; + // Полная отрисовка + if (clippingType == 0) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) triangulate(); + } else { + backfaceCullTriangles(); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) sortPolygons(); + } else { + backfaceCullTriangles(); + if (resultIndicesLength > 0) sortTriangles(); + } + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + backfaceCullPolygons(); + } else { + backfaceCullTriangles(); + } + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, numVertices); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodeTriangles(bsp); + } + // Куллинг + } else if (clippingType == 1) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, numVertices); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + cullNode(bsp, object.culling, camera.nearClipping, camera.farClipping); + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, cameraVertices.length/3); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + clipNode(bsp, object.culling, camera.nearClipping, camera.farClipping, numVertices); + } + } + // Отрисовка + if (resultIndicesLength > 0) { + // Подрезка + resultIndices.length = resultIndicesLength; + // Количество отрисовываемых треугольников + camera.numTriangles += resultIndicesLength/3; + // Коррекция области перерисовки + //correctRedrawRegion(object.culling, camera); + // Проецирование + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Коррекция области перерисовки + if (clippingType > 0) cropVertices(object.culling, camera); + // Отрисовка графики + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.gfx.beginBitmapFill((mipMapping == 0) ? texture : getMipTexture(camera, object), null, repeatTexture, smooth); + canvas.gfx.drawTriangles(projectedVertices, resultIndices, uvts, "none"); + } + // Подрезка вершин и UV + if (clippingType == 2 || sorting == 2) { + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = numVertices << 1; + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + var i:int, j:int, k:int, n:int, vi:int, a:int, b:int, c:int, x:Number, y:Number, z:Number, indicesLength:int, backface:int = backfaceCulling; + if (debugResult & Debug.EDGES || debugResult & Debug.VERTICES || debugResult & Debug.NORMALS) { + // Трансформация + calculateInverseCameraMatrix(object.cameraMatrix); + object.cameraMatrix.transformVectors(vertices, cameraVertices); + if (clipping != 0 || !(object.culling & 1)) { + // Полная отрисовка + if (object.culling == 0 || clipping == 0) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, numVertices); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + } + // Куллинг + } else if (clipping == 1) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) cullPolygons(object.culling, camera.nearClipping, camera.farClipping) else cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) cullPolygons(object.culling, camera.nearClipping, camera.farClipping) else cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, numVertices); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 0; + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) clipPolygons(object.culling, camera.nearClipping, camera.farClipping) else clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + // Динамическое BSP + } else if (sorting == 2) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) clipPolygons(object.culling, camera.nearClipping, camera.farClipping) else clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) { + prepareToDynamicBSP(); + if (fragmentsLength > 0) { + resultIndicesLength = 0; + collectDynamicNode(0, fragmentsLength, cameraVertices.length/3); + } + } + // Статическое BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 0; + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + } + // Отрисовка + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Рёбра + if (debugResult & Debug.EDGES) { + // Полигоны + if ((poly || sorting == 3) && sorting != 2) { + for (i = 0; i < resultIndicesLength; i = k) { + k = resultIndices[i++] + i; + canvas.gfx.lineStyle(0, 0xFFFFFF, 0.3); + for (j = i + 2; j < k - 1; j++) { + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[i] << 1], projectedVertices[++vi]); + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + canvas.gfx.lineStyle(0, 0xFFFFFF); + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[int(k - 1)] << 1], projectedVertices[++vi]); + for (j = i; j < k; j++) { + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + } + // Треугольники + } else { + canvas.gfx.lineStyle(0, 0xFFFFFF); + for (i = 0; i < resultIndicesLength;) { + a = resultIndices[i++], b = resultIndices[i++], c = resultIndices[i++]; + canvas.gfx.moveTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[b << 1], projectedVertices[(b << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[c << 1], projectedVertices[(c << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + } + } + } + // Вершины + if (debugResult & Debug.VERTICES) { + canvas.gfx.lineStyle(); + for (i = 0; i < numVertices; i++) sortingMap[i] = 0; + for (i = 0, k = 0; i < resultIndicesLength; i++) { + if (i == k) k = (poly || sorting == 3) ? (resultIndices[i++] + i) : (i + 3); + if (resultIndices[i] < numVertices) sortingMap[resultIndices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] != 0) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + // Изолированные вершины + for (i = 0, k = 0, indicesLength = indices.length; i < indicesLength; i++) { + if (i == k) k = (poly) ? (indices[i++] + i) : (i + 3); + if (indices[i] < numVertices) sortingMap[indices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] == 0 && (cameraVertices[i*3 + 2]) > camera.nearClipping) { + canvas.gfx.beginFill(0xFF0000); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 3); + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + } + // Нормали + if (debugResult & Debug.NORMALS) { + if (object.culling != 0 && clipping == 2) resultIndices = sourceIndices, resultIndicesLength = sourceIndicesLength; + for (i = 0, k = 0, n = 0; i < resultIndicesLength; i = k) { + k = (poly || sorting == 3) ? (resultIndices[i++] + i) : (i + 3); + x = y = z = 0; + for (j = i; j < k; j++) { + x += cameraVertices[vi = int(resultIndices[j]*3)]; + y += cameraVertices[++vi]; + z += cameraVertices[++vi]; + } + x /= (k - i); + y /= (k - i); + z /= (k - i); + if (clipping == 2 && (z < camera.nearClipping || z > camera.farClipping || z < -x || z < x || z < -y || z < y)) continue; + projectedVertices[n++] = x; + projectedVertices[n++] = y; + projectedVertices[n++] = z; + var ax:Number = cameraVertices[vi = int(resultIndices[i]*3)]; + var ay:Number = cameraVertices[++vi]; + var az:Number = cameraVertices[++vi]; + var abx:Number = cameraVertices[vi = int(resultIndices[int(i + 1)]*3)] - ax; + var aby:Number = cameraVertices[++vi] - ay; + var abz:Number = cameraVertices[++vi] - az; + var acx:Number = cameraVertices[vi = int(resultIndices[int(i + 2)]*3)] - ax; + var acy:Number = cameraVertices[++vi] - ay; + var acz:Number = cameraVertices[++vi] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + if (nl != 0) { + nx /= nl; + ny /= nl; + nz /= nl; + } + var projectionZ:Number = camera.focalLength/z; + projectedVertices[n++] = x + 15*nx*camera.perspectiveScaleX/projectionZ; + projectedVertices[n++] = y + 15*ny*camera.perspectiveScaleY/projectionZ; + projectedVertices[n++] = z + 15*nz/projectionZ; + } + projectedVertices.length = n; + Utils3D.projectVectors(camera.projectionMatrix, projectedVertices, projectedVertices, uvts); + n = n/3 << 1; + for (i = 0; i < n;) { + canvas.gfx.lineStyle(); + canvas.gfx.beginFill(0x00FFFF); + canvas.gfx.drawCircle(projectedVertices[i], projectedVertices[int(i + 1)], 1.5); + canvas.gfx.lineStyle(0, 0x00FFFF); + canvas.gfx.moveTo(projectedVertices[i++], projectedVertices[i++]); + canvas.gfx.lineTo(projectedVertices[i++], projectedVertices[i++]); + } + } + } + // Подрезка + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = numVertices << 1; + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + // Сбор треугольников BSP-дерева + private function collectNodeTriangles(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodeTriangles(node.negative); + for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.positive != null) collectNodeTriangles(node.positive); + } else { + if (node.positive != null) collectNodeTriangles(node.positive); + if (node.negative != null) collectNodeTriangles(node.negative); + } + } + + /*private function collectBSPTriangles():void { + var direction:Boolean = true, node:BSPNode = bsp, negative:BSPNode, positive:BSPNode, prev:BSPNode; + while (node != null) { + if (direction) { + if (node.cameraInfront = cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + negative = node.negative; + if (negative != null) negative.prev = node, node = negative else direction = false; + } else { + positive = node.positive; + if (positive != null) positive.prev = node, node = positive else direction = false; + } + } else { + //if (node.cameraInfront || backfaceCulling == 0) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.cameraInfront) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + prev = node.prev, node.prev = null; + if (node.cameraInfront) { + positive = node.positive; + if (positive != null) positive.prev = prev, node = positive, direction = true else node = prev; + } else { + negative = node.negative; + if (negative != null) negative.prev = prev, node = negative, direction = true else node = prev; + } + } + } + }*/ + + // Сбор полигонов BSP-дерева + private function collectNodePolygons(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodePolygons(node.negative); + for (var i:int = 0, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength;) resultIndices[resultIndicesLength++] = polygons[i++]; + if (node.positive != null) collectNodePolygons(node.positive); + } else { + if (node.positive != null) collectNodePolygons(node.positive); + if (node.negative != null) collectNodePolygons(node.negative); + } + } + + // Куллинг треугольников BSP-дерева + private function cullNode(node:BSPNode, culling:int, near:Number, far:Number):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) cullNode(node.negative, culling, near, far); + // Проход по ноде + for (var i:int = 0, x:Boolean = (culling & 12) > 0, y:Boolean = (culling & 48) > 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) { + var a:int = triangles[i++], b:int = triangles[i++], c:int = triangles[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || az <= -ax && bz <= -bx && cz <= -cx || az <= ax && bz <= bx && cz <= cx || az <= -ay && bz <= -by && cz <= -cy || az <= ay && bz <= by && cz <= cy) continue; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + if (node.positive != null) cullNode(node.positive, culling, near, far); + } else { + if (node.positive != null) cullNode(node.positive, culling, near, far); + if (node.negative != null) cullNode(node.negative, culling, near, far); + } + } + + // Клиппинг полигонов BSP-дерева + private function clipNode(node:BSPNode, culling:int, near:Number, far:Number, v:int):int { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + // Проход по ноде + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, num1:int, num2:int = 0, vi:int, t:Number, au:Number, av:Number, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength; i = k) { + k = (num1 = polygons[i++]) + i; + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + // Триангуляция + for (j = i, a = polygons[j++], b = polygons[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygons[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = polygons[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0, v++; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0, v++; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + } else { + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + } + return v; + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + // Выход по объектному клиппингу + if (clipping == 0 && (object.culling & 1)) return null; + // Подготовка к отсечению по предрасчитанным нормалям + if (backfaceCulling == 1 || sorting == 3) calculateInverseCameraMatrix(object.cameraMatrix); + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Сброс карты + for (var i:int = 0; i < numVertices; i++) { + verticesMap[i] = -1; + } + var geometry:Geometry = Geometry.create(); + var culling:int = (object.culling == 0 || clipping == 0) ? 0 : object.culling + // Если статическое BSP + if (sorting == 3) { + geometry.fragment = getNodeFragments(bsp, geometry, culling, camera.nearClipping, camera.farClipping); + } else { + geometry.fragment = getFragments(geometry, indices, indices.length, poly, culling, camera.nearClipping, camera.farClipping); + } + // Подрезка + if (culling > 0 && clipping == 2) { + cameraVertices.length = uvts.length = numVertices*3; + } + // Если есть фрагменты + if (geometry.fragment != null) { + geometry.cameraMatrix.identity(); + geometry.cameraMatrix.prepend(object.cameraMatrix); + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.texture = (mipMapping == 0) ? texture : getMipTexture(camera, object); + geometry.repeatTexture = repeatTexture; + geometry.smooth = smooth; + if (camera.debugMode) { + geometry.debugResult = camera.checkInDebug(this); + } + return geometry; + } else { + geometry.destroy(); + return null; + } + } + + private function getNodeFragments(node:BSPNode, geometry:Geometry, culling:int, near:Number, far:Number):Fragment { + // Проход по дочерним нодам + var infront:Boolean = cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset; + var fragment:Fragment = infront ? getFragments(geometry, node.polygons, node.polygonsLength, true, culling, near, far) : null; + var negative:Fragment = (node.negative != null) ? getNodeFragments(node.negative, geometry, culling, near, far) : null; + var positive:Fragment = (node.positive != null) ? getNodeFragments(node.positive, geometry, culling, near, far) : null; + // Если нода видна или есть видимые дочерние ноды + if (fragment != null || negative != null && positive != null) { + if (fragment == null) { + fragment = Fragment.create(); + } + fragment.negative = negative; + fragment.positive = positive; + // Расчёт нормали + var vi:int = node.polygons[1]*3; + var ax:Number = cameraVertices[vi]; vi++; + var ay:Number = cameraVertices[vi]; vi++; + var az:Number = cameraVertices[vi]; + vi = node.polygons[2]*3; + var abx:Number = cameraVertices[vi] - ax; vi++; + var aby:Number = cameraVertices[vi] - ay; vi++; + var abz:Number = cameraVertices[vi] - az; + vi = node.polygons[3]*3; + var acx:Number = cameraVertices[vi] - ax; vi++; + var acy:Number = cameraVertices[vi] - ay; vi++; + var acz:Number = cameraVertices[vi] - az; + fragment.normalX = acz*aby - acy*abz; + fragment.normalY = acx*abz - acz*abx; + fragment.normalZ = acy*abx - acx*aby; + var len:Number = 1/Math.sqrt(fragment.normalX*fragment.normalX + fragment.normalY*fragment.normalY + fragment.normalZ*fragment.normalZ); + fragment.normalX *= len; + fragment.normalY *= len; + fragment.normalZ *= len; + fragment.offset = ax*fragment.normalX + ay*fragment.normalY + az*fragment.normalZ; + return fragment; + } else { + return (negative != null) ? negative : positive; + } + } + + private function getFragments(geometry:Geometry, source:Vector., sourceLength:int, poly:Boolean, culling:int, near:Number, far:Number):Fragment { + var first:Fragment; + var last:Fragment; + var j:int; + var nx:Number; + var ny:Number; + var nz:Number; + var no:Number; + var nl:Number; + var a:int; + var b:int; + var c:int; + var ai:int; + var bi:int; + var ci:int; + var vi:int; + var vj:int; + var anx:Number; + var any:Number; + var anz:Number; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var t:Number; + var au:Number; + var av:Number; + var bu:Number; + var bv:Number; + var inside:Boolean; + var outside:Boolean; + var x:Boolean = (culling & 12) > 0; + var y:Boolean = (culling & 48) > 0; + var ni:int = 0; + var num:int = 3; + var k:int = 0; + var v:int = numVertices; + for (var i:int = 0; i < sourceLength; i = k) { + if (poly) { + num = source[i]; i++; + k++; + } + k += num; + var result:Vector. = source; + var resultBegin:int = i; + var resultEnd:int = k; + // Отсечение по backface + if (sorting != 3) { + // Выход по предрасчитанной нормали + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + continue; + } + // Расчёт динамической нормали + if (sorting == 2 || backfaceCulling == 0) { + a = i; + vi = source[a]*3; a++; + anx = cameraVertices[vi]; vi++; + any = cameraVertices[vi]; vi++; + anz = cameraVertices[vi]; + vi = source[a]*3; a++; + var abx:Number = cameraVertices[vi] - anx; vi++; + var aby:Number = cameraVertices[vi] - any; vi++; + var abz:Number = cameraVertices[vi] - anz; + vi = source[a]*3; + var acx:Number = cameraVertices[vi] - anx; vi++; + var acy:Number = cameraVertices[vi] - any; vi++; + var acz:Number = cameraVertices[vi] - anz; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + // Выход по динамической нормали + if (backfaceCulling == 0 && nx*anx + ny*any + nz*anz >= 0) { + continue; + } + } + } + // Отсечение по фрустуму + if (culling > 0) { + // Куллинг + var polygonCulling:int = 0; + // Полигоны + if (poly) { + if (clipping == 1) { + if (culling & 1) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] <= near) break; + } + if (j < k) continue; + } + if (culling & 2) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] < far) break; + } + if (j == k) continue; + } + if (culling & 4) { + for (j = i; j < k; j++) { + vi = source[j]*3; + bi = vi + 2; + if (-cameraVertices[vi] < cameraVertices[bi]) break; + } + if (j == k) continue; + } + if (culling & 8) { + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + if (culling & 16) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (-cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + if (culling & 32) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + } else { + if (culling & 1) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] > near) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 1; + } + } + } + if (culling & 2) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] < far) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 2; + } + } + } + if (culling & 4) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (-cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 4; + } + } + } + if (culling & 8) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 8; + } + } + } + if (culling & 16) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (-cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 16; + } + } + } + if (culling & 32) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 32; + } + } + } + } + // Треугольники + } else { + a = source[i++]; + b = source[i++]; + c = source[i++]; + ai = a*3; + bi = b*3; + ci = c*3; + if (x) { + ax = cameraVertices[ai]; + bx = cameraVertices[bi]; + cx = cameraVertices[ci]; + } + if (y) { + ay = cameraVertices[int(ai + 1)]; + by = cameraVertices[int(bi + 1)]; + cy = cameraVertices[int(ci + 1)]; + } + az = cameraVertices[int(ai + 2)]; + bz = cameraVertices[int(bi + 2)]; + cz = cameraVertices[int(ci + 2)]; + if (clipping == 1) { + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) { + continue; + } + } else { + if (az <= near && bz <= near && cz <= near || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) { + continue; + } + if (az <= near || bz <= near || cz <= near) { + polygonCulling |= 1; + } + if (az >= far || bz >= far || cz >= far) { + polygonCulling |= 2; + } + if (x && (az <= -ax || bz <= -bx || cz <= -cx)) { + polygonCulling |= 4; + } + if (x && (az <= ax || bz <= bx || cz <= cx)) { + polygonCulling |= 8; + } + if (y && (az <= -ay || bz <= -by || cz <= -cy)) { + polygonCulling |= 16; + } + if (y && (az <= ay || bz <= by || cz <= cy)) { + polygonCulling |= 32; + } + } + } + // Клиппинг + if (clipping == 2 && polygonCulling > 0) { + result = polygon; + resultBegin = 0; + // Заполнение полигона + var num1:int = 0; + var num2:int = 0; + if (poly) { + for (j = i; j < k; j++) { + result[num1] = source[j]; + num1++; + } + } else { + result[0] = a; + result[1] = b; + result[2] = c; + num1 = 3; + } + + // Клипинг по ниар + if (polygonCulling & 1) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = near; + uvts[vi] = 0; + } + if (bz > near) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по фар + if (polygonCulling & 2) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz <= far && az > far || bz > far && az <= far) { + t = (far - az)/(bz - az); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = far; + uvts[vi] = 0; + } + if (bz <= far) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по левой стороне + if (polygonCulling & 4) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + ax = cameraVertices[ai]; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bx = cameraVertices[bi]; + bz = cameraVertices[vi]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + result[num2] = v; num2++; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > -bx) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ax = bx; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по правой стороне + if (polygonCulling & 8) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + ax = cameraVertices[ai]; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bx = cameraVertices[bi]; + bz = cameraVertices[vi]; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + result[num2] = v; num2++; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > bx) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ax = bx; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по верхней стороне + if (polygonCulling & 16) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 1; + ay = cameraVertices[vi]; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 1; + by = cameraVertices[vi]; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > -by) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ay = by; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по нижней стороне + if (polygonCulling & 32) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 1; + ay = cameraVertices[vi]; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 1; + by = cameraVertices[vi]; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > by) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ay = by; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + resultEnd = num1; + } + } + // Создание фрагмента + if (first != null) { + last.next = last.create(); + last = last.next; + } else { + first = Fragment.create(); + last = first; + } + // Нормализация и присвоение нормали + if (sorting == 2) { + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + last.normalX = nx*nl; + last.normalY = ny*nl; + last.normalZ = nz*nl; + last.offset = anx*last.normalX + any*last.normalY + anz*last.normalZ; + } + // Заполнение и ремап + for (j = resultBegin; j < resultEnd; j++) { + a = result[j]; + if (a >= numVertices || verticesMap[a] < 0) { + if (a < numVertices) { + verticesMap[a] = geometry.numVertices; + } + vi = a*3; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi]; + geometry.uvts[geometry.verticesLength] = uvts[vi]; vi++; + geometry.verticesLength++; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi]; + geometry.uvts[geometry.verticesLength] = uvts[vi]; vi++; + geometry.verticesLength++; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi]; + geometry.uvts[geometry.verticesLength] = uvts[vi]; + geometry.verticesLength++; + last.indices[last.num] = geometry.numVertices; + geometry.numVertices++; + } else { + last.indices[last.num] = verticesMap[a]; + } + last.num++; + } + } + return first; + } + + // Отсечение треугольников по нормалям + private function backfaceCullTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0; + if (backfaceCulling == 1) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) { + resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + else i += 3; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) { + resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } else { + i += 3; + } + } + } + } + + // Отсечение полигонов по нормалям + private function backfaceCullPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0, k:int = 0; + if (backfaceCulling == 1) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } + } + + // Отсечение треугольников по пирамиде видимости + private function cullTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 0, y:Boolean = (culling & 48) > 0 || backfaceCulling == 0; + for (var i:int = 0, ni:int = 0; i < sourceIndicesLength;) { + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 0) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + } + + // Отсечение полигонов по пирамиде видимости + private function cullPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, j:int, ni:int, num:int, vi:int; i < sourceIndicesLength; i = k) { + k = (num = sourceIndices[i++]) + i; + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 0) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + if (culling & 1) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] <= near) break; + if (j < k) continue; + } + if (culling & 2) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] < far) break; + if (j == k) continue; + } + if (culling & 4) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 8) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 16) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + if (culling & 32) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + resultIndices[resultIndicesLength++] = num; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } + } + + private function clipTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 0, y:Boolean = (culling & 48) > 0 || backfaceCulling == 0; + for (var i:int = 0, j:int, ni:int = 0, v:int = numVertices, vi:int, t:Number, au:Number, av:Number, num1:int, num2:int; i < sourceIndicesLength;) { + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + // За пределами пирамиды видимости + if (az <= near && bz <= near && cz <= near || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 0) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Полностью в пирамиде видимости + var insideNear:Boolean = !(culling & 1) || az > near && bz > near && cz > near, insideFar:Boolean = !(culling & 2) || az < far && bz < far && cz < far, insideLeft:Boolean = !(culling & 4) || az > -ax && bz > -bx && cz > -cx, insideRight:Boolean = !(culling & 8) || az > ax && bz > bx && cz > cx, insideTop:Boolean = !(culling & 16) || az > -ay && bz > -by && cz > -cy, insideBottom:Boolean = !(culling & 32) || az > ay && bz > by && cz > cy; + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } else { + // Заполняем полигон + polygon[0] = a, polygon[1] = b, polygon[2] = c, num1 = 3, num2 = 0; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + } + + private function clipPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, ni:int = 0, num1:int, num2:int = 0, v:int = numVertices, vi:int, t:Number, au:Number, av:Number; i < sourceIndicesLength; i = k) { + k = (num1 = sourceIndices[i++]) + i; + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 0) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Отсечение + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = num1; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = sourceIndices[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Копирование полигона + resultIndices[resultIndicesLength++] = num1; + for (j = 0; j < num1;) resultIndices[resultIndicesLength++] = polygon[j++]; + } + } + } + + // Триангуляция полигонов для отрисовки + private function triangulate():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, a:int, b:int; i < sourceIndicesLength;) { + if (i == k) k = sourceIndices[i++] + i, a = sourceIndices[i++], b = sourceIndices[i++]; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[i++]; + } + } + + private function sortTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, n:int, k:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) map[n = int(i/3)] = i, averageZ[n] = cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)]; + // Сортировка + sortAverageZ(n++); + // Перестановка граней по карте сортировки + for (i = 0; i < n;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i++]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + //flashSort(++n); + //for (i = n - 1; i >= 0;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i--]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + } + + private function sortPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, j:int, n:int = 0, num:int, k:int = 0, z:Number, a:int, b:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) { + if (i == k) map[n] = i, k = (num = sourceIndices[i++]) + i, z = 0; + z += cameraVertices[int(sourceIndices[i]*3 + 2)]; + if (++i == k) averageZ[n++] = z/num; + } + // Сортировка + sortAverageZ(n - 1); + // Перестановка граней по карте сортировки и триангуляция + for (i = 0; i < n; i++) for (j = map[i], k = sourceIndices[j] + ++j, a = sourceIndices[j++], b = sourceIndices[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[j++]; + } + + public function flashSort(length:int):void { + var i:int = 0, j:int = 0, k:int = 0, t:int; + // Одна восьмая часть длины вектора + var len:int = length >> 3; + // Массив значений Z + var averageZ:Vector. = sortingAverageZ; + // Карта перестановок + var map:Vector. = sortingMap; + var mapIndex:int; + // Вспомогательный вектор + var ind:Vector. = new Vector.(len); + //if (ind.length < len) ind.length = len; + // Минимальное значение + var minValue:Number = averageZ[0]; + // Максимальное значение + var maxValue:Number = minValue; + // Индекс максимального элемента + var maxIndex:int = 0; + // Значение текущего элемента + var value:Number; + + // Нахождение минимального значения и индекса максимального элемента + for (i = 1; i < length; ++i) { + value = averageZ[i]; + if (value < minValue) minValue = value; + else if (value > maxValue) maxValue = value, maxIndex = i; + } + + // Если все элементы одинаковы + if (minValue == maxValue) return; + + // Классификация + var c1:Number = (len - 1)/(maxValue - minValue); + + for (i = 0; i < length; i++) { + k = c1*(averageZ[i] - minValue); + ind[k]++; + } + + for (k = 1; k < len; k++) { + t = k - 1; + ind[k] += ind[t]; + } + + // Обмен максимального и нулевого элемента + averageZ[maxIndex] = averageZ[0]; + averageZ[0] = maxValue; + + j = 0; + k = len - 1; + i = length - 1; + + var swap:Number; + var nmove:int = 0; + while (nmove < i) { + while (j > (ind[k] - 1)) { + k = c1*(averageZ[++j] - minValue); + } + + value = averageZ[j]; + + while (j != ind[k]) { + k = c1*(value - minValue); + t = ind[k] - 1; + swap = averageZ[t]; + averageZ[t] = value; + mapIndex = map[t]; + map[t] = map[j]; + map[j] = mapIndex; + value = swap; + ind[k]--; + nmove++; + } + } + + for(i = 1; i < length; i++) { + swap = averageZ[i]; + mapIndex = map[i]; + j = i - 1; + while(j >= 0 && averageZ[j] > swap) { + t = j + 1; + averageZ[t] = averageZ[j]; + map[t] = map[j]; + j--; + } + t = j + 1; + averageZ[t] = swap; + map[t] = mapIndex; + } + } + + private function sortAverageZ(r:int):void { + var l:int = 0; + var i:int, j:int, stack:Vector. = sortingStack, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ, stackIndex:int, left:Number, median:Number, right:Number, mapIndex:int; + for (stack[0] = l, stack[1] = r, stackIndex = 2; stackIndex > 0;) { + j = r = stack[--stackIndex], i = l = stack[--stackIndex], median = averageZ[(r + l) >> 1]; + for (;i <= j;) { + for (;(left = averageZ[i]) > median; i++); + for (;(right = averageZ[j]) < median; j--); + if (i <= j) mapIndex = map[i], map[i] = map[j], map[j] = mapIndex, averageZ[i++] = right, averageZ[j--] = left; + } + if (l < j) stack[stackIndex++] = l, stack[stackIndex++] = j; + if (i < r) stack[stackIndex++] = i, stack[stackIndex++] = r; + } + } + + /*private function correctRedrawRegion(culling:int, camera:Camera3D):void { + var ix:int, iy:int, iz:int, verticesLength:int = cameraVertices.length, vi:int = resultIndices[1]*3; + var tx:Number = cameraVertices[vi++], ty:Number = cameraVertices[vi++], tz:Number = cameraVertices[vi]; + var x:Number, y:Number, z:Number, t:Number = 0.01, near:Number = camera.nearClipping, far:Number = camera.farClipping + t + t; + for (ix = 0, iy = 1, iz = 2; iz < verticesLength; ix += 3, iy += 3, iz += 3) { + x = cameraVertices[ix], y = cameraVertices[iy], z = cameraVertices[iz] + t; + if (z < near || z > far || z < -x || z < x || z < -y || z < y) cameraVertices[ix] = tx, cameraVertices[iy] = ty, cameraVertices[iz] = tz; + } + if (culling & 12) { + if (culling & 48) { + if (culling & 3) { + // xyz + } else { + // xy + } + } else { + if (culling & 3) { + // xz + + } else { + // x + + } + } + } else { + if (culling & 48) { + if (culling & 3) { + // yz + + } else { + // y + + } + } else { + if (culling & 3) { + // z + + } + } + } + }*/ + + // Коррекция области перерисовки + private function cropVertices(culling:int, camera:Camera3D):void { + // Коррекция ширины и высоты с учётом ошибки вычислений + var w:Number = camera.width/2 + 0.1, h:Number = camera.height/2 + 0.1, nearDist:Number = camera.nearClipping; + var i:int, j:int, projectedVerticesLength:int = projectedVertices.length, c:Number; + if (clipping == 1) { + if (culling & 1) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= nearDist) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } + } + } + } else { + if (culling & 1) { + if (culling & 12 && culling & 48) { + for (i = 0, j = 2; i < projectedVerticesLength; i++, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[++i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + if ((c = projectedVertices[++i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } else if (culling & 12) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } + } else if (culling & 48) { + for (i = 1, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[int(i - 1)] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } + } else { + if (culling & 4 && culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } else if (culling & 4) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -w) projectedVertices[i] = -w; + } else if (culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > w) projectedVertices[i] = w; + } + if (culling & 16 && culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } else if (culling & 16) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -h) projectedVertices[i] = -h; + } else if (culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > h) projectedVertices[i] = h; + } + } + } + } + + alternativa3d function getMipTexture(camera:Camera3D, object:Object3D):BitmapData { + // Находим расстояние до объекта + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + object.cameraMatrix.transformVectors(cameraCenter, cameraCenter); + return mipMap.textures[mipMap.getLevel(cameraCenter[2], camera)]; + } + + /** + * Расчёт нормалей + * @param normalize Флаг нормализации + */ + public function calculateNormals(normalize:Boolean = false):void { + // Подготавливаем массив нормалей + if (normals == null) normals = new Vector.() else normals.length = numFaces << 2; + // Расчитываем нормали + for (var i:int = 0, j:int = 0, num:int = 3, vi:int, indicesLength:int = indices.length; i < indicesLength; i += num) { + if (poly) num = indices[i++]; + // Получаем координаты A + var ax:Number = vertices[vi = int(indices[i]*3)]; + var ay:Number = vertices[++vi]; + var az:Number = vertices[++vi]; + // Получаем вектор AB + var abx:Number = vertices[vi = int(indices[int(i + 1)]*3)] - ax; + var aby:Number = vertices[++vi] - ay; + var abz:Number = vertices[++vi] - az; + // Получаем вектор AC + var acx:Number = vertices[vi = int(indices[int(i + 2)]*3)] - ax; + var acy:Number = vertices[++vi] - ay; + var acz:Number = vertices[++vi] - az; + // Считаем нормаль + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + // Нормализуем + if (normalize) { + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > 0.001) { + nl = 1/Math.sqrt(nl); + nx *= nl; + ny *= nl; + nz *= nl; + } + } + // Сохраняем нормаль и смещение + normals[j++] = nx; + normals[j++] = ny; + normals[j++] = nz; + normals[j++] = ax*nx + ay*ny + az*nz; + } + } + + public function optimizeForDynamicBSP():void { + var i:int; + var j:int; + var k:int; + var n:int; + var infront:Boolean; + var behind:Boolean; + var num:int = 3; + var ni:int = 0; + var nj:int = 0; + var indicesLength:int = indices.length; + var splits:Vector. = sortingAverageZ; + var stack:Vector. = sortingStack; + var map:Vector. = sortingMap; + // Сбор сплитов + for (i = 0; i < numFaces; i++) { + var normalX:Number = normals[ni]; ni++; + var normalY:Number = normals[ni]; ni++; + var normalZ:Number = normals[ni]; ni++; + var offset:Number = normals[ni]; ni++; + splits[i] = 0; + for (j = 0, k = 0, n = 0; j < indicesLength;) { + if (j == k) { + if (n == i) { + map[i] = (i << 16) + j; + } + n++; + if (poly) { + num = indices[j]; j++; + k++; + } + k += num; + if (n - 1 == i) { + j = k; + continue; + } + behind = false; + infront = false; + } + var vi:int = indices[j]*3; + var x:Number = vertices[vi]; vi++; + var y:Number = vertices[vi]; vi++; + var z:Number = vertices[vi]; + var o:Number = x*normalX + y*normalY + z*normalZ - offset; + if (o < -threshold) { + behind = true; + if (infront) { + j = k - 1; + } + } else if (o > threshold) { + infront = true; + if (behind) { + j = k - 1; + } + } + j++; + if (j == k && behind && infront) { + splits[i]++; + } + } + } + // Сортировка по сплитам + stack[0] = 0; + stack[1] = numFaces - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + k = (r + l) >> 1; + var median:Number = splits[k]; + while (i <= j) { + var left:Number = splits[i]; + while (left > median) { + i++; + left = splits[i]; + } + var right:Number = splits[j]; + while (right < median) { + j--; + right = splits[j]; + } + if (i <= j) { + var mapIndex:int = map[i]; + map[i] = map[j]; + map[j] = mapIndex; + splits[i] = right; + i++; + splits[j] = left; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Перестановка + var newIndices:Vector. = new Vector.(indicesLength); + var newNormals:Vector. = new Vector.(numFaces << 2); + j = 0; + for (n = numFaces - 1; n >= 0; n--) { + k = map[n]; + ni = (k >> 16) << 2; + i = k & 0xFFFF; + if (poly) { + num = indices[i]; i++; + newIndices[j] = num; j++; + } + k = i + num; + for (; i < k; i++) { + newIndices[j] = indices[i]; j++; + } + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; + } + indices = newIndices; + normals = newNormals; + } + + /** + * Расчёт локального BSP-дерева + * @param splitAnalysis Флаг сплит-анализа. + * Если он включен, дерево построится с наименьшим количеством распилов, но построение будет медленнее + */ + public function calculateBSP(splitAnalysis:Boolean = false):void { + bsp = null; + if (numFaces == 0) return; + // Подготовка к построению нового дерева + fragmentsLength = 0; + for (var i:int = 0, k:int = 0, ni:int = 0, num:int, indicesLength:int = indices.length; i < indicesLength; i++) { + if (i == k) k = (num = poly ? indices[i++] : 3) + i, fragments[fragmentsLength++] = (ni << 16) + num, ni += 4; + fragments[fragmentsLength++] = indices[i]; + } + // Построение дерева + bsp = createNode(0, fragmentsLength, splitAnalysis); + } + + private function prepareToDynamicBSP():void { + fragmentsLength = 0; + for (var i:int = 0, num:int = 3, ni:int = 0; i < resultIndicesLength;) { + if (poly) num = resultIndices[i++]; + fragments[fragmentsLength++] = (ni << 16) + num; + var vi:int; + var ax:Number = cameraVertices[vi = int(resultIndices[i]*3)]; + var ay:Number = cameraVertices[++vi]; + var az:Number = cameraVertices[++vi]; + var abx:Number = cameraVertices[vi = int(resultIndices[int(i + 1)]*3)] - ax; + var aby:Number = cameraVertices[++vi] - ay; + var abz:Number = cameraVertices[++vi] - az; + var acx:Number = cameraVertices[vi = int(resultIndices[int(i + 2)]*3)] - ax; + var acy:Number = cameraVertices[++vi] - ay; + var acz:Number = cameraVertices[++vi] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + i += num; + nx *= nl; + ny *= nl; + nz *= nl; + dynamicNormals[ni++] = nx; + dynamicNormals[ni++] = ny; + dynamicNormals[ni++] = nz; + dynamicNormals[ni++] = ax*nx + ay*ny + az*nz; + for (var j:int = i - num; j < i; j++) { + fragments[fragmentsLength++] = resultIndices[j]; + } + } + } + + private function collectDynamicNode(begin:int, end:int, v:int):int { + // Построение ноды + var mi:int = fragments[begin], ni:int = mi >> 16, num:int = mi & 0xFFFF; + var normalX:Number = dynamicNormals[ni++]; + var normalY:Number = dynamicNormals[ni++]; + var normalZ:Number = dynamicNormals[ni++]; + var offset:Number = dynamicNormals[ni]; + // Подготовка к разделению + var reserve:int = end - begin + ((end - begin) >> 2); + var negativeBegin:int = fragmentsLength; + var negativeEnd:int = negativeBegin; + var nodeBegin:int = fragmentsLength + reserve; + var nodeEnd:int = nodeBegin; + var positiveBegin:int = fragmentsLength + reserve + reserve; + var positiveEnd:int = positiveBegin; + if ((fragmentsLength = positiveBegin + reserve) > fragmentsRealLength) fragments.length = fragmentsRealLength = fragmentsLength; + var i:int; + var k:int = begin + num + 1; + // Добавление сплиттера + fragments[nodeEnd++] = num; + for (i = begin + 1; i < k; i++) { + fragments[nodeEnd++] = fragments[i]; + } + // Перебираем грани + for (var j1:int = negativeEnd, j2:int = positiveEnd, vi:int = v*3, infront:Boolean, behind:Boolean, t:Number, uv:Number; i < end;) { + if (i == k) { + // Подготовка к разбиению + mi = fragments[i], ni = mi >> 16, num = mi & 0xFFFF, k = num + ++i, j1++, j2++, infront = false, behind = false; + // Первая точка ребра + var a:int = fragments[int(k - 1)], ai:int = a*3; + var ax:Number = cameraVertices[ai], ay:Number = cameraVertices[int(ai + 1)], az:Number = cameraVertices[int(ai + 2)]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + } + // Вторая точка ребра + var b:int = fragments[i], bi:int = b*3; + var bx:Number = cameraVertices[bi], by:Number = cameraVertices[int(bi + 1)], bz:Number = cameraVertices[int(bi + 2)]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) t = ao/(ao - bo), cameraVertices[vi] = ax + (bx - ax)*t, uvts[vi++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi++] = 0, fragments[j1++] = fragments[j2++] = v++; + // Добавление точки + if (bo < -threshold) { + fragments[j1++] = b, behind = true; + } else if (bo > threshold) { + fragments[j2++] = b, infront = true; + } else { + fragments[j1++] = fragments[j2++] = b; + } + // Анализ разбиения + if (++i == k) { + if (infront && behind) { + // Фрагмент распилился + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1; + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2; + } else if (infront) { + // Фрагмент спереди + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2, j1 = negativeEnd; + } else if (behind) { + // Фрагмент сзади + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1, j2 = positiveEnd; + } else { + // Фрагмент в плоскости ноды + fragments[nodeEnd++] = num; + for (var j:int = k - num; j < k; j++) { + fragments[nodeEnd++] = fragments[j]; + } + j1 = negativeEnd, j2 = positiveEnd; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ao = bo; + } + } + // Разделение заднй части + if (negativeEnd > negativeBegin) { + v = collectDynamicNode(negativeBegin, negativeEnd, v); + } + // Сбор фрагментов в плоскости ноды + for (i = nodeBegin, k = nodeBegin; i < nodeEnd;) { + if (i == k) k = fragments[i++] + i, a = fragments[i++], b = fragments[i++]; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = fragments[i++]; + } + // Разделение передней части + if (positiveEnd > positiveBegin) { + v = collectDynamicNode(positiveBegin, positiveEnd, v); + } + return v; + } + + private function createNode(begin:int, end:int, splitAnalysis:Boolean):BSPNode { + var i:int; + var j:int; + var k:int; + var num:int; + var vi:int; + var mi:int; + var ni:int; + var infront:Boolean; + var behind:Boolean; + var ii:int; + var normalX:Number; + var normalY:Number; + var normalZ:Number; + var offset:Number; + var a:int; + var ai:int; + var ax:Number; + var ay:Number; + var az:Number; + var ao:Number; + var b:int; + var bi:int; + var bx:Number; + var by:Number; + var bz:Number; + var bo:Number; + // Определение сплиттера + var splitter:int = begin; + if (splitAnalysis) { + var bestSplits:int = int.MAX_VALUE; + // Перебираем нормали + for (i = begin; i < end; i += (mi & 0xFFFF) + 1) { + var currentSplits:int = 0; + mi = fragments[i]; + ni = mi >> 16; + normalX = normals[ni]; ni++; + normalY = normals[ni]; ni++; + normalZ = normals[ni]; ni++; + offset = normals[ni]; + // Перебираем точки граней + for (j = begin, k = begin; j < end;) { + if (j == k) { + k = (fragments[j] & 0xFFFF) + ++j; + infront = false; + behind = false; + } + vi = fragments[j]*3; + ax = vertices[vi]; vi++; + ay = vertices[vi]; vi++; + az = vertices[vi]; + ao = ax*normalX + ay*normalY + az*normalZ - offset; + if (ao < -threshold) { + behind = true; + if (infront) { + j = k - 1; + } + } else if (ao > threshold) { + infront = true; + if (behind) { + j = k - 1; + } + } + if (++j == k) { + if (behind && infront) { + currentSplits++; + if (currentSplits >= bestSplits) break; + } + } + } + // Если найдена плоскость лучше текущей + if (currentSplits < bestSplits) { + splitter = i; + bestSplits = currentSplits; + // Если плоскость ничего не распиливает + if (bestSplits == 0) break; + } + } + } + // Построение ноды + var node:BSPNode = new BSPNode(); + mi = fragments[splitter]; + ni = mi >> 16; + num = mi & 0xFFFF; + node.normalX = normals[ni++]; + node.normalY = normals[ni++]; + node.normalZ = normals[ni++]; + node.offset = normals[ni]; + node.addFragment(fragments, splitter + 1, splitter + num + 1); + // Если в куче только сплиттер + if (end - begin == num + 1) return node; + // Подготовка к разделению + var reserve:int = end - begin + ((end - begin) >> 2); + var negativeBegin:int = fragmentsLength; + var negativeEnd:int = negativeBegin; + var positiveBegin:int = fragmentsLength + reserve; + var positiveEnd:int = positiveBegin; + fragmentsLength = positiveBegin + reserve; + if (fragmentsLength > fragmentsRealLength) { + fragments.length = fragmentsLength; + fragmentsRealLength = fragmentsLength; + } + // Перебираем грани + var j1:int = negativeEnd; + var j2:int = positiveEnd; + for (i = begin, k = begin, vi = numVertices*3; i < end;) { + if (i == k) { + // Пропуск сплиттера + if (i == splitter) { + i += (fragments[i] & 0xFFFF) + 1; + if (i == end) break; + } + // Подготовка к разбиению + mi = fragments[i]; + ni = mi >> 16; + num = mi & 0xFFFF; + k = num + ++i; + j1++; + j2++; + infront = false; + behind = false; + // Первая точка ребра + ii = k - 1; + a = fragments[ii]; + ai = a*3; + ax = vertices[ai]; ii = ai + 1; + ay = vertices[ii]; ii++; + az = vertices[ii]; + ao = ax*node.normalX + ay*node.normalY + az*node.normalZ - node.offset; + } + // Вторая точка ребра + b = fragments[i]; + bi = b*3; + bx = vertices[bi]; ii = bi + 1; + by = vertices[ii]; ii++; + bz = vertices[ii]; + bo = bx*node.normalX + by*node.normalY + bz*node.normalZ - node.offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ii = ai + 1; + var av:Number = uvts[ii]; + var bu:Number = uvts[bi]; ii = bi + 1; + var bv:Number = uvts[ii]; + vertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + vertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + vertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; vi++; + fragments[j1] = numVertices; j1++; + fragments[j2] = numVertices; j2++; + numVertices++; + } + // Добавление точки + if (bo < -threshold) { + fragments[j1] = b; j1++; + behind = true; + } else if (bo > threshold) { + fragments[j2] = b; j2++; + infront = true; + } else { + fragments[j1] = b; j1++; + fragments[j2] = b; j2++; + } + // Анализ разбиения + if (++i == k) { + if (infront && behind) { + // Фрагмент распилился + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1; + negativeEnd = j1; + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1; + positiveEnd = j2; + } else if (infront) { + // Фрагмент спереди + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1; + positiveEnd = j2; + j1 = negativeEnd; + } else if (behind) { + // Фрагмент сзади + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1; + negativeEnd = j1; + j2 = positiveEnd; + } else { + // Фрагмент в плоскости ноды + normalX = normals[ni]; ii = ni + 1; + normalY = normals[ii]; ii++; + normalZ = normals[ii]; + if (node.normalX*normalX + node.normalY*normalY + node.normalZ*normalZ > 0) { + // Фрагмент сонаправлен ноде + node.addFragment(fragments, k - num, k); + j1 = negativeEnd; + j2 = positiveEnd; + } else { + // Фрагмент противонаправлен ноде + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1; + negativeEnd = j1; + j2 = positiveEnd; + } + } + } else { + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + } + // Разделение заднй части + if (negativeEnd > negativeBegin) { + node.negative = createNode(negativeBegin, negativeEnd, splitAnalysis); + } + // Разделение передней части + if (positiveEnd > positiveBegin) { + node.positive = createNode(positiveBegin, positiveEnd, splitAnalysis); + } + return node; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = numVertices*3; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } + + /** + * Копирование свойств другого меша. Осторожно, свойства будут иметь прямые ссылки на свойства копируемого меша. + * @param mesh Объект копирования + */ + public function copyFrom(mesh:Mesh):void { + + alpha = mesh.alpha; + blendMode = mesh.blendMode; + + poly = mesh.poly; + + numVertices = mesh.numVertices; + numFaces = mesh.numFaces; + vertices = mesh.vertices; + indices = mesh.indices; + uvts = mesh.uvts; + + texture = mesh.texture; + mipMap = mesh.mipMap; + + smooth = mesh.smooth; + repeatTexture = mesh.repeatTexture; + + backfaceCulling = mesh.backfaceCulling; + clipping = mesh.clipping; + sorting = mesh.sorting; + mipMapping = mesh.mipMapping; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + + normals = mesh.normals; + bsp = mesh.bsp; + } + + public function generateClass(className:String = "GeneratedMesh", packageName:String = "", textureName:String = null):String { + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["__AS3__.vec.Vector"] = true; + importSet["alternativa.engine3d.core.Mesh"] = true; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + + constructor += "\t\t\tnumVertices = " + numVertices +";\r"; + constructor += "\t\t\tnumFaces = " + numFaces +";\r"; + constructor += "\t\t\tvertices = Vector.(["; + var length:uint = numVertices*3; + var n:int = 0; + + var i:int; + + for (i = 0; i < length; i++) { + constructor += vertices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tindices = Vector.(["; + length = numFaces*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += indices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + + constructor += "\t\t\tuvts = Vector.(["; + length = numVertices*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvts[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + var embeds:String = ""; + if (textureName != null) { + importSet["flash.display.BitmapData"] = true; + var bmpName:String = textureName.charAt(0).toUpperCase() + textureName.substr(1); + embeds += "\t\t[Embed(source=\"" + textureName + "\")] private static const bmp" + bmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + textureName + ":BitmapData = new bmp" + bmpName + "().bitmapData;\r\r"; + constructor += "\t\t\ttexture = " + textureName + ";\r\r"; + } + + constructor += "\t\t\tclipping = " + clipping +";\r"; + constructor += "\t\t\trepeatTexture = " + (repeatTexture ? "true" : "false") +";\r\r"; + + constructor += "\t\t\tmatrix.rawData = Vector.([" + matrix.rawData + "]);\r"; + if (_boundBox != null) { + importSet["alternativa.engine3d.bounds.BoundBox"] = true; + constructor += "\t\t\t_boundBox = new BoundBox(" + _boundBox.minX + ", " + _boundBox.minY + ", " + _boundBox.minZ + ", " + _boundBox.maxX + ", " + _boundBox.maxY + ", " + _boundBox.maxZ + ");\r"; + } + + var imports:String = ""; + + var importArray:Array = new Array(); + for (var key:* in importSet) { + importArray.push(key); + } + importArray.sort(); + + var newLine:Boolean = false; + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + // Объединение вершин + /** + * Объединение вершин с одинаковыми координатами + * @param distanceThreshold Погрешность, в пределах которой координаты считаются одинаковыми + * @param uvThreshold Погрешность, в пределах которой UV-координаты считаются одинаковыми + */ + public function weldVertices(distanceThreshold:Number = 0, uvThreshold:Number = 0):void { + var i:int, j:int, k:int, t:int; + + // Карта соответствий + var weld:Vector. = new Vector.(numVertices = vertices.length/3); + for (i = 0; i < numVertices; i++) weld[i] = i; + + // Ненужные индексы + var uselessIndices:Vector. = new Vector.(); + var numUselessIndices:uint = 0; + + // Сравнение вершин по координатам и UV + for (i = 0; i < numVertices - 1; i++) { + if (weld[i] == i) { + var ax:Number = vertices[k = i*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + for (j = i + 1; j < numVertices; j++) { + if (weld[j] == j) { + var bx:Number = vertices[k = j*3]; + var by:Number = vertices[k + 1]; + var bz:Number = vertices[k + 2]; + var bu:Number = uvts[k]; + var bv:Number = uvts[k + 1]; + if ((ax - bx <= distanceThreshold) && (ax - bx >= -distanceThreshold) && (ay - by <= distanceThreshold) && (ay - by >= -distanceThreshold) && (az - bz <= distanceThreshold) && (az - bz >= -distanceThreshold) && (au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + weld[j] = i; + uselessIndices[numUselessIndices++] = j; + } + } + } + } + } + + // Удаление ненужных вершин и UV + for (i = 0, j = 0; j < numVertices; j++) { + if (weld[j] == j) { + if (i != j) { + vertices[k = i*3] = vertices[t = j*3]; + vertices[k + 1] = vertices[t + 1]; + vertices[k + 2] = vertices[t + 2]; + uvts[k] = uvts[t]; + uvts[k + 1] = uvts[t + 1]; + } + i++; + } + } + vertices.length = i*3; + uvts.length = i*3; + numVertices = i; + + // Корректировка индексов + uselessIndices.sort(function(x:int, y:int):Number {return x - y;}); + var numIndices:int = indices.length; + for (i = 0, j = 0; i < numIndices; i++) { + j = (poly && i == j) ? (indices[i++] + i) : j; + k = t = weld[indices[i]]; + for (var u:int = 0; u < numUselessIndices; u++) { + if (k > uselessIndices[u]) { + t--; + } else { + break; + } + } + indices[i] = t; + } + } + + /** + * Объединение треугольников в многоугольники + * После вызова этого метода флаг poly становится true + * @param angleThreshold Допустимый угол в радианах между нормалями, чтобы считать, что объединяемые грани в одной плоскости + * @param uvThreshold Допустимая разница uv-координат, чтобы считать, что объединяемые грани состыковываются по UV + * @param convexThreshold Величина, уменьшающая допустимый угол между смежными рёбрами объединяемых граней + */ + public function convertToPoly(angleThreshold:Number = 0, uvThreshold:Number = 0, convexThreshold:Number = 0):void { + + if (poly) return; + + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + uvThreshold += digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + + var i:int, j:int, n:int, k:int, t:int; + + // Вспомогательные флаги + var contains:Vector. = new Vector.(numFaces = indices.length/3); + + // Расчёт нормалей и матриц uv-трансформации + var normals:Vector. = new Vector.(numFaces*3); + var matrices:Vector. = new Vector.(numFaces*8); + for (i = 0; i < numFaces; i++) { + // Нахождение нормали + var ax:Number = vertices[k = indices[t = i*3]*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + var abx:Number = vertices[k = indices[t + 1]*3] - ax; + var aby:Number = vertices[k + 1] - ay; + var abz:Number = vertices[k + 2] - az; + var abu:Number = uvts[k] - au; + var abv:Number = uvts[k + 1] - av; + var acx:Number = vertices[k = indices[t + 2]*3] - ax; + var acy:Number = vertices[k + 1] - ay; + var acz:Number = vertices[k + 2] - az; + var acu:Number = uvts[k] - au; + var acv:Number = uvts[k + 1] - av; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + // Если грань не вырождена + if (nl > digitThreshold) { + contains[i] = 1; + // Нормализация и сохранение нормали + normals[t] = nx /= nl; + normals[t + 1] = ny /= nl; + normals[t + 2] = nz /= nl; + // Нахождение обратной матрицы грани + var det:Number = -nx*acy*abz + acx*ny*abz + nx*aby*acz - abx*ny*acz - acx*aby*nz + abx*acy*nz; + var ma:Number = (-ny*acz + acy*nz)/det; + var mb:Number = (nx*acz - acx*nz)/det; + var mc:Number = (-nx*acy + acx*ny)/det; + var md:Number = (ax*ny*acz - nx*ay*acz - ax*acy*nz + acx*ay*nz + nx*acy*az - acx*ny*az)/det; + var me:Number = (ny*abz - aby*nz)/det; + var mf:Number = (-nx*abz + abx*nz)/det; + var mg:Number = (nx*aby - abx*ny)/det; + var mh:Number = (nx*ay*abz - ax*ny*abz + ax*aby*nz - abx*ay*nz - nx*aby*az + abx*ny*az)/det; + // Умножение прямой uv-матрицы на обратную матрицу грани и сохранение матрицы uv-трансформации + matrices[t = i*8] = abu*ma + acu*me; + matrices[t + 1] = abu*mb + acu*mf; + matrices[t + 2] = abu*mc + acu*mg; + matrices[t + 3] = abu*md + acu*mh + au; + matrices[t + 4] = abv*ma + acv*me; + matrices[t + 5] = abv*mb + acv*mf; + matrices[t + 6] = abv*mc + acv*mg; + matrices[t + 7] = abv*md + acv*mh + av; + } + } + + // Разбиение граней на группы по углу, UV и соседству + var islands:Vector.>> = new Vector.>>(); + var numIslands:int = 0; + var island:Vector.>; + var islandLength:int; + var f:Vector., fLen:int, fi:int, fj:int; + var s:Vector., sLen:int, si:int, sj:int; + for (i = 0; i < numFaces; i++) { + if (contains[i] > 0) { + contains[i] = 0; + // Создание группы + island = new Vector.>(); + islands[numIslands] = island; + // Создание грани и добавление в группу + f = new Vector.(3); + f[0] = indices[k = i*3]; + f[1] = indices[k + 1]; + f[2] = indices[k + 2]; + island[0] = f; + islandLength = 1; + normals[t = numIslands++*3] = nx = normals[k = i*3]; + normals[t + 1] = ny = normals[k + 1]; + normals[t + 2] = nz = normals[k + 2]; + ma = matrices[k = i*8]; + mb = matrices[k + 1]; + mc = matrices[k + 2]; + md = matrices[k + 3]; + me = matrices[k + 4]; + mf = matrices[k + 5]; + mg = matrices[k + 6]; + mh = matrices[k + 7]; + // Перебор и дополнение группы + for (n = 0; n < islandLength; n++) { + f = island[n]; + var a1:int = f[0]; + var b1:int = f[1]; + var c1:int = f[2]; + for (j = i + 1; j < numFaces; j++) { + if (contains[j] > 0) { + // Если грани сонаправлены + if (nx*normals[k = j*3] + ny*normals[k + 1] + nz*normals[k + 2] >= angleThreshold) { + var a2:int = indices[k]; + var b2:int = indices[k + 1]; + var c2:int = indices[k + 2]; + // Если грани соседние + if ((k = (a1 == c2 && b1 == b2 || b1 == c2 && c1 == b2 || c1 == c2 && a1 == b2) ? a2 : ((a1 == a2 && b1 == c2 || b1 == a2 && c1 == c2 || c1 == a2 && a1 == c2) ? b2 : ((a1 == b2 && b1 == a2 || b1 == b2 && c1 == a2 || c1 == b2 && a1 == a2) ? c2 : -1))) >= 0) { + ax = vertices[k *= 3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + au = uvts[k]; + av = uvts[k + 1]; + var bu:Number = ma*ax + mb*ay + mc*az + md; + var bv:Number = me*ax + mf*ay + mg*az + mh; + // Если совпадают по UV + if ((au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + contains[j] = 0; + s = new Vector.(3); + s[0] = a2; + s[1] = b2; + s[2] = c2; + island[islandLength++] = s; + } + } + } + } + } + } + } + } + + poly = true; + numFaces = 0; + + // Объединение + var numIndices:int = 0; + var faces1:Vector.> = new Vector.>(); + var faces2:Vector.> = new Vector.>(); + for (n = 0; n < numIslands; n++) { + island = islands[n]; + islandLength = island.length; + nx = normals[k = n*3]; + ny = normals[k + 1]; + nz = normals[k + 2]; + // Дополнение вспомогательных списков, если нужно + for (i = faces1.length; i < islandLength; i++) { + faces1[i] = new Vector.(); + faces2[i] = new Vector.(); + } + var numFaces1:int = islandLength; + var numFaces2:int = 0; + // Копирование граней из группы в первый список + for (i = 0; i < islandLength; i++) { + f = island[i]; + fLen = f.length; + s = faces1[i]; + for (j = 0; j < fLen; j++) s[j] = f[j]; + s.length = fLen; + } + // Объединение + do { + // Подготовка к итерации + var weld:Boolean = false; + for (i = 0; i < numFaces1; i++) contains[i] = 1; + // Попытки объединить текущую грань со всеми следующими + for (i = 0; i < numFaces1; i++) { + if (contains[i] > 0) { + f = faces1[i]; + fLen = f.length; + for (j = i + 1; j < numFaces1; j++) { + if (contains[j] > 0) { + s = faces1[j]; + sLen = s.length; + // Проверка на соседство + for (fi = 0; fi < fLen; fi++) { + a1 = f[fi]; + b1 = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]; + for (si = 0; si < sLen; si++) { + a2 = s[si]; + b2 = s[sj = (si < sLen - 1) ? (si + 1) : 0]; + if (a1 == b2 && b1 == a2) break; + } + if (si < sLen) break; + } + // Если грань соседняя + if (fi < fLen) { + // Расширение граней объединеия + while (true) { + fj = (fj < fLen - 1) ? (fj + 1) : 0; + si = (si > 0) ? (si - 1) : (sLen - 1); + b2 = f[fj]; + c2 = s[si]; + if (b2 == c2) a2 = c2 else break; + } + while (true) { + sj = (sj < sLen - 1) ? (sj + 1) : 0; + fi = (fi > 0) ? (fi - 1) : (fLen - 1); + b1 = s[sj]; + c1 = f[fi]; + if (b1 == c1) a1 = c1 else break; + } + // Первый перегиб + ax = vertices[k = a1*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b1*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c1*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + var crx:Number = aby*acz - abz*acy; + var cry:Number = abz*acx - abx*acz; + var crz:Number = abx*acy - aby*acx; + var zeroCross:Boolean = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Второй перегиб + ax = vertices[k = a2*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b2*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c2*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + zeroCross = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Объединение + var fs:Vector. = faces2[numFaces2++]; + var fsLen:int = 0; + fs[fsLen++] = a2; + while (true) { + fs[fsLen++] = f[fj]; + if (fj != fi) fj = (fj < fLen - 1) ? (fj + 1) : 0 else break; + } + fs[fsLen++] = a1; + while (true) { + fs[fsLen++] = s[sj]; + if (sj != si) sj = (sj < sLen - 1) ? (sj + 1) : 0 else break; + } + fs.length = fsLen; + contains[j] = 0; + weld = true; + break; + } + } + } + // Если не было объединения + if (j == numFaces1) { + s = faces2[numFaces2++]; + for (fi = 0; fi < fLen; fi++) s[fi] = f[fi]; + s.length = fLen; + } + } + } + // Переброс списков + island = faces1; + faces1 = faces2; + numFaces1 = numFaces2; + faces2 = island; + numFaces2 = 0; + } while (weld); + // Запись индексов в полигональной форме + for (i = 0; i < numFaces1; i++) { + f = faces1[i]; + fLen = f.length; + indices[numIndices++] = fLen; + if (fLen > 3) { + // Определение наилучшей последовательности + var max:Number = -Number.MAX_VALUE; + for (fi = 0; fi < fLen; fi++) { + ax = vertices[k = f[fi]*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = f[(fj < fLen - 1) ? (fj + 1) : 0]*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + nl = Math.sqrt(crx*crx + cry*cry + crz*crz); + if (nl > max) { + max = nl; + j = fi; + } + } + for (fi = j; fi < fLen; fi++) indices[numIndices++] = f[fi]; + for (fi = 0; fi < j; fi++) indices[numIndices++] = f[fi]; + } else { + for (fi = 0; fi < fLen; fi++) indices[numIndices++] = f[fi]; + } + numFaces++; + } + } + indices.length = numIndices; + } + + } +} + + class BSPNode { + + // Дочерние ноды + public var negative:BSPNode; + public var positive:BSPNode; + // Нормаль и смещение + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + // Треугольники + public var triangles:Vector. = new Vector.(); + public var trianglesLength:uint = 0; + // Полигоны + public var polygons:Vector. = new Vector.(); + public var polygonsLength:int = 0; + + // Добавление фрагмента в ноду + public function addFragment(fragment:Vector., begin:int, end:int):void { + polygons[polygonsLength++] = end - begin; + var i:int = begin, a:int = polygons[polygonsLength++] = fragment[i++], b:int = polygons[polygonsLength++] = fragment[i++], c:int; + while (i < end) { + triangles[trianglesLength++] = a; + triangles[trianglesLength++] = b; + triangles[trianglesLength++] = c = polygons[polygonsLength++] = fragment[i++]; + b = c; + } + } + } diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/MeshReference.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/MeshReference.as new file mode 100644 index 0000000..96a8515 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/MeshReference.as @@ -0,0 +1,110 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + import flash.display.BitmapData; + import alternativa.engine3d.core.MipMap; + + use namespace alternativa3d; + + public class MeshReference extends Object3D { + + public var referenceMesh:Mesh; + + public var sorting:int; + + public var texture:BitmapData; + + public var mipMapping:int; + + public var mipMap:MipMap; + + public function MeshReference(referenceMesh:Mesh = null, sorting:int = 0, texture:BitmapData = null, mipMapping:int = 0, mipMap:MipMap = null) { + this.referenceMesh = referenceMesh; + this.sorting = sorting; + this.texture = texture; + this.mipMapping = mipMapping; + this.mipMap = mipMap; + } + + override alternativa3d function get canDraw():Boolean { + return (texture != null || mipMap != null) && referenceMesh.numFaces > 0; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сохранение параметров + var meshSorting:int = referenceMesh.sorting; + var meshTexture:BitmapData = referenceMesh.texture; + var meshMipMapping:int = referenceMesh.mipMapping; + var meshMipMap:MipMap = referenceMesh.mipMap; + // Назначение своих + referenceMesh.sorting = sorting; + referenceMesh.texture = texture; + referenceMesh.mipMapping = mipMapping; + referenceMesh.mipMap = mipMap; + // Отрисовка + referenceMesh.draw(camera, object, parentCanvas); + // Возврат параметров + referenceMesh.sorting = meshSorting; + referenceMesh.texture = meshTexture; + referenceMesh.mipMapping = meshMipMapping; + referenceMesh.mipMap = meshMipMap; + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сохранение параметров + var meshSorting:int = referenceMesh.sorting; + var meshTexture:BitmapData = referenceMesh.texture; + var meshMipMapping:int = referenceMesh.mipMapping; + var meshMipMap:MipMap = referenceMesh.mipMap; + // Назначение своих + referenceMesh.sorting = sorting; + referenceMesh.texture = texture; + referenceMesh.mipMapping = mipMapping; + referenceMesh.mipMap = mipMap; + // Отрисовка + referenceMesh.debug(camera, object, parentCanvas); + // Возврат параметров + referenceMesh.sorting = meshSorting; + referenceMesh.texture = meshTexture; + referenceMesh.mipMapping = meshMipMapping; + referenceMesh.mipMap = meshMipMap; + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + // Сохранение параметров + var meshSorting:int = referenceMesh.sorting; + var meshTexture:BitmapData = referenceMesh.texture; + var meshMipMapping:int = referenceMesh.mipMapping; + var meshMipMap:MipMap = referenceMesh.mipMap; + // Назначение своих + referenceMesh.sorting = sorting; + referenceMesh.texture = texture; + referenceMesh.mipMapping = mipMapping; + referenceMesh.mipMap = mipMap; + // Получение геометрии + var geometry:Geometry = referenceMesh.getGeometry(camera, object); + // Возврат параметров + referenceMesh.sorting = meshSorting; + referenceMesh.texture = meshTexture; + referenceMesh.mipMapping = meshMipMapping; + referenceMesh.mipMap = meshMipMap; + return geometry; + } + + override public function get boundBox():BoundBox { + return referenceMesh.boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return referenceMesh.calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Occluder.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Occluder.as new file mode 100644 index 0000000..0932d7e --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Occluder.as @@ -0,0 +1,421 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Полигональный объект-перекрытие. + * Объекты, которые он перекрывает от видимости камеры, исключаются из отрисовки. + * Сам окклюдер не отрисовывается. + * Должен быть конвексным + */ + public class Occluder extends Object3D { + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; + public var vertices:Vector.; + public var edges:Vector.; // Два индекса - вершины, два - грани + public var indices:Vector.; + public var normals:Vector.; + + // Отношение площади перекрытия к площади вьюпорта (0 - 1) + /** + * Минимальное отношение площади перекрытия окклюдером вьюпорта к площади вьюпорта (от 0 до 1) + * Если окклюдер перекрывает больше, он помещается в очередь и учитывается + * при дальнейшей отрисовке в пределах кадра, иначе игнорируется + */ + public var minSize:Number = 0; + + private const cameraVertices:Vector. = new Vector.(); + private const visibilityMap:Vector. = new Vector.; + + /** + * Коприрование геометрии меша + * @param mesh Объект копирования + * Меш, геометрия которого копируется, обязан быть конвексным, иначе окклюдер будет некорректно работать + */ + public function copyFrom(mesh:Mesh):void { + poly = mesh.poly; + vertices = mesh.vertices; + indices = mesh.indices; + normals = mesh.normals; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = vertices.length; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } + + /** + * Расчёт рёбер по имеющимся вершинам и граням + */ + public function calculateEdges():void { + // Подготавливаем массив рёбер + if (edges == null) edges = new Vector.(); + + // Собираем рёбра + for (var i:int = 0, j:int = 0, n:int = 0, k:int = 0, a:int, b:int, length:int = indices.length; i < length;) { + if (i == k) { + k = poly ? (indices[i++] + i) : (i + 3); + a = indices[int(k - 1)]; + } + b = indices[i]; + edges[j++] = a; + edges[j++] = b; + edges[j++] = n; + edges[j++] = -1; + if (++i == k) n++; else a = b; + } + edges.length = j; + + // Убираем дубли + length = j, i = 0; k = 0; + var ac:int, bc:int; + while (i < length) { + if ((a = edges[i++]) >= 0) { + b = edges[i++]; + edges[k++] = a; + edges[k++] = b; + edges[k++] = edges[i++]; + j = ++i; + while (j < length) { + ac = edges[j++]; + bc = edges[j++]; + if (ac == a && bc == b || ac == b && bc == a) { + edges[int(j - 2)] = -1; + edges[k] = edges[j]; + break; + } + j += 2; + } + k++; + } else i += 3; + } + edges.length = k; + } + + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const center:Vector. = new Vector.(3, true); + private var cameraX:Number; + private var cameraY:Number; + private var cameraZ:Number; + static private const projectedEdges:Vector. = new Vector.(); + static private const uvts:Vector. = new Vector.(); + static private const viewEdges:Vector. = new Vector.(); + static private const debugEdges:Vector. = new Vector.(); + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (cameraInside) return; + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var planeOccluder:Vector., edgeOccluder:Vector., planeOccluderLength:int = 0, edgeOccluderLength:int = 0, viewEdgesLength:int = 0; + if (camera.occlusionPlanes.length > camera.numOccluders) { + planeOccluder = camera.occlusionPlanes[camera.numOccluders]; + edgeOccluder = camera.occlusionEdges[camera.numOccluders]; + } else { + planeOccluder = camera.occlusionPlanes[camera.numOccluders] = new Vector.(); + edgeOccluder = camera.occlusionEdges[camera.numOccluders] = new Vector.(); + } + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) { + ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n]; + } else { + bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + } + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + // Расчёт нормали плоскости отсечения + planeOccluder[planeOccluderLength++] = bz*ay - by*az, planeOccluder[planeOccluderLength++] = bx*az - bz*ax, planeOccluder[planeOccluderLength++] = by*ax - bx*ay; + // Сохранение рёбер + edgeOccluder[edgeOccluderLength++] = ax, edgeOccluder[edgeOccluderLength++] = ay, edgeOccluder[edgeOccluderLength++] = az, edgeOccluder[edgeOccluderLength++] = bx, edgeOccluder[edgeOccluderLength++] = by, edgeOccluder[edgeOccluderLength++] = bz; + } else i -= 2; + } + if (planeOccluderLength > 0) { + // Проверка размера на экране + if (minSize > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((edgeOccluder.length = edgeOccluderLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, edgeOccluder, projectedEdges, uvts); + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + var square:Number = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + if (square/(camera.viewSizeX*camera.viewSizeY*8) < minSize) return; + } + // Добавление окклюдера + camera.numOccluders++; + planeOccluder.length = planeOccluderLength; + edgeOccluder.length = edgeOccluderLength; + } else { + if (occludeAll) { + camera.numOccluders = 0, camera.occludedAll = true; + } else { + return; + } + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var inDebug:int = camera.checkInDebug(this); + if (inDebug == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + + // Рёбра + if (inDebug & Debug.EDGES) { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (!cameraInside) { + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var debugEdgesLength:int = 0, viewEdgesLength:int = 0; + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) { + ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n]; + } else { + bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + } + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + debugEdges[debugEdgesLength++] = ax; + debugEdges[debugEdgesLength++] = ay; + debugEdges[debugEdgesLength++] = az; + debugEdges[debugEdgesLength++] = bx; + debugEdges[debugEdgesLength++] = by; + debugEdges[debugEdgesLength++] = bz; + } else i -= 2; + } + if (debugEdgesLength > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((debugEdges.length = debugEdgesLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, debugEdges, projectedEdges, uvts); + // Проверка размера на экране + var square:Number = Number.MAX_VALUE; + if (minSize > 0) { + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + square = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + } + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + var color:int, thickness:Number; + if (square/(camera.viewSizeX*camera.viewSizeY*8) >= minSize) { + color = 0x0000FF, thickness = 3; + } else { + color = 0x0077AA, thickness = 1; + } + for (i = 0; i < projectedEdges.length;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + canvas.gfx.moveTo(ax, ay); + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.lineTo(ax + (bx - ax)*0.8, ay + (by - ay)*0.8); + canvas.gfx.lineStyle(thickness, 0xFF0000); + canvas.gfx.lineTo(bx, by); + } + for (i = 0; i < viewEdgesLength;) { + canvas.gfx.moveTo(viewEdges[i++], viewEdges[i++]); + canvas.gfx.lineTo(viewEdges[i++], viewEdges[i++]); + } + } else { + if (occludeAll) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(6, 0xFF0000); + canvas.gfx.moveTo(-camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, -camera.viewSizeY); + } + } + } + } + // Оси, центры, имена, баунды + if (inDebug & Debug.AXES) object.drawAxes(camera, canvas); + if (inDebug & Debug.CENTERS) object.drawCenter(camera, canvas); + if (inDebug & Debug.NAMES) object.drawName(camera, canvas); + if (inDebug & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Reference.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Reference.as new file mode 100644 index 0000000..e3611a7 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Reference.as @@ -0,0 +1,56 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект-ссылка. + * Может ссылаться на любой трёхмерный объект, в том числе контейнер с любой вложенностью или Reference. + * При отрисовке он отрисовывает вместо себя объект, + * на который ссылается, подставляя только свою трансформацию, alpha, blendMode, colorTransform и filters. + */ + public class Reference extends Object3D { + + /** + * Объект, который подставляется при отрисовке вместо себя + */ + public var referenceObject:Object3D; + + public function Reference(referenceObject:Object3D = null) { + this.referenceObject = referenceObject; + } + + override alternativa3d function get canDraw():Boolean { + return referenceObject.canDraw; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return referenceObject.getGeometry(camera, object); + } + + override public function get boundBox():BoundBox { + return referenceObject.boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return referenceObject.calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/SkeletalMesh.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/SkeletalMesh.as new file mode 100644 index 0000000..b5554f2 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/SkeletalMesh.as @@ -0,0 +1,146 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SkeletalMesh extends Mesh { + + alternativa3d var _numBones:uint = 0; + alternativa3d var bones:Vector. = new Vector.(); + private var weights:Vector.>; + private var originalVertices:Vector.; + private var originalBonesMatrices:Vector.; + private var boneVertices:Vector.; + + public function initBones():void { + // Инициализируем массив весов и сохраняем оригинальные матрицы костей + originalBonesMatrices = new Vector.(_numBones, true); + weights = new Vector.>(_numBones, true); + for (var j:int = 0; j < _numBones; j++) { + originalBonesMatrices[j] = new Matrix3D(); + originalBonesMatrices[j].prepend(bones[j].matrix); + originalBonesMatrices[j].invert(); + weights[j] = new Vector.(numVertices, true); + } + // Формируем вспомогательные массивы для вершин + originalVertices = new Vector.(numVertices*3, true); + boneVertices = new Vector.(numVertices*3, true); + + // Обрабатываем вершины + var v:Vector3D = new Vector3D(); + for (var i:int = 0; i < numVertices; i++) { + var k:int = i*3; + // Сохраняем оригинальные координаты вершин + originalVertices[k] = vertices[k]; + originalVertices[k + 1] = vertices[k + 1]; + originalVertices[k + 2] = vertices[k + 2]; + + // Находим веса для каждой кости + var sumWeight:Number = 0; + for (j = 0; j < _numBones; j++) { + // Находим расстояние от вершины до кости + var b1:Vector3D = bones[j].matrix.transformVector(new Vector3D()); + var b2:Vector3D = bones[j].matrix.transformVector(new Vector3D(0, 0, bones[j].length)); + v.x = originalVertices[k]; + v.y = originalVertices[k + 1]; + v.z = originalVertices[k + 2]; + var w:Number = 1 - distanceToBone(b1, b2, v)/bones[j].distance; + //trace(w); + w = (w > 0) ? w : 0; + weights[j][i] = w; + sumWeight += w; + } + + // Нормализуем веса + if (sumWeight > 0) { + for (j = 0; j < _numBones; j++) { + weights[j][i] /= sumWeight; + } + } else { + // Если вершина не относится ни к какой кости, помечаем + for (j = 0; j < _numBones; j++) { + weights[j][i] = -1; + } + } + + } + } + + private function distanceToBone(b1:Vector3D, b2:Vector3D, p:Vector3D):Number { + var v:Vector3D = b2.subtract(b1); + var w:Vector3D = p.subtract(b1); + + var c1:Number = w.dotProduct(v); + if ( c1 <= 0 ) + return Vector3D.distance(p, b1); + + var c2:Number = v.dotProduct(v); + if ( c2 <= c1 ) + return Vector3D.distance(p, b2); + + v.scaleBy(c1 / c2); + var Pb:Vector3D = b1.add(v); + return Vector3D.distance(p, Pb); + + + } + + private var m:Matrix3D = new Matrix3D(); + public function calculateBones():void { + // Обнуление координат + for (var i:int = 0; i < numVertices*3; i++) { + vertices[i] = 0; + } + // Добавление трансформации через кости + for (var j:int = 0; j < _numBones; j++) { + m.identity(); + m.prepend(bones[j].matrix); + m.prepend(originalBonesMatrices[j]); + m.transformVectors(originalVertices, boneVertices); + var boneWeights:Vector. = weights[j]; + for (i = 0; i < numVertices; i++) { + var weight:Number = boneWeights[i]; + var k1:int = i*3; + var k2:int = k1 + 1; + var k3:int = k1 + 2; + if (weight >= 0) { + vertices[k1] += boneVertices[k1]*weight; + vertices[k2] += boneVertices[k2]*weight; + vertices[k3] += boneVertices[k3]*weight; + } else { + vertices[k1] = originalVertices[k1]; + vertices[k2] = originalVertices[k2]; + vertices[k3] = originalVertices[k3]; + } + } + } + } + + public function addBone(bone:Bone):void { + bones[_numBones++] = bone; + } +/* + public function removeChild(bone:Bone):void { + var i:int = bones.indexOf(bone); + if (i < 0) throw new ArgumentError(); + children.splice(i, 1); + _numBones--; + var len:uint = drawChildren.length; + for (i = 0; i < len; i++) { + if (drawChildren[i] == bone) { + drawChildren.splice(i, 1); + break; + } + } + bone._parent = null; + bone.setStage(null); + } +*/ + + + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/Sprite3D.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..b42fb0f --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,508 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Плоский, всегда развёрнутый к камере трёхмерный объект + */ + public class Sprite3D extends Object3D { + public var texture:BitmapData; + public var smooth:Boolean = false; + /** + * Режим сортировки на случай конфликта + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + * 3 - построение динамического BSP при отрисовке + */ + public var sorting:int = 0; + /** + * Применение мипмаппинга + * 0 - без мипмаппинга + * 1 - мипмаппинг по удалённости от камеры. Требуется установка свойства mipMap + */ + public var mipMapping:int = 0; + public var mipMap:MipMap; + /** + * X точки привязки + */ + public var originX:Number = 0.5; + /** + * Y точки привязки + */ + public var originY:Number = 0.5; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; + /** + * Угол поворота в радианах в плоскости экрана + */ + public var rotation:Number = 0; + /** + * Зависимость размера на экране от удалённости от камеры + */ + public var perspectiveScale:Boolean = true; + + // Вспомогательные + static private const vertices:Vector. = new Vector.(); + static private const axes:Vector. = Vector.([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]); + static private const cameraAxes:Vector. = new Vector.(12, true); + static private const textureMatrix:Matrix = new Matrix(); + static private var drawTexture:BitmapData; + private var projectionX:Number; + private var projectionY:Number; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (texture == null && mipMap == null) return; + var verticesLength:int = calculateVertices(object, camera); + if (verticesLength > 0) { + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.gfx.beginBitmapFill(drawTexture, textureMatrix, false, smooth); + var x:Number = vertices[0]*projectionX; + var y:Number = vertices[1]*projectionY; + if (rotation == 0) { + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + canvas.gfx.moveTo(x, y); + for (var i:int = 3; i < verticesLength; i++) { + x = vertices[i]*projectionX; i++; + y = vertices[i]*projectionY; i++; + canvas.gfx.lineTo(x, y); + } + } + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + var i:int, length:int, x:Number, y:Number, t:Number = 0.1; + // Рёбра + if (debugResult & Debug.EDGES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(0, 0xFFFFFF); + if (rotation == 0) { + x = vertices[0]*projectionX, y = vertices[1]*projectionY; + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + // Отрисовка + canvas.gfx.moveTo(vertices[length - 3]*projectionX, vertices[length - 2]*projectionY); + for (i = 0; i < length; i++) canvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + } + } + } + // Вершины + if (debugResult & Debug.VERTICES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(); + if (rotation == 0) { + var x1:Number = vertices[0]*projectionX, y1:Number = vertices[1]*projectionY; + var x2:Number = vertices[6]*projectionX, y2:Number = vertices[7]*projectionY; + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y1, 2); + } + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y1, 2); + } + } else { + for (i = 0; i < length; i++) { + x = vertices[i++]*projectionX, y = vertices[i++]*projectionY; + if (x > -camera.viewSizeX + t && x < camera.viewSizeX - t && y > -camera.viewSizeY + t && y < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x, y, 2); + } + } + } + } + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (texture == null && mipMap == null) return null; + var verticesLength:int = calculateVertices(object, camera); + if (verticesLength > 0) { + var geometry:Geometry = Geometry.create(); + geometry.fragment = Fragment.create(); + geometry.numVertices = verticesLength/3; + geometry.fragment.num = geometry.numVertices; + geometry.verticesLength = verticesLength; + if (geometry.uvts.length < verticesLength) { + geometry.uvts.length = verticesLength; + } + for (var i:int = 0, j:int = 0; i < geometry.numVertices; i++) { + geometry.vertices[j] = vertices[j]; j++; + geometry.vertices[j] = vertices[j]; j++; + geometry.vertices[j] = vertices[j]; j++; + geometry.fragment.indices[i] = i; + } + geometry.cameraMatrix.identity(); + geometry.cameraMatrix.prepend(object.cameraMatrix); + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.texture = drawTexture; + geometry.repeatTexture = false; + geometry.smooth = smooth; + if (camera.debugMode) { + geometry.debugResult = camera.checkInDebug(this); + } + geometry.viewAligned = true; + geometry.textureMatrix.a = textureMatrix.a; + geometry.textureMatrix.b = textureMatrix.b; + geometry.textureMatrix.c = textureMatrix.c; + geometry.textureMatrix.d = textureMatrix.d; + geometry.textureMatrix.tx = textureMatrix.tx; + geometry.textureMatrix.ty = textureMatrix.ty; + geometry.projectionX = projectionX; + geometry.projectionY = projectionY; + return geometry; + } else { + return null; + } + } + + private function calculateVertices(object:Object3D, camera:Camera3D):int { + // Трансформация локальных осей в камеру + object.cameraMatrix.transformVectors(axes, cameraAxes); + var x:Number = cameraAxes[0]; + var y:Number = cameraAxes[1]; + var z:Number = cameraAxes[2]; + if (z <= camera.nearClipping || z >= camera.farClipping) return 0; + // Проекция + projectionX = camera.viewSizeX/z; + projectionY = camera.viewSizeY/z; + var projectionZ:Number = camera.focalLength/z; + // Нахождение среднего размера спрайта + var ax:Number = (cameraAxes[3] - x)/camera.perspectiveScaleX; + var ay:Number = (cameraAxes[4] - y)/camera.perspectiveScaleY; + var az:Number = cameraAxes[5] - z; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[6] - x)/camera.perspectiveScaleX; + ay = (cameraAxes[7] - y)/camera.perspectiveScaleY; + az = cameraAxes[8] - z; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[9] - x)/camera.perspectiveScaleX; + ay = (cameraAxes[10] - y)/camera.perspectiveScaleY; + az = cameraAxes[11] - z; + size += Math.sqrt(ax*ax + ay*ay + az*az); + size /= 3; + // Определение текстуры и коррекция размера + if (mipMapping == 0) { + drawTexture = texture; + } else { + var level:int = mipMap.getLevel(z/size, camera); + size *= Math.pow(2, level); + drawTexture = mipMap.textures[level]; + } + // Учёт флага масштабирования + if (!perspectiveScale) { + size /= projectionZ; + } + var x1:Number; + var y1:Number; + var x2:Number; + var y2:Number; + // Если не задано вращение + if (rotation == 0) { + // Размеры спрайта в матрице камеры + var cameraWidth:Number = size*drawTexture.width*camera.perspectiveScaleX; + var cameraHeight:Number = size*drawTexture.height*camera.perspectiveScaleY; + + // Расчёт вершин в матрице камеры + x1 = x - originX*cameraWidth; + y1 = y - originY*cameraHeight; + x2 = x1 + cameraWidth; + y2 = y1 + cameraHeight; + + // Отсечение по вьюпорту + if (object.culling > 0 && (x1 > z || y1 > z || x2 < -z || y2 < -z)) return 0; + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = size*projectionZ; + textureMatrix.b = textureMatrix.c = 0; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + // Подрезка + if (clipping == 2) { + if (x1 < -z) x1 = -z; + if (y1 < -z) y1 = -z; + if (x2 > z) x2 = z; + if (y2 > z) y2 = z; + } + + // Заполняем вершины + vertices[0] = x1; + vertices[1] = y1; + vertices[2] = z; + vertices[3] = x1; + vertices[4] = y2; + vertices[5] = z; + vertices[6] = x2; + vertices[7] = y2; + vertices[8] = z; + vertices[9] = x2; + vertices[10] = y1; + vertices[11] = z; + + return 12; + + } else { + + // Размер спрайта в камере без коррекции под FOV90 + var textureWidth:Number = drawTexture.width; + var textureHeight:Number = drawTexture.height; + + // Расчёт векторов ширины и высоты + var sin:Number = Math.sin(rotation)*size; + var cos:Number = Math.cos(rotation)*size; + var cameraWidthX:Number = cos*textureWidth*camera.perspectiveScaleX; + var cameraWidthY:Number = -sin*textureWidth*camera.perspectiveScaleY; + var cameraHeightX:Number = sin*textureHeight*camera.perspectiveScaleX; + var cameraHeightY:Number = cos*textureHeight*camera.perspectiveScaleY; + + // Заполняем вершины + var length:int = 12; + vertices[0] = x1 = x - originX*cameraWidthX - originY*cameraHeightX; + vertices[1] = y1 = y - originX*cameraWidthY - originY*cameraHeightY; + vertices[2] = z; + vertices[3] = x1 + cameraHeightX; + vertices[4] = y1 + cameraHeightY; + vertices[5] = z; + vertices[6] = x1 + cameraWidthX + cameraHeightX; + vertices[7] = y1 + cameraWidthY + cameraHeightY; + vertices[8] = z; + vertices[9] = x1 + cameraWidthX; + vertices[10] = y1 + cameraWidthY; + vertices[11] = z; + + if (object.culling > 0) { + // Отсечение по вьюпорту + var i:int, infront:Boolean, behind:Boolean, inside:Boolean; + var clipLeft:Boolean = false; + if (object.culling & 4) { + for (i = 0; i < length; i += 3) if ((inside = -vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipLeft = true; + infront = false; behind = false; + } + var clipRight:Boolean = false; + if (object.culling & 8) { + for (i = 0; i < length; i += 3) if ((inside = vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipRight = true; + infront = false; behind = false; + } + var clipTop:Boolean = false; + if (object.culling & 16) { + for (i = 1; i < length; i += 3) if ((inside = -vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipTop = true; + infront = false; behind = false; + } + var clipBottom:Boolean = false; + if (object.culling & 32) { + for (i = 1; i < length; i += 3) if ((inside = vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipBottom = true; + } + // Подрезка + if (clipping == 2) { + var n:int = 0, t:Number, bx:Number, by:Number; + if (clipLeft) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (-bx < z && -ax >= z || -bx >= z && -ax < z) { + t = (ax + z)/(ax - bx); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (-bx < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipRight) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (bx < z && ax >= z || bx >= z && ax < z) { + t = (z - ax)/(bx - ax); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (bx < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipTop) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (-by < z && -ay >= z || -by >= z && -ay < z) { + t = (ay + z)/(ay - by); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (-by < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipBottom) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (by < z && ay >= z || by >= z && ay < z) { + t = (z - ay)/(by - ay); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (by < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + } + } + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = cos*projectionZ; + textureMatrix.b = -sin*projectionZ; + textureMatrix.c = sin*projectionZ; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + return length; + + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчёт локального радиуса + var t:BitmapData = (mipMapping == 0) ? texture : mipMap.textures[0]; + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*t.width; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*t.height; + var radius:Number = Math.sqrt(w*w + h*h); + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(axes, cameraAxes); + var cz:Number = cameraAxes[2]; + var cx:Number = cameraAxes[0]; + var cy:Number = cameraAxes[1]; + // Нахождение среднего размера спрайта + var ax:Number = cameraAxes[3] - cx; + var ay:Number = cameraAxes[4] - cy; + var az:Number = cameraAxes[5] - cz; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[6] - cx; + ay = cameraAxes[7] - cy; + az = cameraAxes[8] - cz; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[9] - cx; + ay = cameraAxes[10] - cy; + az = cameraAxes[11] - cz; + size = radius*(size + Math.sqrt(ax*ax + ay*ay + az*az))/3; + boundBox.setSize(cx - size, cy - size, cz - size, cx + size, cy + size, cz + size); + } else { + boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + return boundBox; + } + + public function copyFrom(source:Sprite3D):void { + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + originX = source.originX; + originY = source.originY; + smooth = source.smooth; + clipping = source.clipping; + rotation = source.rotation; + perspectiveScale = source.perspectiveScale; + texture = source.texture; + mipMapping = source.mipMapping; + mipMap = source.mipMap; + matrix.identity(); + matrix.append(source.matrix); + if (source.boundBox != null) { + if (boundBox == null) boundBox = new BoundBox(); + boundBox.copyFrom(source.boundBox); + } else boundBox = null; + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/WireBoundBox.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/WireBoundBox.as new file mode 100644 index 0000000..a3c7a6e --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/WireBoundBox.as @@ -0,0 +1,90 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireBoundBox extends Object3D { + + static private const cameraVertices:Vector. = new Vector.(24, true); + static private const projectedVertices:Vector. = new Vector.(16, true); + static private const uvts:Vector. = new Vector.(24, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + cameraVertices[0] = _boundBox.minX; + cameraVertices[1] = _boundBox.minY; + cameraVertices[2] = _boundBox.minZ; + + cameraVertices[3] = _boundBox.minX; + cameraVertices[4] = _boundBox.minY; + cameraVertices[5] = _boundBox.maxZ; + + cameraVertices[6] = _boundBox.minX; + cameraVertices[7] = _boundBox.maxY; + cameraVertices[8] = _boundBox.minZ; + + cameraVertices[9] = _boundBox.minX; + cameraVertices[10] = _boundBox.maxY; + cameraVertices[11] = _boundBox.maxZ; + + cameraVertices[12] = _boundBox.maxX; + cameraVertices[13] = _boundBox.minY; + cameraVertices[14] = _boundBox.minZ; + + cameraVertices[15] = _boundBox.maxX; + cameraVertices[16] = _boundBox.minY; + cameraVertices[17] = _boundBox.maxZ; + + cameraVertices[18] = _boundBox.maxX; + cameraVertices[19] = _boundBox.maxY; + cameraVertices[20] = _boundBox.minZ; + + cameraVertices[21] = _boundBox.maxX; + cameraVertices[22] = _boundBox.maxY; + cameraVertices[23] = _boundBox.maxZ; + + object.cameraMatrix.transformVectors(cameraVertices, cameraVertices); + for (var i:int = 0; i < 8; i++) { + if (cameraVertices[int(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.moveTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.moveTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.moveTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/objects/WireQuad.as b/Alternativa3D7/7.0/alternativa/engine3d/objects/WireQuad.as new file mode 100644 index 0000000..84d9a9b --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/objects/WireQuad.as @@ -0,0 +1,45 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireQuad extends Object3D { + + public var vertices:Vector.; + static private const cameraVertices:Vector. = new Vector.(12, true); + static private const projectedVertices:Vector. = new Vector.(8, true); + static private const uvts:Vector. = new Vector.(12, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + for (var i:int = 0; i < 4; i++) { + if (cameraVertices[(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/primitives/Box.as b/Alternativa3D7/7.0/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..314e821 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,233 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class Box extends Mesh { + + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + var hp:uint = heightSegments + 1; + + createEmptyGeometry((wp*(lp + hp) + lp*hp) << 1, (widthSegments*(lengthSegments + heightSegments) + lengthSegments*heightSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var hh:Number = height*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Нижняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = (widthSegments - x)*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = -hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + indices[f++] = x*lp + y; + + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y; + } else { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = (x + 1)*lp + y; + + indices[f++] = x*lp + y; + indices[f++] = x*lp + y + 1; + indices[f++] = (x + 1)*lp + y + 1; + } + } + } + } + var o:uint = wp*lp; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = o + x*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y; + + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + x*lp + y; + } else { + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y + 1; + } + } + } + } + o += wp*lp; + + // Передняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = -lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z; + + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + } + } + } + } + o += wp*hp; + + // Задняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v++] = (widthSegments - x)*wd; + vertices[v] = lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + } + } + } + } + o += wp*hp; + + // Левая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = -wh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v] = y*ls - lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + } + } + } + } + o += lp*hp; + + // Правая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = wh; + uvts[v++] = y*ld; + vertices[v] = y*ls - lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z; + + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, -hh, wh, lh, hh); + + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D7/7.0/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..c479589 --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,244 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class GeoSphere extends Mesh { + + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + + const sections:uint = 20; + + var i:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + + var v:uint = 0; + + var f:uint = sections*segments*segments; + createEmptyGeometry(f/2 + 2, f); + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = radius; + + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*2*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = subz; + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = -subz; + } + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = -radius; + + for (i = 1; i < 6; i++) { + v = interpolate(0, i, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i % 5 + 1, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i + 5, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, (i + 3) % 5 + 6, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i + 5, i % 5 + 6, segments, v); + } + for (i = 6; i < 11; i++) { + v = interpolate(11, i, segments, v); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, v); + } + } + + for (i = 0; i < numVertices; i++) { + var j:uint = i*3; + uvts[j] = Math.atan2(vertices[j + 1], vertices[j])/(Math.PI*2); + uvts[j] = 0.5 + (reverse ? -uvts[j] : uvts[j]); + uvts[j + 1] = 0.5 + Math.asin(vertices[j + 2]/radius)/Math.PI; + } + + var num:uint = 0; + for (f = 0; f <= sections - 1; f++) { + for (var row:uint = 0; row <= segments - 1; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + + if (reverse) { + indices[num++] = a; + indices[num++] = c; + indices[num++] = b; + } else { + indices[num++] = a; + indices[num++] = b; + indices[num++] = c; + } + + if (column < row) { + var d:uint = findVertices(segments, f, row, column + 1); + if (reverse) { + indices[num++] = a; + indices[num++] = d; + indices[num++] = c; + } else { + indices[num++] = a; + indices[num++] = c; + indices[num++] = d; + } + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + + private function interpolate(a:uint, b:uint, num:uint, v:uint):uint { + if (num < 2) { + return v; + } + a *= 3; + b *= 3; + var ax:Number = vertices[a]; + var ay:Number = vertices[a + 1]; + var az:Number = vertices[a + 2]; + var bx:Number = vertices[b]; + var by:Number = vertices[b + 1]; + var bz:Number = vertices[b + 2]; + var cos:Number = (ax*bx + ay*by + az*bz)/(ax*ax + ay*ay + az*az); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + vertices[v++] = (ax*st2 + bx*st1)/sin; + vertices[v++] = (ay*st2 + by*st1)/sin; + vertices[v++] = (az*st2 + bz*st1)/sin; + } + return v; + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + } +} diff --git a/Alternativa3D7/7.0/alternativa/engine3d/primitives/Plane.as b/Alternativa3D7/7.0/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..4719aba --- /dev/null +++ b/Alternativa3D7/7.0/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,67 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * + */ + public class Plane extends Mesh { + + /** + * + * @param width + * @param length + * @param widthSegments + * @param lengthSegments + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + + createEmptyGeometry(wp*lp, (widthSegments*lengthSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = 0; + + if (x < widthSegments && y < lengthSegments) { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + } + } + } + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, 0, wh, lh, 0); + } + + } +} diff --git a/Alternativa3D7/7.1/alternativa/Alternativa3D.as b/Alternativa3D7/7.1/alternativa/Alternativa3D.as new file mode 100644 index 0000000..b6a7b32 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "7.0.0"; + } +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/alternativa3d.as b/Alternativa3D7/7.1/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/containers/AverageZContainer.as b/Alternativa3D7/7.1/alternativa/engine3d/containers/AverageZContainer.as new file mode 100644 index 0000000..70fa38d --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/containers/AverageZContainer.as @@ -0,0 +1,66 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import flash.geom.Vector3D; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого отрисовываются по удалённости от камеры + */ + public class AverageZContainer extends Object3DContainer { + + static private const averageZ:Vector. = new Vector.(); + static private const center:Vector. = Vector.([0, 0, 0]); + static private const cameraCenter:Vector. = new Vector.(3, true); + static private const sortingStack:Vector. = new Vector.(); + private var sortingStackIndex:int; + private var sortingLeft:Number; + private var sortingMedian:Number; + private var sortingRight:Number; + private var sortingChild:Object3D; + + override protected function calculateOrder(camera:Camera3D, object:Object3D):void { + + // Сортировка + for (var i:int = 0; i < numVisibleChildren; i++) { + (visibleChildren[i] as Object3D).cameraMatrix.transformVectors(center, cameraCenter); + averageZ[i] = cameraCenter[0]*cameraCenter[0] + cameraCenter[1]*cameraCenter[1] + cameraCenter[2]*cameraCenter[2]; + } + + var j:int, l:int = 0, r:int = numVisibleChildren - 1; + sortingStack[0] = l; + sortingStack[1] = r; + sortingStackIndex = 2; + while (sortingStackIndex > 0) { + j = r = sortingStack[--sortingStackIndex]; + i = l = sortingStack[--sortingStackIndex]; + sortingMedian = averageZ[(r + l) >> 1]; + do { + while ((sortingLeft = averageZ[i]) > sortingMedian) i++; + while ((sortingRight = averageZ[j]) < sortingMedian) j--; + if (i <= j) { + sortingChild = visibleChildren[i]; + visibleChildren[i] = visibleChildren[j]; + visibleChildren[j] = sortingChild; + averageZ[i++] = sortingRight; + averageZ[j--] = sortingLeft; + } + } while (i <= j); + if (l < j) { + sortingStack[sortingStackIndex++] = l; + sortingStack[sortingStackIndex++] = j; + } + if (i < r) { + sortingStack[sortingStackIndex++] = i; + sortingStack[sortingStackIndex++] = r; + } + } + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/containers/DirectionContainer.as b/Alternativa3D7/7.1/alternativa/engine3d/containers/DirectionContainer.as new file mode 100644 index 0000000..2c70dab --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/containers/DirectionContainer.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.containers { + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Vector3D; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + public class DirectionContainer extends Object3DContainer { + + public var direction:Vector3D = new Vector3D(0, 0, -1); + + override protected function calculateOrder(camera:Camera3D, object:Object3D):void { + if (Vector3D.Z_AXIS.dotProduct(object.cameraMatrix.deltaTransformVector(direction)) < 0) { + var num:uint = numVisibleChildren >> 1; + for (var i:uint = 0; i < num; i++) { + var child:Object3D = visibleChildren[i]; + visibleChildren[i] = visibleChildren[numVisibleChildren - 1 - i]; + visibleChildren[numVisibleChildren - 1 - i] = child; + } + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/containers/KDTree.as b/Alternativa3D7/7.1/alternativa/engine3d/containers/KDTree.as new file mode 100644 index 0000000..a9dea74 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/containers/KDTree.as @@ -0,0 +1,1235 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.KDNode; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.objects.KDObject; + import alternativa.engine3d.objects.Occluder; + import alternativa.engine3d.objects.Reference; + import alternativa.engine3d.objects.WireBoundBox; + import alternativa.engine3d.objects.WireQuad; + + import flash.display.DisplayObject; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого помещены в бинарную древовидную структуру. + * Для построения дерева нужно добавить статические дочерние объекты + * с помощью addStaticChild(), затем вызвать createTree(). По баундам статических объектов + * построится ориентированная по осям бинарная древовидная структура (KD - частный случай BSP). + * Динамические объекты, можно в любое время добавлять addDynamicChild() и удалять removeDynamicChild(). + * Объекты, добавленные с помощью addChild() будут отрисовываться поверх всего в порядке добавления. + */ + public class KDTree extends Object3DContainer { + + /** + * Геометрическая погрешность. + */ + public var threshold:Number = 0.1; + + /** + * @private + */ + alternativa3d var _numStaticChildren:int = 0; + /** + * @private + */ + alternativa3d var _numDynamicChildren:int = 0; + /** + * @private + */ + alternativa3d var staticChildren:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var dynamicChildren:Vector. = new Vector.(); + + private var rootNode:KDNode; + + static private const kdObjects:Vector. = new Vector.(); + static private var kdObjectsRealLength:int; + private var kdObjectsLength:int; + + // Камера в контейнере + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + private var cameraX:Number; + private var cameraY:Number; + private var cameraZ:Number; + + // Плоскости отсечения + static private const cameraPlanes:Vector. = new Vector.(21, true); + private var nearPlaneX:Number; + private var nearPlaneY:Number; + private var nearPlaneZ:Number; + private var nearPlaneOffset:Number; + private var farPlaneX:Number; + private var farPlaneY:Number; + private var farPlaneZ:Number; + private var farPlaneOffset:Number; + private var leftPlaneX:Number; + private var leftPlaneY:Number; + private var leftPlaneZ:Number; + private var leftPlaneOffset:Number; + private var rightPlaneX:Number; + private var rightPlaneY:Number; + private var rightPlaneZ:Number; + private var rightPlaneOffset:Number; + private var topPlaneX:Number; + private var topPlaneY:Number; + private var topPlaneZ:Number; + private var topPlaneOffset:Number; + private var bottomPlaneX:Number; + private var bottomPlaneY:Number; + private var bottomPlaneZ:Number; + private var bottomPlaneOffset:Number; + + // Перекрытия + private var occluders:Vector.> = new Vector.>(); + private var numOccluders:int; + static private var edgeOccluder:Vector. = new Vector.(); + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0 || rootNode != null || _numDynamicChildren > 0; + } + + /** + * Построение дерева на базе добавленных статических дочерних объектов. + * @param boundBox Изначально заданный баунд. + * Он расширяется, если статические объекты не помещаются в него. + */ + public function createTree(boundBox:BoundBox = null):void { + if (_numStaticChildren == 0) return; + // Создаём корневую ноду + rootNode = new KDNode(); + rootNode.objects = new Vector.(); + rootNode.bounds = new Vector.(); + rootNode.numObjects = 0; + // Расчитываем баунды объектов и рутовой ноды + var rootNodeBoundBox:BoundBox = rootNode.boundBox = (boundBox != null) ? boundBox : new BoundBox(); + // Сначала добавляем не окклюдеры + var staticOccluders:Vector. = new Vector.(), staticOccludersLength:int = 0; + for (var i:int = 0; i < _numStaticChildren; i++) { + var child:Object3D = staticChildren[i]; + // Поиск оригинального объекта + var source:Object3D = child; + while (source is Reference) source = (source as Reference).referenceObject; + // Если окклюдер + if (source is Occluder) { + staticOccluders[staticOccludersLength++] = child; + } else { + var childBoundBox:BoundBox = child.calculateBoundBox(child.matrix); + rootNode.objects[rootNode.numObjects] = child, rootNode.bounds[rootNode.numObjects++] = childBoundBox; + rootNodeBoundBox.addBoundBox(childBoundBox); + } + } + // Добавляем окклюдеры + for (i = 0; i < staticOccludersLength; i++) { + child = staticOccluders[i]; + childBoundBox = child.calculateBoundBox(child.matrix); + rootNode.objects[rootNode.numObjects] = child, rootNode.bounds[rootNode.numObjects++] = childBoundBox; + rootNodeBoundBox.addBoundBox(childBoundBox); + } + // Разделяем рутовую ноду + splitNode(rootNode); + } + + public function traceTree():void { + traceNode("", rootNode); + } + + private function traceNode(str:String, node:KDNode):void { + if (node == null) return; + trace(str, (node.axis == 0) ? "X" : ((node.axis == 1) ? "Y" : "Z"), "objs:", node.objects); + traceNode(str + "-", node.negative); + traceNode(str + "-", node.positive); + } + + /** + * Для отображения нод дерева. Метод нужно вызвать один раз после построения дерева. + */ + public function addWireBounds(container:Object3DContainer, alphaCoeff:Number = 0.8):void { + if (rootNode != null) { + var w:WireBoundBox = new WireBoundBox(); + w.boundBox = rootNode.boundBox; + container.addChild(w); + + addNodeWireBounds(container, rootNode, 1, alphaCoeff); + } + } + + private function addNodeWireBounds(container:Object3DContainer, node:KDNode, alpha:Number, alphaCoeff:Number):void { + if (node == null) return; + + var w:WireQuad = new WireQuad(); + w.alpha = alpha; + if (node.axis == 0) { + w.vertices = Vector.([ + node.coord, node.boundBox.minY, node.boundBox.minZ, + node.coord, node.boundBox.minY, node.boundBox.maxZ, + node.coord, node.boundBox.maxY, node.boundBox.maxZ, + node.coord, node.boundBox.maxY, node.boundBox.minZ, + ]); + w.color = 0xFF0000; + } else if (node.axis == 1) { + w.vertices = Vector.([ + node.boundBox.minX, node.coord, node.boundBox.minZ, + node.boundBox.minX, node.coord, node.boundBox.maxZ, + node.boundBox.maxX, node.coord, node.boundBox.maxZ, + node.boundBox.maxX, node.coord, node.boundBox.minZ, + ]); + w.color = 0x00FF00; + } else { + w.vertices = Vector.([ + node.boundBox.minX, node.boundBox.minY, node.coord, + node.boundBox.minX, node.boundBox.maxY, node.coord, + node.boundBox.maxX, node.boundBox.maxY, node.coord, + node.boundBox.maxX, node.boundBox.minY, node.coord, + ]); + w.color = 0x0000FF; + } + container.addChild(w); + + alpha *= alphaCoeff; + + addNodeWireBounds(container, node.negative, alpha, alphaCoeff); + addNodeWireBounds(container, node.positive, alpha, alphaCoeff); + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Определяем видимые объекты + numVisibleChildren = 0; + calculateVisibleChildren(camera, object); + // Если есть видимые дочерние объекты, расчитываем порядок отрисовки + if (numVisibleChildren > 0) calculateOrder(camera, object); + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, parentCanvas); + } + + // Отрисовка KD-дерева + override protected function drawBack(camera:Camera3D, object:Object3D, canvas:Canvas):void { + // Подготовка камеры к отсечению в координатах контейнера + if (rootNode != null || _numDynamicChildren > 0) { + + // Матрица камеры в контейнере + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + + // Перевод плоскостей камеры в пространство контейнера + cameraPlanes[0] = cameraPlanes[1] = cameraPlanes[2] = cameraPlanes[3] = cameraPlanes[4] = cameraPlanes[6] = cameraPlanes[7] = 0; + cameraPlanes[5] = camera.nearClipping; + cameraPlanes[8] = camera.farClipping; + cameraPlanes[9] = cameraPlanes[10] = cameraPlanes[13] = cameraPlanes[18] = -1; + cameraPlanes[11] = cameraPlanes[12] = cameraPlanes[14] = cameraPlanes[15] = cameraPlanes[16] = cameraPlanes[17] = cameraPlanes[19] = cameraPlanes[20] = 1; + inverseCameraMatrix.transformVectors(cameraPlanes, cameraPlanes); + + // Расчёт координат камеры в контейнере + cameraX = cameraPlanes[0]; + cameraY = cameraPlanes[1]; + cameraZ = cameraPlanes[2]; + + // Ближняя плоскость + var bax:Number = cameraPlanes[9] - cameraPlanes[12]; + var bay:Number = cameraPlanes[10] - cameraPlanes[13]; + var baz:Number = cameraPlanes[11] - cameraPlanes[14]; + var bcx:Number = cameraPlanes[15] - cameraPlanes[12]; + var bcy:Number = cameraPlanes[16] - cameraPlanes[13]; + var bcz:Number = cameraPlanes[17] - cameraPlanes[14]; + nearPlaneX = bcy*baz - bcz*bay; + nearPlaneY = bcz*bax - bcx*baz; + nearPlaneZ = bcx*bay - bcy*bax; + nearPlaneOffset = cameraPlanes[3]*nearPlaneX + cameraPlanes[4]*nearPlaneY + cameraPlanes[5]*nearPlaneZ; + + // Дальняя плоскость + farPlaneX = -nearPlaneX; + farPlaneY = -nearPlaneY; + farPlaneZ = -nearPlaneZ; + farPlaneOffset = cameraPlanes[6]*farPlaneX + cameraPlanes[7]*farPlaneY + cameraPlanes[8]*farPlaneZ; + + // Рёбра пирамиды + var ax:Number = cameraPlanes[9] - cameraX; + var ay:Number = cameraPlanes[10] - cameraY; + var az:Number = cameraPlanes[11] - cameraZ; + var bx:Number = cameraPlanes[12] - cameraX; + var by:Number = cameraPlanes[13] - cameraY; + var bz:Number = cameraPlanes[14] - cameraZ; + var cx:Number = cameraPlanes[15] - cameraX; + var cy:Number = cameraPlanes[16] - cameraY; + var cz:Number = cameraPlanes[17] - cameraZ; + var dx:Number = cameraPlanes[18] - cameraX; + var dy:Number = cameraPlanes[19] - cameraY; + var dz:Number = cameraPlanes[20] - cameraZ; + + // Левая плоскость + leftPlaneX = dy*az - dz*ay; + leftPlaneY = dz*ax - dx*az; + leftPlaneZ = dx*ay - dy*ax; + leftPlaneOffset = cameraX*leftPlaneX + cameraY*leftPlaneY + cameraZ*leftPlaneZ; + + // Правая плоскость + rightPlaneX = by*cz - bz*cy; + rightPlaneY = bz*cx - bx*cz; + rightPlaneZ = bx*cy - by*cx; + rightPlaneOffset = cameraX*rightPlaneX + cameraY*rightPlaneY + cameraZ*rightPlaneZ; + + // Верхняя плоскость + topPlaneX = ay*bz - az*by; + topPlaneY = az*bx - ax*bz; + topPlaneZ = ax*by - ay*bx; + topPlaneOffset = cameraX*topPlaneX + cameraY*topPlaneY + cameraZ*topPlaneZ; + + // Нижняя плоскость + bottomPlaneX = cy*dz - cz*dy; + bottomPlaneY = cz*dx - cx*dz; + bottomPlaneZ = cx*dy - cy*dx; + bottomPlaneOffset = cameraX*bottomPlaneX + cameraY*bottomPlaneY + cameraZ*bottomPlaneZ; + + // Окклюдеры + numOccluders = 0; + if (camera.numOccluders > 0) updateOccluders(camera); + } + // Подготовка динамических объектов + kdObjectsLength = 0; + if (_numDynamicChildren > 0) { + var kdObject:KDObject; + for (var i:int = 0; i < _numDynamicChildren; i++) { + var child:Object3D = dynamicChildren[i]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + // Если объект попадает в пирамиду видимости + if (child.cullingInCamera(camera, object.culling) >= 0) { + // Создаём KD-объект + if ((kdObject = KDObject.createFrom(child, camera, inverseCameraMatrix)) != null) kdObjects[kdObjectsLength++] = kdObject; + } + } + } + // Если необходимо, увеличиваем реальный размер массива KD-объектов + if (kdObjectsLength > kdObjectsRealLength) kdObjectsRealLength = kdObjectsLength; + } + if (rootNode != null) { + // Отрисовка дерева + var culling:int; + if ((culling = cullingInContainer(camera, rootNode.boundBox, object.culling)) >= 0) { + drawNode(rootNode, culling, camera, object, canvas, 0, kdObjectsLength); + } + } else { + // Отрисовка только динамических объектов + if (kdObjectsLength > 0) { + if (kdObjectsLength > 1) { + drawKDObjects(camera, object, canvas, 0, kdObjectsLength); + } else { + (kdObject = kdObjects[0]).draw(camera, object, canvas), kdObjects[0] = null, kdObject.destroy(); + } + } + } + } + + private function updateOccluders(camera:Camera3D):void { + for (var o:int = numOccluders, occluder:Vector.; o < camera.numOccluders; o++) { + var cameraEdgeOccluder:Vector. = camera.occlusionEdges[o], edgeOccluderLength:int = cameraEdgeOccluder.length; + edgeOccluder.length = edgeOccluderLength; + // Перевод точек рёбер окклюдеров в пространство контейнера + inverseCameraMatrix.transformVectors(cameraEdgeOccluder, edgeOccluder); + // Создание окклюдера в контейнере + if (occluders.length > numOccluders) occluder = occluders[numOccluders++] else occluder = occluders[numOccluders++] = new Vector.(); + // Построение плоскостей отсечения + for (var i:int = 0, ni:int = 0, nx:Number, ny:Number, nz:Number; i < edgeOccluderLength;) { + var ax:Number = edgeOccluder[i++] - cameraX, ay:Number = edgeOccluder[i++] - cameraY, az:Number = edgeOccluder[i++] - cameraZ, bx:Number = edgeOccluder[i++] - cameraX, by:Number = edgeOccluder[i++] - cameraY, bz:Number = edgeOccluder[i++] - cameraZ; + occluder[ni++] = nx = bz*ay - by*az, occluder[ni++] = ny = bx*az - bz*ax, occluder[ni++] = nz = by*ax - bx*ay, occluder[ni++] = cameraX*nx + cameraY*ny + cameraZ*nz; + } + occluder.length = ni; + } + } + + private function cullingInContainer(camera:Camera3D, boundBox:BoundBox, culling:int):int { + if (camera.occludedAll) return -1; + if (culling > 0) { + // Отсечение по ниар + if (culling & 1) { + if (nearPlaneX >= 0) if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } + } + // Отсечение по фар + if (culling & 2) { + if (farPlaneX >= 0) if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } + } + // Отсечение по левой стороне + if (culling & 4) { + if (leftPlaneX >= 0) if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } + } + // Отсечение по правой стороне + if (culling & 8) { + if (rightPlaneX >= 0) if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } + } + // Отсечение по верхней стороне + if (culling & 16) { + if (topPlaneX >= 0) if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } + } + // Отсечение по нижней стороне + if (culling & 32) { + if (bottomPlaneX >= 0) if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } + } + } + // Отсечение по окклюдерам + for (var o:int = 0; o < numOccluders; o++) { + var occluder:Vector. = occluders[o], occluderLength:int = occluder.length; + for (var ni:int = 0; ni < occluderLength; ni += 4) { + var nx:Number = occluder[ni], ny:Number = occluder[int(ni + 1)], nz:Number = occluder[int(ni + 2)], no:Number = occluder[int(ni + 3)]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } + } + if (ni == occluderLength) return -1; + } + return culling; + } + + private function cullKDObject(camera:Camera3D, kdObject:KDObject):Boolean { + if (camera.occludedAll) return true; + for (var o:int = kdObject.numCheckedOccluders, boundBox:BoundBox = kdObject._boundBox; o < numOccluders; o++) { + var occluder:Vector. = occluders[o], occluderLength:int = occluder.length; + for (var ni:int = 0; ni < occluderLength; ni += 4) { + var nx:Number = occluder[ni], ny:Number = occluder[int(ni + 1)], nz:Number = occluder[int(ni + 2)], no:Number = occluder[int(ni + 3)]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } + } + if (ni == occluderLength) return true; + } + kdObject.numCheckedOccluders = numOccluders; + return false; + } + + // Отрисовка ноды + private function drawNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, begin:int, end:int):void { + var kdObject:KDObject, i:int, nodeObjects:Vector. = node.objects, nodeNumObjects:int = node.numObjects, nodeNumNonOccluders:int = node.numNonOccluders, staticChild:Object3D, displayObject:DisplayObject; + var negativeBegin:int, negativeEnd:int, nodeBegin:int, nodeEnd:int, positiveBegin:int, positiveEnd:int; + if (camera.occludedAll) { + for (i = begin; i < end; i++) kdObject = kdObjects[i], kdObjects[i] = null, kdObject.destroy(); + return; + } + if (node.negative != null) { + // Узловая нода + var negativeCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.negative.boundBox, culling) : 0; + var positiveCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.positive.boundBox, culling) : 0; + var axisX:Boolean = node.axis == 0, axisY:Boolean = node.axis == 1; + var min:Number, max:Number; + var negative:KDObject, positive:KDObject; + // Если видны обе дочерние ноды + if (negativeCulling >= 0 && positiveCulling >= 0) { + // Если есть динамические объекты + if (begin < end) { + // Делаем резерв для KD-объектов + negativeBegin = negativeEnd = kdObjectsLength, nodeBegin = nodeEnd = negativeBegin + end - begin, positiveBegin = positiveEnd = nodeBegin + end - begin; + if ((kdObjectsLength = positiveBegin + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + // Разделяем + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами + if (kdObject.numCheckedOccluders < numOccluders && cullKDObject(camera, kdObject)) { + kdObject.destroy(); + continue; + } + min = axisX ? kdObject._boundBox.minX : (axisY ? kdObject._boundBox.minY : kdObject._boundBox.minZ); + max = axisX ? kdObject._boundBox.maxX : (axisY ? kdObject._boundBox.maxY : kdObject._boundBox.maxZ); + if (max <= node.maxCoord) { + if (min < node.minCoord) kdObjects[negativeEnd++] = kdObject; + else kdObjects[nodeEnd++] = kdObject; + } else { + if (min >= node.minCoord) kdObjects[positiveEnd++] = kdObject; + else { + // Попилился + kdObject.split(axisX, axisY, node.coord, threshold, negative = kdObject.create(), positive = kdObject.create()), kdObject.destroy(); + if (negative.numVertices > 0) kdObjects[negativeEnd++] = negative else negative.destroy(); + if (positive.numVertices > 0) kdObjects[positiveEnd++] = positive else positive.destroy(); + } + } + } + } + // Отрисовка дочерних нод и объектов в плоскости + if (axisX && cameraX > node.coord || axisY && cameraY > node.coord || !axisX && !axisY && cameraZ > node.coord) { + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positiveBegin, positiveEnd); + // Отрисовка динамических объектов в ноде + for (i = nodeBegin; i < nodeEnd; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами и отрисовка + if (kdObject.numCheckedOccluders == numOccluders || !cullKDObject(camera, kdObject)) kdObject.draw(camera, object, canvas); + kdObject.destroy(); + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) updateOccluders(camera); + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negativeBegin, negativeEnd); + } else { + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negativeBegin, negativeEnd); + // Отрисовка динамических объектов в ноде + for (i = nodeBegin; i < nodeEnd; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами и отрисовка + if (kdObject.numCheckedOccluders == numOccluders || !cullKDObject(camera, kdObject)) kdObject.draw(camera, object, canvas); + kdObject.destroy(); + } + // Отрисовка статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) updateOccluders(camera); + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positiveBegin, positiveEnd); + } + } else { + // Если видна только негативная + if (negativeCulling >= 0) { + // Если есть динамические объекты + if (begin < end) { + // Делаем резерв для KD-объектов + negativeBegin = negativeEnd = kdObjectsLength; + if ((kdObjectsLength = negativeBegin + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + // Разделяем + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами + if (kdObject.numCheckedOccluders < numOccluders && cullKDObject(camera, kdObject)) { + kdObject.destroy(); + continue; + } + min = axisX ? kdObject._boundBox.minX : (axisY ? kdObject._boundBox.minY : kdObject._boundBox.minZ); + max = axisX ? kdObject._boundBox.maxX : (axisY ? kdObject._boundBox.maxY : kdObject._boundBox.maxZ); + if (max <= node.maxCoord) kdObjects[negativeEnd++] = kdObject; + else if (min < node.minCoord) { + // Подрезаем + kdObject.crop(axisX, axisY, node.coord, threshold, false, negative = kdObject.create()), kdObject.destroy(); + if (negative.verticesLength > 0) kdObjects[negativeEnd++] = negative else negative.destroy(); + } + } + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negativeBegin, negativeEnd); + // Если видна только позитивная + } else if (positiveCulling >= 0) { + // Если есть динамические объекты + if (begin < end) { + // Делаем резерв для KD-объектов + positiveBegin = positiveEnd = kdObjectsLength; + if ((kdObjectsLength = positiveBegin + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + // Разделяем + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами + if (kdObject.numCheckedOccluders < numOccluders && cullKDObject(camera, kdObject)) { + kdObject.destroy(); + continue; + } + min = axisX ? kdObject._boundBox.minX : (axisY ? kdObject._boundBox.minY : kdObject._boundBox.minZ); + max = axisX ? kdObject._boundBox.maxX : (axisY ? kdObject._boundBox.maxY : kdObject._boundBox.maxZ); + if (min >= node.minCoord) kdObjects[positiveEnd++] = kdObject; + else if (max > node.maxCoord) { + // Подрезаем + kdObject.crop(axisX, axisY, node.coord, threshold, true, positive = kdObject.create()), kdObject.destroy(); + if (positive.verticesLength > 0) kdObjects[positiveEnd++] = positive else positive.destroy(); + } + } + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positiveBegin, positiveEnd); + } + } + } else { + // Конечная нода + // Если есть статические объекты, не считая окклюдеры + if (nodeNumNonOccluders > 0) { + // Если есть конфликт + if (nodeNumNonOccluders > 1 || begin < end) { + // Делаем резерв для KD-объектов + nodeBegin = nodeEnd = kdObjectsLength; + if ((kdObjectsLength = nodeBegin + nodeNumNonOccluders + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + // Собираем статики + for (i = 0; i < nodeNumNonOccluders; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + // Создаём KD-объект + if ((kdObject = KDObject.createFrom(staticChild, camera)) != null) kdObjects[nodeEnd++] = kdObject; + } + } + // Собираем динамики + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами + if (kdObject.numCheckedOccluders < numOccluders && cullKDObject(camera, kdObject)) { + kdObject.destroy(); + continue; + } + kdObjects[nodeEnd++] = kdObject; + // Подрезаем массив вершин + kdObject.vertices.length = kdObject.verticesLength; + // Переводим координаты в матрицу камеры + object.cameraMatrix.transformVectors(kdObject.vertices, kdObject.vertices); + } + // Разруливаем конфликт + if (nodeEnd > nodeBegin) KDObject.drawConflict(camera, object, canvas, kdObjects, nodeBegin, nodeEnd, threshold); + } else { + // Если только один статик + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + staticChild.culling = culling; + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Если нет статических объектов + } else { + // Если есть динамические объекты + if (begin < end) { + // Если динамических объектов несколько + if (end - begin > 1) { + // Если есть окклюдеры + if (numOccluders > 0) { + // Делаем резерв для KD-объектов + nodeBegin = nodeEnd = kdObjectsLength; + if ((kdObjectsLength = nodeBegin + nodeNumNonOccluders + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + // Проверка с окклюдерами + if (kdObject.numCheckedOccluders < numOccluders && cullKDObject(camera, kdObject)) { + kdObject.destroy(); + continue; + } + kdObjects[nodeEnd++] = kdObject; + } + // Если остались объекты + if (nodeEnd > nodeBegin) { + // Если оставшихся объектов несколько + if (nodeEnd - nodeBegin > 1) { + drawKDObjects(camera, object, canvas, nodeBegin, nodeEnd); + } else { + kdObject = kdObjects[nodeBegin], kdObjects[nodeBegin] = null; + kdObject.draw(camera, object, canvas), kdObject.destroy(); + } + } + } else { + drawKDObjects(camera, object, canvas, begin, end); + } + } else { + kdObject = kdObjects[begin], kdObjects[begin] = null; + // Проверка с окклюдерами и отрисовка + if (kdObject.numCheckedOccluders == numOccluders || !cullKDObject(camera, kdObject)) kdObject.draw(camera, object, canvas); + kdObject.destroy(); + } + } + } + // Если в ноде есть окклюдеры + if (nodeNumObjects > nodeNumNonOccluders) { + for (i = nodeNumNonOccluders; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + updateOccluders(camera); + } + } + } + + // Отрисовка динамических объектов + /** + * @private + */ + alternativa3d function drawKDObjects(camera:Camera3D, object:Object3D, canvas:Canvas, begin:int, end:int):void { + // Ищем сплит + var i:int, j:int, kdObject:KDObject; + var boundBox:BoundBox, comparedBoundBox:BoundBox; + var infront:Boolean, behind:Boolean; + var coord:Number, coordMin:Number, coordMax:Number; + var axisX:Boolean, axisY:Boolean; + for (i = begin; i < end; i++) { + boundBox = (kdObjects[i] as KDObject)._boundBox; + // Сплиты по оси X + coord = boundBox.minX, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxX <= coordMax) behind = true else if (comparedBoundBox.minX >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = true, axisY = false; + break; + } + coord = boundBox.maxX, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxX <= coordMax) behind = true else if (comparedBoundBox.minX >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = true, axisY = false; + break; + } + // Сплиты по оси Y + coord = boundBox.minY, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxY <= coordMax) behind = true else if (comparedBoundBox.minY >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = false, axisY = true; + break; + } + coord = boundBox.maxY, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxY <= coordMax) behind = true else if (comparedBoundBox.minY >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = false, axisY = true; + break; + } + // Сплиты по оси Z + coord = boundBox.minZ, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxZ <= coordMax) behind = true else if (comparedBoundBox.minZ >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = false, axisY = false; + break; + } + coord = boundBox.maxZ, coordMin = coord - threshold, coordMax = coord + threshold, behind = false, infront = false; + for (j = begin; j < end; j++) if ((comparedBoundBox = (kdObjects[j] as KDObject)._boundBox).maxZ <= coordMax) behind = true else if (comparedBoundBox.minZ >= coordMin) infront = true else break; + if (j == end && behind && infront) { + axisX = false, axisY = false; + break; + } + } + // Если найден сплит + if (i < end) { + // Делаем резерв для разделённых KD-объектов + var negativeBegin:int = kdObjectsLength, negativeEnd:int = negativeBegin, nodeBegin:int = negativeBegin + end - begin, nodeEnd:int = nodeBegin, positiveBegin:int = nodeBegin + end - begin, positiveEnd:int = positiveBegin; + if ((kdObjectsLength = positiveBegin + end - begin) > kdObjectsRealLength) kdObjects.length = kdObjectsRealLength = kdObjectsLength; + // Разделяем KD-объекты + for (i = begin; i < end; i++) { + kdObject = kdObjects[i], kdObjects[i] = null; + var min:Number = axisX ? kdObject._boundBox.minX : (axisY ? kdObject._boundBox.minY : kdObject._boundBox.minZ); + var max:Number = axisX ? kdObject._boundBox.maxX : (axisY ? kdObject._boundBox.maxY : kdObject._boundBox.maxZ); + if (max <= coordMax) { + if (min < coordMin) kdObjects[negativeEnd++] = kdObject else kdObjects[nodeEnd++] = kdObject; + } else kdObjects[positiveEnd++] = kdObject; + } + // Определяем положение камеры + if (axisX && cameraX > coord || axisY && cameraY > coord || !axisX && !axisY && cameraZ > coord) { + // Отрисовка объектов спереди + if (positiveEnd > positiveBegin) { + if (positiveEnd - positiveBegin > 1) { + drawKDObjects(camera, object, canvas, positiveBegin, positiveEnd); + } else { + (kdObject = kdObjects[positiveBegin]).draw(camera, object, canvas), kdObjects[positiveBegin] = null, kdObject.destroy(); + } + } + // Отрисовка объектов в плоскости + for (i = nodeBegin; i < nodeEnd; i++) (kdObject = kdObjects[i]).draw(camera, object, canvas), kdObjects[i] = null, kdObject.destroy(); + // Отрисовка объектов сзади + if (negativeEnd > negativeBegin) { + if (negativeEnd - negativeBegin > 1) { + drawKDObjects(camera, object, canvas, negativeBegin, negativeEnd); + } else { + (kdObject = kdObjects[negativeBegin]).draw(camera, object, canvas), kdObjects[negativeBegin] = null, kdObject.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negativeEnd > negativeBegin) { + if (negativeEnd - negativeBegin > 1) { + drawKDObjects(camera, object, canvas, negativeBegin, negativeEnd); + } else { + (kdObject = kdObjects[negativeBegin]).draw(camera, object, canvas), kdObjects[negativeBegin] = null, kdObject.destroy(); + } + } + // Отрисовка объектов в плоскости + for (i = nodeBegin; i < nodeEnd; i++) (kdObject = kdObjects[i]).draw(camera, object, canvas), kdObjects[i] = null, kdObject.destroy(); + // Отрисовка объектов спереди + if (positiveEnd > positiveBegin) { + if (positiveEnd - positiveBegin > 1) { + drawKDObjects(camera, object, canvas, positiveBegin, positiveEnd); + } else { + (kdObject = kdObjects[positiveBegin]).draw(camera, object, canvas), kdObjects[positiveBegin] = null, kdObject.destroy(); + } + } + } + } else { + // Переводим в камеру + for (i = begin; i < end; i++) { + kdObject = kdObjects[i]; + // Подрезаем массив вершин + kdObject.vertices.length = kdObject.verticesLength; + // Переводим координаты в матрицу камеры + object.cameraMatrix.transformVectors(kdObject.vertices, kdObject.vertices); + } + // Разруливаем конфликт + KDObject.drawConflict(camera, object, canvas, kdObjects, begin, end, threshold); + } + } + + private var splitAxis:int; + private var splitCoord:Number; + private var splitCost:Number; + static private const nodeBoundBoxThreshold:BoundBox = new BoundBox(); + static private const splitCoordsX:Vector. = new Vector.(); + static private const splitCoordsY:Vector. = new Vector.(); + static private const splitCoordsZ:Vector. = new Vector.(); + private function splitNode(node:KDNode):void { + + var object:Object3D, boundBox:BoundBox, i:int, j:int, k:int, c1:Number, c2:Number, coordMin:Number, coordMax:Number, area:Number, areaNegative:Number, areaPositive:Number, numNegative:int, numPositive:int, conflict:Boolean, cost:Number; + var nodeBoundBox:BoundBox = node.boundBox; + + // Подготовка баунда с погрешностями + nodeBoundBoxThreshold.minX = nodeBoundBox.minX + threshold; + nodeBoundBoxThreshold.minY = nodeBoundBox.minY + threshold; + nodeBoundBoxThreshold.minZ = nodeBoundBox.minZ + threshold; + nodeBoundBoxThreshold.maxX = nodeBoundBox.maxX - threshold; + nodeBoundBoxThreshold.maxY = nodeBoundBox.maxY - threshold; + nodeBoundBoxThreshold.maxZ = nodeBoundBox.maxZ - threshold; + var doubleThreshold:Number = threshold + threshold; + + // Собираем опорные координаты + var numSplitCoordsX:int = 0, numSplitCoordsY:int = 0, numSplitCoordsZ:int = 0; + for (i = 0; i < node.numObjects; i++) { + boundBox = node.bounds[i]; + if (boundBox.maxX - boundBox.minX <= doubleThreshold) { + if (boundBox.minX <= nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.minX; + else if (boundBox.maxX >= nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.maxX; + else splitCoordsX[numSplitCoordsX++] = (boundBox.minX + boundBox.maxX)*0.5; + } else { + if (boundBox.minX > nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = boundBox.minX; + if (boundBox.maxX < nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = boundBox.maxX; + } + if (boundBox.maxY - boundBox.minY <= doubleThreshold) { + if (boundBox.minY <= nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.minY; + else if (boundBox.maxY >= nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.maxY; + else splitCoordsY[numSplitCoordsY++] = (boundBox.minY + boundBox.maxY)*0.5; + } else { + if (boundBox.minY > nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = boundBox.minY; + if (boundBox.maxY < nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = boundBox.maxY; + } + if (boundBox.maxZ - boundBox.minZ <= doubleThreshold) { + if (boundBox.minZ <= nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.minZ; + else if (boundBox.maxZ >= nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.maxZ; + else splitCoordsZ[numSplitCoordsZ++] = (boundBox.minZ + boundBox.maxZ)*0.5; + } else { + if (boundBox.minZ > nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.minZ; + if (boundBox.maxZ < nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.maxZ; + } + } + + // Убираем дубликаты координат, ищем наилучший сплит + splitAxis = -1; splitCost = Number.MAX_VALUE; + i = 0; area = (nodeBoundBox.maxY - nodeBoundBox.minY)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsX) { + if (!isNaN(c1 = splitCoordsX[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minX); + areaPositive = area*(nodeBoundBox.maxX - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxX <= coordMax) { + if (boundBox.minX < coordMin) numNegative++; + } else { + if (boundBox.minX >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 0; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsX) if ((c2 = splitCoordsX[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsX[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsY) { + if (!isNaN(c1 = splitCoordsY[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minY); + areaPositive = area*(nodeBoundBox.maxY - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxY <= coordMax) { + if (boundBox.minY < coordMin) numNegative++; + } else { + if (boundBox.minY >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 1; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsY) if ((c2 = splitCoordsY[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsY[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxY - nodeBoundBox.minY); + while (i < numSplitCoordsZ) { + if (!isNaN(c1 = splitCoordsZ[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minZ); + areaPositive = area*(nodeBoundBox.maxZ - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxZ <= coordMax) { + if (boundBox.minZ < coordMin) numNegative++; + } else { + if (boundBox.minZ >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 2; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsZ) if ((c2 = splitCoordsZ[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsZ[j] = NaN; + } + } + + // Если сплит не найден, выходим + if (splitAxis < 0) { + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + return; + } + + // Разделяем ноду + var axisX:Boolean = splitAxis == 0, axisY:Boolean = splitAxis == 1; + node.axis = splitAxis; + node.coord = splitCoord; + node.minCoord = coordMin = splitCoord - threshold; + node.maxCoord = coordMax = splitCoord + threshold; + + // Создаём дочерние ноды + node.negative = new KDNode(); + node.positive = new KDNode(); + node.negative.boundBox = nodeBoundBox.clone(); + node.positive.boundBox = nodeBoundBox.clone(); + node.negative.numObjects = 0; + node.positive.numObjects = 0; + if (axisX) node.negative.boundBox.maxX = node.positive.boundBox.minX = splitCoord; + else if (axisY) node.negative.boundBox.maxY = node.positive.boundBox.minY = splitCoord; + else node.negative.boundBox.maxZ = node.positive.boundBox.minZ = splitCoord; + + // Распределяем объекты по дочерним нодам + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + boundBox = node.bounds[i]; + var min:Number = axisX ? boundBox.minX : (axisY ? boundBox.minY : boundBox.minZ); + var max:Number = axisX ? boundBox.maxX : (axisY ? boundBox.maxY : boundBox.maxZ); + if (max <= coordMax) { + if (min < coordMin) { + // Объект в негативной стороне + if (node.negative.objects == null) node.negative.objects = new Vector.(), node.negative.bounds = new Vector.(); + node.negative.objects[node.negative.numObjects] = object, node.negative.bounds[node.negative.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Остаётся в ноде + } + } else { + if (min >= coordMin) { + // Объект в положительной стороне + if (node.positive.objects == null) node.positive.objects = new Vector.(), node.positive.bounds = new Vector.(); + node.positive.objects[node.positive.numObjects] = object, node.positive.bounds[node.positive.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Распилился + } + } + } + + // Очистка списка объектов + for (i = 0, j = 0; i < node.numObjects; i++) if (node.objects[i] != null) node.objects[j] = node.objects[i], node.bounds[j++] = node.bounds[i]; + if (j > 0) { + node.numObjects = node.objects.length = node.bounds.length = j; + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + } else { + node.numObjects = node.numNonOccluders = 0, node.objects = null, node.bounds = null; + } + + // Разделение дочерних нод + if (node.negative.objects != null) splitNode(node.negative); + if (node.positive.objects != null) splitNode(node.positive); + } + + public function addStaticChild(child:Object3D):void { + staticChildren[_numStaticChildren++] = child; + child._parent = this; + } + + public function removeStaticChild(child:Object3D):void { + var i:int = staticChildren.indexOf(child); + if (i < 0) throw new ArgumentError(); + _numStaticChildren--; + for (; i < _numStaticChildren; i++) staticChildren[i] = staticChildren[int(i + 1)]; + staticChildren.length = _numStaticChildren; + child._parent = null; + } + + public function getStaticChildAt(index:uint):Object3D { + return staticChildren[index]; + } + + public function get numStaticChildren():uint { + return _numStaticChildren; + } + + public function addDynamicChild(child:Object3D):void { + dynamicChildren[_numDynamicChildren++] = child; + child._parent = this; + } + + public function removeDynamicChild(child:Object3D):void { + var i:int = dynamicChildren.indexOf(child); + if (i < 0) throw new ArgumentError(); + _numDynamicChildren--; + for (; i < _numDynamicChildren; i++) dynamicChildren[i] = dynamicChildren[int(i + 1)]; + dynamicChildren.length = _numDynamicChildren; + child._parent = null; + } + + public function getDynamicChildAt(index:uint):Object3D { + return dynamicChildren[index]; + } + + public function get numDynamicChildren():uint { + return _numDynamicChildren; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/containers/SkyBox.as b/Alternativa3D7/7.1/alternativa/engine3d/containers/SkyBox.as new file mode 100644 index 0000000..3263aaa --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/containers/SkyBox.as @@ -0,0 +1,156 @@ +package alternativa.engine3d.containers { + import flash.display.BitmapData; + import __AS3__.vec.Vector; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.objects.Mesh; + + + public class SkyBox extends Object3DContainer { + + private var backPlane:Mesh; + private var frontPlane:Mesh; + private var topPlane:Mesh; + private var bottomPlane:Mesh; + private var leftPlane:Mesh; + private var rightPlane:Mesh; + + public function SkyBox():void { + backPlane = new Mesh(); + frontPlane = new Mesh(); + topPlane = new Mesh(); + bottomPlane = new Mesh(); + leftPlane = new Mesh(); + rightPlane = new Mesh(); + + backPlane.clipping = 2; + frontPlane.clipping = 2; + topPlane.clipping = 2; + bottomPlane.clipping = 2; + leftPlane.clipping = 2; + rightPlane.clipping = 2; + + backPlane.perspectiveCorrection = true; + frontPlane.perspectiveCorrection = true; + topPlane.perspectiveCorrection = true; + bottomPlane.perspectiveCorrection = true; + leftPlane.perspectiveCorrection = true; + rightPlane.perspectiveCorrection = true; + + const size:Number = 1000; + + backPlane.vertices = Vector.([ + -size, -size, -size, + -size, -size, size, + size, -size, size, + size, -size, -size, + ]); + frontPlane.vertices = Vector.([ + size, size, -size, + size, size, size, + -size, size, size, + -size, size, -size, + ]); + topPlane.vertices = Vector.([ + size, size, size, + size, -size, size, + -size, -size, size, + -size, size, size, + ]); + bottomPlane.vertices = Vector.([ + size, -size, -size, + size, size, -size, + -size, size, -size, + -size, -size, -size, + ]); + leftPlane.vertices = Vector.([ + -size, size, -size, + -size, size, size, + -size, -size, size, + -size, -size, -size, + ]); + rightPlane.vertices = Vector.([ + size, -size, -size, + size, -size, size, + size, size, size, + size, size, -size, + ]); + + var uvts:Vector. = Vector.([1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]); + backPlane.uvts = uvts; + frontPlane.uvts = uvts; + topPlane.uvts = uvts; + bottomPlane.uvts = uvts; + leftPlane.uvts = uvts; + rightPlane.uvts = uvts; + + var uvs:Vector. = Vector.([1, 1, 1, 0, 0, 0, 0, 1]); + backPlane.uvs = uvs; + frontPlane.uvs = uvs; + topPlane.uvs = uvs; + bottomPlane.uvs = uvs; + leftPlane.uvs = uvs; + rightPlane.uvs = uvs; + + var indices:Vector. = Vector.([4, 0, 1, 2, 3]); + backPlane.indices = indices; + frontPlane.indices = indices; + topPlane.indices = indices; + bottomPlane.indices = indices; + leftPlane.indices = indices; + rightPlane.indices = indices; + + backPlane.numVertices = 4; + frontPlane.numVertices = 4; + topPlane.numVertices = 4; + bottomPlane.numVertices = 4; + leftPlane.numVertices = 4; + rightPlane.numVertices = 4; + + backPlane.numFaces = 1; + frontPlane.numFaces = 1; + topPlane.numFaces = 1; + bottomPlane.numFaces = 1; + leftPlane.numFaces = 1; + rightPlane.numFaces = 1; + + backPlane.poly = true; + frontPlane.poly = true; + topPlane.poly = true; + bottomPlane.poly = true; + leftPlane.poly = true; + rightPlane.poly = true; + + addChild(backPlane); + addChild(frontPlane); + addChild(topPlane); + addChild(bottomPlane); + addChild(leftPlane); + addChild(rightPlane); + } + + public function set backTexture(value:BitmapData):void { + backPlane.texture = value; + } + + public function set frontTexture(value:BitmapData):void { + frontPlane.texture = value; + } + + public function set topTexture(value:BitmapData):void { + topPlane.texture = value; + } + + public function set bottomTexture(value:BitmapData):void { + bottomPlane.texture = value; + } + + public function set leftTexture(value:BitmapData):void { + leftPlane.texture = value; + } + + public function set rightTexture(value:BitmapData):void { + rightPlane.texture = value; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/containers/SplitContainer.as b/Alternativa3D7/7.1/alternativa/engine3d/containers/SplitContainer.as new file mode 100644 index 0000000..125b666 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/containers/SplitContainer.as @@ -0,0 +1,38 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SplitContainer extends Object3DContainer { + + public var splitPlane:Vector3D = new Vector3D(0, 0, 1, 0); + static private const invertCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraPosition:Vector. = Vector.([0, 0, 0]); + static private const invertCameraPosition:Vector. = new Vector.(3, true); + + override protected function calculateOrder(camera:Camera3D, object:Object3D):void { + invertCameraMatrix.identity(); + invertCameraMatrix.prepend(object.cameraMatrix); + invertCameraMatrix.invert(); + invertCameraMatrix.transformVectors(cameraPosition, invertCameraPosition); + if (invertCameraPosition[0]*splitPlane.x + invertCameraPosition[1]*splitPlane.y + invertCameraPosition[2]*splitPlane.z < splitPlane.w) { + var num:uint = numVisibleChildren >> 1; + for (var i:uint = 0; i < num; i++) { + var child:Object3D = visibleChildren[i]; + visibleChildren[i] = visibleChildren[numVisibleChildren - 1 - i]; + visibleChildren[numVisibleChildren - 1 - i] = child; + } + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/controllers/SimpleObjectController.as b/Alternativa3D7/7.1/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..55f6195 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,454 @@ +package alternativa.engine3d.controllers { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + + import flash.display.InteractiveObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.ui.Keyboard; + import flash.utils.getTimer; + + /** + * + */ + public class SimpleObjectController { + + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + + public var speed:Number; + public var speedMultiplier:Number; + public var mouseSensitivity:Number; + public var maxPitch:Number = Number.MAX_VALUE; + public var minPitch:Number = -Number.MAX_VALUE; + + private var eventSource:InteractiveObject; + private var _object:Object3D; + + private var _up:Boolean; + private var _down:Boolean; + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _accelerate:Boolean; + + private var displacement:Vector3D = new Vector3D(); + private var mousePoint:Point = new Point(); + private var mouseLook:Boolean; + private var objectTransform:Vector.; + + private var time:int; + + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + private var actionBindings:Object = {}; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Object = {}; + + /** + * + * @param eventSource источник событий для контроллера + * @param speed скорость поступательного перемещения объекта + * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши + */ + public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { + this.eventSource = eventSource; + this.object = object; + this.speed = speed; + this.speedMultiplier = speedMultiplier; + this.mouseSensitivity = mouseSensitivity; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_ACCELERATE] = accelerate; + + setDefaultBindings(); + + enable(); + } + + /** + * Активирует контроллер. + */ + public function enable():void { + eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Дективирует контроллер. + */ + public function disable():void { + eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + stopMouseLook(); + } + + /** + * + */ + private function onMouseDown(e:MouseEvent):void { + startMouseLook(); + } + + /** + * + */ + private function onMouseUp(e:MouseEvent):void { + stopMouseLook(); + } + + /** + * Включает режим взгляда мышью. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Отключает режим взгляда мышью. + */ + public function stopMouseLook():void { + mouseLook = false; + } + + /** + * + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + + /** + * Управляемый объект. + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * + */ + public function get object():Object3D { + return _object; + } + + /** + * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.matrix.decompose(); + } + + /** + * Вычисляет новое положение объекта, используя внутренний счётчик времени. + */ + public function update():void { + if (_object == null) return; + + var frameTime:Number = time; + time = getTimer(); + frameTime = 0.001*(time - frameTime); + if (frameTime > 0.1) frameTime = 0.1; + + var moved:Boolean = false; + + if (mouseLook) { + var dx:Number = eventSource.mouseX - mousePoint.x; + var dy:Number = eventSource.mouseY - mousePoint.y; + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + var v:Vector3D = objectTransform[1]; + v.x -= dy*Math.PI/180*mouseSensitivity; + if (v.x > maxPitch) v.x = maxPitch; + if (v.x < minPitch) v.x = minPitch; + v.z -= dx*Math.PI/180*mouseSensitivity; + moved = true; + } + + displacement.x = _right ? 1 : (_left ? -1 : 0); + displacement.y = _forward ? 1 : (_back ? -1 : 0); + displacement.z = _up ? 1 : (_down ? -1 : 0); + if (displacement.lengthSquared > 0) { + if (_object is Camera3D) { + var tmp:Number = displacement.z; + displacement.z = displacement.y; + displacement.y = -tmp; + } + deltaTransformVector(displacement); + if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); + else displacement.scaleBy(speed*frameTime/displacement.length); + (objectTransform[0] as Vector3D).incrementBy(displacement); + moved = true; + } + + if (moved) _object.matrix.recompose(objectTransform); + } + + /** + * + * @param pos + */ + public function setObjectPos(pos:Vector3D):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = pos.x; + v.y = pos.y; + v.z = pos.z; + } + } + + /** + * + * @param pos + */ + public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = x; + v.y = y; + v.z = z; + } + } + + /** + * + * @param point + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * + * @param x + * @param y + * @param z + */ + public function lookAtXYZ(x:Number, y:Number, z:Number):void { + if (_object == null) return; + var v:Vector3D = objectTransform[0]; + var dx:Number = x - v.x; + var dy:Number = y - v.y; + var dz:Number = z - v.z; + v = objectTransform[1]; + v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); + if (_object is Camera3D) v.x -= 0.5*Math.PI; + v.y = 0; + v.z = -Math.atan2(dx, dy); + _object.matrix.recompose(objectTransform); + } + + private var _vin:Vector. = new Vector.(3); + private var _vout:Vector. = new Vector.(3); + + private function deltaTransformVector(v:Vector3D):void { + _vin[0] = v.x; + _vin[1] = v.y; + _vin[2] = v.z; + _object.matrix.transformVectors(_vin, _vout); + var c:Vector3D = objectTransform[0]; + v.x = _vout[0] - c.x; + v.y = _vout[1] - c.y; + v.z = _vout[2] - c.z; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:* in keyBindings) delete keyBindings[key]; + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + bindKey(87, ACTION_FORWARD); + bindKey(83, ACTION_BACK); + bindKey(65, ACTION_LEFT); + bindKey(68, ACTION_RIGHT); + bindKey(69, ACTION_UP); + bindKey(67, ACTION_DOWN); + bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); + + bindKey(Keyboard.UP, ACTION_FORWARD); + bindKey(Keyboard.DOWN, ACTION_BACK); + bindKey(Keyboard.LEFT, ACTION_LEFT); + bindKey(Keyboard.RIGHT, ACTION_RIGHT); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/BSPNode.as b/Alternativa3D7/7.1/alternativa/engine3d/core/BSPNode.as new file mode 100644 index 0000000..0ff9aaf --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/BSPNode.as @@ -0,0 +1,83 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import __AS3__.vec.Vector; + + use namespace alternativa3d; + + public class BSPNode { + + static private var collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + // Нормаль и смещение + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + // Дочерние ноды + public var negative:BSPNode; + public var positive:BSPNode; + + // Треугольники + public var triangles:Vector. = new Vector.(); + public var trianglesLength:uint = 0; + + // Полигоны + public var polygons:Vector. = new Vector.(); + public var polygonsLength:uint = 0; + + // Флаг расположения камеры + public var cameraInfront:Boolean; + + // Предыдущая нода в стеке (для раскрытия рекурсии) + //public var prev:BSPNode; + + // Для КД + public var ax:Number; + public var ay:Number; + public var az:Number; + public var abx:Number; + public var aby:Number; + public var abz:Number; + public var acx:Number; + public var acy:Number; + public var acz:Number; + + static alternativa3d function create():BSPNode { + return (collectorLength > 0) ? collector[--collectorLength] : new BSPNode(); + } + + public function create():BSPNode { + return (collectorLength > 0) ? collector[--collectorLength] : new BSPNode(); + } + + public function destroy():void { + if (negative != null) { + negative.destroy(); + negative = null; + } + if (positive != null) { + positive.destroy(); + positive = null; + } + trianglesLength = 0; + polygonsLength = 0; + collector[collectorLength++] = this; + } + + // Добавление фрагмента в ноду + public function addFragment(fragment:Vector., begin:int, end:int):void { + polygons[polygonsLength++] = end - begin; + var i:int = begin, a:int = polygons[polygonsLength++] = fragment[i++], b:int = polygons[polygonsLength++] = fragment[i++], c:int; + while (i < end) { + triangles[trianglesLength++] = a; + triangles[trianglesLength++] = b; + triangles[trianglesLength++] = c = polygons[polygonsLength++] = fragment[i++]; + b = c; + } + } + + } +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/BackfaceCulling.as b/Alternativa3D7/7.1/alternativa/engine3d/core/BackfaceCulling.as new file mode 100644 index 0000000..9ac2b68 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/BackfaceCulling.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + public class BackfaceCulling { + + /** + * Грань рисуется с обеих сторон. + */ + static public const NONE:int = 0; + /** + * Отсечение нативными механизмами на этапе отрисовки треугольников. + */ + static public const NATIVE:int = 1; + /** + * Отсечение по предрасчитанным нормалям. + */ + static public const CALCULATED_NORMALS:int = 2; + /** + * Отсечение по динамически расчитываемым нормалям. + */ + static public const DYNAMIC_NORMALS:int = 3; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/BoundBox.as b/Alternativa3D7/7.1/alternativa/engine3d/core/BoundBox.as new file mode 100644 index 0000000..9b6e256 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/BoundBox.as @@ -0,0 +1,67 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + import flash.geom.Matrix3D; + + public class BoundBox { + + public var minX:Number = Number.MAX_VALUE; + public var minY:Number = Number.MAX_VALUE; + public var minZ:Number = Number.MAX_VALUE; + public var maxX:Number = -Number.MAX_VALUE; + public var maxY:Number = -Number.MAX_VALUE; + public var maxZ:Number = -Number.MAX_VALUE; + + public function setSize(minX:Number, minY:Number, minZ:Number, maxX:Number, maxY:Number, maxZ:Number):void { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public function addBoundBox(boundBox:BoundBox):void { + minX = (boundBox.minX < minX) ? boundBox.minX : minX; + minY = (boundBox.minY < minY) ? boundBox.minY : minY; + minZ = (boundBox.minZ < minZ) ? boundBox.minZ : minZ; + maxX = (boundBox.maxX > maxX) ? boundBox.maxX : maxX; + maxY = (boundBox.maxY > maxY) ? boundBox.maxY : maxY; + maxZ = (boundBox.maxZ > maxZ) ? boundBox.maxZ : maxZ; + } + + public function addPoint(x:Number, y:Number, z:Number):void { + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + if (z < minZ) minZ = z; + if (z > maxZ) maxZ = z; + } + + public function infinity():void { + minX = minY = minZ = Number.MAX_VALUE; + maxX = maxY = maxZ = -Number.MAX_VALUE; + } + + public function copyFrom(boundBox:BoundBox):void { + minX = boundBox.minX; + minY = boundBox.minY; + minZ = boundBox.minZ; + maxX = boundBox.maxX; + maxY = boundBox.maxY; + maxZ = boundBox.maxZ; + } + + public function clone():BoundBox { + var clone:BoundBox = new BoundBox(); + clone.copyFrom(this); + return clone; + } + + public function toString():String { + return "BoundBox [" + minX.toFixed(2) + ", " + minY.toFixed(2) + ", " + minZ.toFixed(2) + " - " + maxX.toFixed(2) + ", " + maxY.toFixed(2) + ", " + maxZ.toFixed(2) + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Camera3D.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..b28949c --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,479 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.events.Event; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.System; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.getTimer; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + + use namespace alternativa3d; + + public class Camera3D extends Object3D { + + /** + * Вьюпорт камеры. + */ + public var canvas:Canvas; + public var fov:Number = Math.PI/2; + /** + * Ширина вьюпорта. + */ + public var width:Number = 500; + /** + * Высота вьюпорта. + */ + public var height:Number = 500; + public var farClipping:Number = 5000; + public var farFalloff:Number = 4000; + public var nearClipping:Number = 50; + + // Матрица проецирования + /** + * @private + */ + alternativa3d var projectionMatrixData:Vector. = new Vector.(16, true); + /** + * @private + */ + alternativa3d var projectionMatrix:Matrix3D; + // Параметры перспективы + /** + * @private + */ + alternativa3d var viewSize:Number; + /** + * @private + */ + alternativa3d var viewSizeX:Number; + /** + * @private + */ + alternativa3d var viewSizeY:Number; + /** + * @private + */ + alternativa3d var perspectiveScaleX:Number; + /** + * @private + */ + alternativa3d var perspectiveScaleY:Number; + /** + * @private + */ + alternativa3d var invertPerspectiveScaleX:Number; + /** + * @private + */ + alternativa3d var invertPerspectiveScaleY:Number; + /** + * @private + */ + alternativa3d var focalLength:Number; + // Перекрытия + /** + * @private + */ + alternativa3d var occlusionPlanes:Vector.> = new Vector.>(); + /** + * @private + */ + alternativa3d var occlusionEdges:Vector.> = new Vector.>(); + /** + * @private + */ + alternativa3d var numOccluders:int; + /** + * @private + */ + alternativa3d var occludedAll:Boolean; + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return false; + } + + /** + * Отрисовка иерархии объектов, в которой находится камера. + * Перед render(), если менялись параметры камеры, нужно вызвать updateProjection(). + */ + public function render():void { + // Расчёт матрицы перевода из рута в камеру + cameraMatrix.identity(); + var object:Object3D = this; + while (object._parent != null) { + cameraMatrix.append(object.matrix); + object = object._parent; + } + cameraMatrix.invert(); + cameraMatrix.appendScale(perspectiveScaleX, perspectiveScaleY, 1); + + numOccluders = 0; + occludedAll = false; + + numTriangles = 0; + + // Отрисовка + if (object.visible && object.canDraw) { + object.cameraMatrix.identity(); + object.cameraMatrix.prepend(cameraMatrix); + object.cameraMatrix.prepend(object.matrix); + if (object.cullingInCamera(this, 63) >= 0) { + // Отрисовка объекта + canvas.numDraws = 0; + if (debugMode) object.debug(this, object, canvas); + object.draw(this, object, canvas); + // Если не нарисовалось, зачищаем рутовый канвас + if (canvas.numDraws == 0) canvas.removeChildren(0); + } else { + // Если отсеклось, зачищаем рутовый канвас + canvas.removeChildren(0); + } + } + } + + /** + * После изменения параметров fov, width, height нужно вызвать этот метод. + */ + public function updateProjection():void { + // Расчёт параметров перспективы + viewSize = Math.sqrt(width*width + height*height)*0.5; + focalLength = viewSize/Math.tan(fov*0.5); + viewSizeX = width*0.5; + viewSizeY = height*0.5; + perspectiveScaleX = focalLength/viewSizeX; + perspectiveScaleY = focalLength/viewSizeY; + invertPerspectiveScaleX = viewSizeX/focalLength; + invertPerspectiveScaleY = viewSizeY/focalLength; + + // Подготовка матрицы проецирования + projectionMatrixData[0] = viewSizeX; + projectionMatrixData[5] = viewSizeY; + projectionMatrixData[10] = 1; + projectionMatrixData[11] = 1; + projectionMatrix = new Matrix3D(projectionMatrixData); + } + + /* + // Occlusion culling + if (numOccluders > 0) { + for (var n:int = 0; n < numOccluders; n++) { + var occluder:Vector. = occluders[n]; + var occlude:Boolean = true; + occlude: for (var j:int = 0, length:int = occluder.length; j < length;) { + var x:Number = occluder[j++], y:Number = occluder[j++], z:Number = occluder[j++] + for (i = 0; i <= 21; i += 3) { + if (boundBoxVertices[i]*x + boundBoxVertices[int(i + 1)]*y + boundBoxVertices[int(i + 2)]*z > 0) { + occlude = false; + break occlude; + } + } + } + if (occlude) return -1; + } + } + */ + + private static var _tmpv:Vector. = new Vector.(3); + + /** + * @param v + * @param result + */ + public function projectGlobal(v:Vector3D, result:Vector3D):void { + _tmpv[0] = v.x; _tmpv[1] = v.y; _tmpv[2] = v.z; + cameraMatrix.transformVectors(_tmpv, _tmpv); + projectionMatrix.transformVectors(_tmpv, _tmpv); + result.z = _tmpv[2]; + result.x = _tmpv[0]/result.z; + result.y = _tmpv[1]/result.z; + } + + // DEBUG + + // Режим отладки + public var debugMode:Boolean = false; + + // Список объектов дебага + private var debugSet:Object = new Object(); + + // Добавить в дебаг + public function addToDebug(debug:int, ... rest):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + for (var i:int = 0; i < rest.length;) debugSet[debug][rest[i++]] = true; + } + + // Убрать из дебага + public function removeFromDebug(debug:int, ... rest):void { + if (debugSet[debug]) { + for (var i:int = 0; i < rest.length;) delete debugSet[debug][rest[i++]]; + for (var key:* in debugSet[debug]); + if (!key) delete debugSet[debug]; + } + } + + // Проверка, находится ли объект или один из классов, от которых он нследован, в дебаге + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 256; debug = debug << 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + public var diagram:Sprite = createDiagram(); + + private var fpsTextField:TextField; + private var memoryTextField:TextField; + private var trianglesTextField:TextField; + private var timerTextField:TextField; + private var graph:Bitmap; + private var rect:Rectangle; + + private var _diagramAlign:String = "TR"; + private var _diagramHorizontalMargin:Number = 2; + private var _diagramVerticalMargin:Number = 2; + + public var fpsUpdatePeriod:int = 10; + private var fpsUpdateCounter:int; + private var previousFrameTime:int; + private var previousPeriodTime:int; + + private var maxMemory:int; + alternativa3d var numTriangles:int; + + public var timerUpdatePeriod:int = 10; + private var timerUpdateCounter:int; + private var timeSum:int; + private var timeCount:int; + private var timer:int; + + private function createDiagram():Sprite { + var diagram:Sprite = new Sprite(); + diagram.mouseEnabled = false; + diagram.mouseChildren = false; + // Инициализация диаграммы + diagram.addEventListener(Event.ADDED_TO_STAGE, function():void { + // FPS + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.LEFT; + fpsTextField.text = "FPS: " + Number(diagram.stage.frameRate).toFixed(2); + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + diagram.addChild(fpsTextField); + // Память + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.LEFT; + memoryTextField.text = "MEM: " + bytesToString(System.totalMemory); + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 4; + diagram.addChild(memoryTextField); + // Треугольники + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.LEFT; + trianglesTextField.text = "TRI: " + 0; + trianglesTextField.selectable = false; + trianglesTextField.x = -2; + trianglesTextField.y = 13; + diagram.addChild(trianglesTextField); + // Время выполнения метода + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.LEFT; + timerTextField.text = "TMR:"; + timerTextField.selectable = false; + timerTextField.x = -2; + timerTextField.y = 13 + 9; + diagram.addChild(timerTextField); + // График + graph = new Bitmap(new BitmapData(60, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40) + graph.x = 0; + graph.y = 27 + 9; + diagram.addChild(graph); + // Сброс параметров + previousFrameTime = previousPeriodTime = getTimer(); + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + // Подписка + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Деинициализация диаграммы + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void { + // Обнуление + diagram.removeChild(fpsTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(graph); + fpsTextField = null; + memoryTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Отписка + diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram); + diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram); + }); + return diagram; + } + + private function resizeDiagram(e:Event = null):void { + if (diagram.stage != null) { + var coord:Point = diagram.parent.globalToLocal(new Point()); + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) { + diagram.x = Math.round(coord.x + _diagramHorizontalMargin); + } + if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth/2 - graph.width/2); + } + if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width); + } + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) { + diagram.y = Math.round(coord.y + _diagramVerticalMargin); + } + if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight/2 - (graph.y + graph.height)/2); + } + if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height); + } + } + } + + private function updateDiagram(e:Event):void { + var fps:Number; + var mod:int; + var time:int = getTimer(); + var stageFrameRate:int = diagram.stage.frameRate; + + // FPS текст + if (++fpsUpdateCounter == fpsUpdatePeriod) { + fps = 1000*fpsUpdatePeriod/(time - previousPeriodTime); + if (fps > stageFrameRate) fps = stageFrameRate; + mod = fps*100 % 100; + fpsTextField.text = "FPS: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS график + fps = 1000/(time - previousFrameTime); + if (fps > stageFrameRate) fps = stageFrameRate; + graph.bitmapData.scroll(1, 0); + graph.bitmapData.fillRect(rect, 0x20FFFFFF); + graph.bitmapData.setPixel32(0, 40*(1 - fps/stageFrameRate), 0xFFCCCCCC); + previousFrameTime = time; + + // Память текст + var memory:int = System.totalMemory; + memoryTextField.text = "MEM: " + bytesToString(memory); + // Память график + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00); + + // Треугольники текст + trianglesTextField.text = "TRI: " + numTriangles; + + // Время текст + if (++timerUpdateCounter == timerUpdatePeriod) { + if (timeCount > 0) { + fps = timeSum/timeCount; + mod = fps*100 % 100; + timerTextField.text = "TMR: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = "TMR:"; + } + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + } + } + + public function startTimer():void { + timer = getTimer(); + } + + public function stopTimer():void { + timeSum += getTimer() - timer; + timeCount++; + } + + public function get diagramAlign():String { + return _diagramAlign; + } + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + public function get diagramVerticalMargin():Number { + return _diagramVerticalMargin; + } + public function set diagramVerticalMargin(value:Number):void { + _diagramVerticalMargin = value; + resizeDiagram(); + } + + private function bytesToString(bytes:int):String { + if (bytes < 1024) return bytes + "b"; + else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb"; + else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb"; + else if (bytes < 1048576) return (bytes >> 10) + "kb"; + else if (bytes < 10485760) return (bytes/1048576).toFixed(2) + "mb"; + else if (bytes < 104857600) return (bytes/1048576).toFixed(1) + "mb"; + else return (bytes >> 20) + "mb"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Canvas.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Canvas.as new file mode 100644 index 0000000..29366ca --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Canvas.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.ColorTransform; + import __AS3__.vec.Vector; + import flash.display.DisplayObject; + + use namespace alternativa3d; + + public class Canvas extends Sprite { + + static private const defaultColorTransform:ColorTransform = new ColorTransform(); + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + /** + * @private + */ + alternativa3d var gfx:Graphics = graphics; + + private var modifiedGraphics:Boolean; + private var modifiedAlpha:Boolean; + private var modifiedBlendMode:Boolean; + private var modifiedColorTransform:Boolean; + private var modifiedFilters:Boolean; + + /** + * @private + */ + alternativa3d var _numChildren:int = 0; + /** + * @private + */ + alternativa3d var numDraws:int = 0; + + public function Canvas() { + mouseEnabled = false; + mouseChildren = false; + } + + /** + * @private + */ + alternativa3d function getChildCanvas(useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas, displayObject:DisplayObject; + // Зачистка не канвасов + while (_numChildren > numDraws && !((displayObject = getChildAt(_numChildren - 1 - numDraws)) is Canvas)) removeChild(displayObject), _numChildren--; + // Получение канваса + if (_numChildren > numDraws++) canvas = displayObject as Canvas, canvas.gfx.clear() else canvas = (collectorLength > 0) ? collector[--collectorLength] : new Canvas(), addChildAt(canvas, 0), _numChildren++; + // Пометка о том, что в graphics будет что-то нарисовано + canvas.modifiedGraphics = useGraphics; + // Если не нужны дочерние канвасы + if (canvas._numChildren > 0 && !useChildren) canvas.removeChildren(0); + // Установка свойств + if (alpha != 1) canvas.alpha = alpha, canvas.modifiedAlpha = true else if (canvas.modifiedAlpha) canvas.alpha = 1, canvas.modifiedAlpha = false; + if (blendMode != "normal") canvas.blendMode = blendMode, canvas.modifiedBlendMode = true else if (canvas.modifiedBlendMode) canvas.blendMode = "normal", canvas.modifiedBlendMode = false; + if (colorTransform != null) colorTransform.alphaMultiplier = alpha, canvas.transform.colorTransform = colorTransform, canvas.modifiedColorTransform = true else if (canvas.modifiedColorTransform) defaultColorTransform.alphaMultiplier = alpha, canvas.transform.colorTransform = defaultColorTransform, canvas.modifiedColorTransform = false; + if (filters != null) canvas.filters = filters, canvas.modifiedFilters = true else if (canvas.modifiedFilters) canvas.filters = null, canvas.modifiedFilters = false; + return canvas; + } + + /** + * @private + */ + alternativa3d function removeChildren(keep:int):void { + for (var canvas:Canvas; _numChildren > keep; _numChildren--) { + if ((canvas = removeChildAt(0) as Canvas) != null) { + if (canvas.modifiedGraphics) canvas.gfx.clear(); + if (canvas._numChildren > 0) canvas.removeChildren(0); + collector[collectorLength++] = canvas; + } + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Clipping.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Clipping.as new file mode 100644 index 0000000..75e06f8 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Clipping.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Clipping { + + /** + * Объект отсекается целиком, если он полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const OBJECT_CULLING:int = 0; + /** + * Грань отсекается целиком, если она полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const FACE_CULLING:int = 1; + /** + * Грань подрезается пирамидой видимости камеры. + */ + static public const FACE_CLIPPING:int = 2; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Debug.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..7701b1a --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Debug.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class Debug { + + static public const NAMES:int = 1; + static public const AXES:int = 2; + static public const CENTERS:int = 4; + static public const BOUNDS:int = 8; + + static public const EDGES:int = 16; + static public const VERTICES:int = 32; + static public const NORMALS:int = 64; + + static public const NODES:int = 128; + static public const SPLITS:int = 256; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/KDNode.as b/Alternativa3D7/7.1/alternativa/engine3d/core/KDNode.as new file mode 100644 index 0000000..de8775e --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/KDNode.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + public final class KDNode { + + public var axis:int; // 0 - x, 1 - y, 2 - z + public var coord:Number; + public var minCoord:Number; + public var maxCoord:Number; + + public var positive:KDNode; + public var negative:KDNode; + + public var boundBox:BoundBox; + + public var objects:Vector.; + public var bounds:Vector.; + + public var numObjects:int = 0; + public var numNonOccluders:int = 0; + } +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/MipMap.as b/Alternativa3D7/7.1/alternativa/engine3d/core/MipMap.as new file mode 100644 index 0000000..4d574c7 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/MipMap.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Объект, представляющий текстуру в виде последовательности её уменьшенных копий. + * Каждая следующая в два раза меньше предыдущей. Последняя имеет размер 1х1 пиксел. + * Чем дальше от камеры отрисовываемый объект, тем меньшая текстура выбирается. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class MipMap { + + /** + * Мип-текстуры + */ + public var textures:Vector. = new Vector.(); + /** + * Количество мип-текстур + */ + public var num:int; + /** + * Отношение размера пиксела текстуры к единице измерения трёхмерного пространства + */ + public var resolution:Number; + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + static private const point:Point = new Point(); + static private const matrix:Matrix = new Matrix(); + static private const rect:Rectangle = new Rectangle(); + public function MipMap(texture:BitmapData, resolution:Number = 1) { + var bmp:BitmapData = new BitmapData(texture.width, texture.height, texture.transparent); + var current:BitmapData = textures[num++] = texture; + filter.preserveAlpha = !texture.transparent; + var w:Number = rect.width = texture.width, h:Number = rect.height = texture.height; + while (w > 1 && h > 1 && rect.width > 1 && rect.height > 1) { + bmp.applyFilter(current, rect, point, filter); + rect.width = w >> 1; + rect.height = h >> 1; + matrix.a = rect.width/w; + matrix.d = rect.height/h; + w *= 0.5; + h *= 0.5; + current = new BitmapData(rect.width, rect.height, texture.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + textures[num++] = current; + } + bmp.dispose(); + this.resolution = resolution; + } + + /** + * Получение мип-уровня в зависимости от удалённости объекта от камеры + * @param distance Z-координата объекта в пространстве камеры + * @param camera Камера + * @return Индекс в списке мип-текстур textures + */ + public function getLevel(distance:Number, camera:Camera3D):int { + var level:int = Math.log(distance/(camera.focalLength*resolution))*1.442695040888963387; + if (level < 0) return 0; else if (level >= num) return num - 1; + return level; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/MipMapping.as b/Alternativa3D7/7.1/alternativa/engine3d/core/MipMapping.as new file mode 100644 index 0000000..375b596 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/MipMapping.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class MipMapping { + + /** + * Нет мипмаппинга. + */ + static public const NONE:int = 0; + /** + * Мипмаппинг по удалённости объекта от камеры. + */ + static public const PER_OBJECT:int = 1; + + //static public const PER_POLYGON:int = 2; + //static public const PER_PIXEL:int = 3; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Object3D.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..56a2655 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,276 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import flash.utils.Dictionary; + import flash.utils.getQualifiedClassName; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedSuperclassName; + + use namespace alternativa3d; + + /** + * Базовый трёхмерный объект + */ + public class Object3D { + + public var name:String; + /** + * Матрица трансформации. Управлять трансформацией объекта можно только через это свойство + * путём назначения новой матрицы или с помощью методов матрицы. + */ + public var matrix:Matrix3D = new Matrix3D(); + public var visible:Boolean = true; + + public var alpha:Number = 1; + public var blendMode:String = "normal"; + public var colorTransform:ColorTransform = null; + public var filters:Array = null; + + /** + * @private + */ + alternativa3d var _parent:Object3DContainer; + /** + * @private + */ + alternativa3d var _boundBox:BoundBox; + /** + * @private + */ + alternativa3d var culling:int = 0; + /** + * @private + */ + alternativa3d var cameraMatrix:Matrix3D = new Matrix3D(); + + static private const boundBoxVertices:Vector. = new Vector.(24, true); + + /** + * @private + * Может быть отрисован + */ + alternativa3d function get canDraw():Boolean { + return true; + } + + /** + * @private + * Отрисовка + */ + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void {} + + /** + * @private + * Дебаг + */ + alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void {} + + public function get boundBox():BoundBox { + return _boundBox; + } + + public function set boundBox(value:BoundBox):void { + _boundBox = value; + } + + /** + * Расчёт баунда + * @param matrix Трансформация пространства, в системе которого расчитывается баунд. + * Если этот параметр не указан, баунд расчитается в локальных координатах объекта. + * @param boundBox Баунд, в который записывается результат. + * Если этот параметр не указан, создаётся новый экземпляр. + * @return Расчитанный баунд. + */ + public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return null; + } + + public function get parent():Object3DContainer { + return _parent; + } + + /** + * @private + */ + alternativa3d function cullingInCamera(camera:Camera3D, parentCulling:int):int { + if (camera.occludedAll) return -1; + culling = parentCulling; + var i:int, infront:Boolean, behind:Boolean, boundBox:BoundBox = this.boundBox, numOccluders:int = camera.numOccluders, cull:Boolean = culling > 0 && boundBox != null, occlude:Boolean = numOccluders > 0 && boundBox != null; + // Расчёт точек баунда в координатах камеры + if (cull || occlude) boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX, boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY, boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ, boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX, boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY, boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ, cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + // Куллинг + if (cull) { + if (culling & 1) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] > camera.nearClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + // TODO: проверка не нужна + if (infront) culling &= 62; + } + } + if (culling & 2) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] < camera.farClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 61; + } + } + if (culling & 4) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 59; + } + } + if (culling & 8) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 55; + } + } + if (culling & 16) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 47; + } + } + if (culling & 32) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 31; + } + } + } + // Окклюдинг + if (occlude) { + for (var o:int = 0; o < numOccluders; o++) { + var planeOccluder:Vector. = camera.occlusionPlanes[o], planeOccluderLength:int = planeOccluder.length; + for (var ni:int = 0; ni < planeOccluderLength; ni += 3) { + var nx:Number = planeOccluder[ni], ny:Number = planeOccluder[int(ni + 1)], nz:Number = planeOccluder[int(ni + 2)]; + for (i = 0; i < 24; i += 3) if (nx*boundBoxVertices[i] + ny*boundBoxVertices[int(i + 1)] + nz*boundBoxVertices[int(i + 2)] >= 0) break; + if (i < 24) break; + } + if (ni == planeOccluderLength) return -1; + } + } + return culling; + } + + alternativa3d function drawAxes(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawCenter(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawName(camera:Camera3D, canvas:Canvas):void { + + } + + static private const boundBoxProjectedVertices:Vector. = new Vector.(16, true); + static private const boundBoxUVTs:Vector. = new Vector.(24, true); + alternativa3d function drawBoundBox(camera:Camera3D, canvas:Canvas, color:int = -1):void { + + var boundBox:BoundBox = this.boundBox; + if (boundBox == null) return; + + boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX; + boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY; + boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ; + + boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX; + boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY; + boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ; + + cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + for (var i:int = 0; i < 8; i++) { + if (boundBoxVertices[int(i*3 +2)] <= 0) return; + } + Utils3D.projectVectors(camera.projectionMatrix, boundBoxVertices, boundBoxProjectedVertices, boundBoxUVTs); + canvas.gfx.endFill(); + canvas.gfx.lineStyle(0, (color < 0) ? ((culling > 0) ? 0xFFFF00 : 0x00FF00) : color); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.moveTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.moveTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.moveTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Object3DContainer.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Object3DContainer.as new file mode 100644 index 0000000..bad3e2b --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Object3DContainer.as @@ -0,0 +1,164 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Базовый контейнер трёхмерных объектов. + * Логика контейнеров и child-parent-отношений идентична логике + * displayObject'ов во Flash. + */ + public class Object3DContainer extends Object3D { + /** + * @private + */ + alternativa3d var _numChildren:int = 0; + /** + * @private + */ + alternativa3d var children:Vector. = new Vector.(); + + protected var numVisibleChildren:int = 0; + protected var visibleChildren:Vector. = new Vector.(); + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Определяем видимые объекты + numVisibleChildren = 0; + calculateVisibleChildren(camera, object); + // Если нет видимых дочерних объектов выходим без отрисовки + if (numVisibleChildren == 0) return; + // Расчёт порядка вывода видимых объектов + calculateOrder(camera, object); + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, parentCanvas); + } + + protected function calculateVisibleChildren(camera:Camera3D, object:Object3D):void { + var i:int = 0, child:Object3D; + while (i < _numChildren) { + child = children[i++]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + if (child.cullingInCamera(camera, object.culling) >= 0) { + visibleChildren[numVisibleChildren++] = child; + } + } + } + // Подрезаем список видимых детей + visibleChildren.length = numVisibleChildren; + } + + protected function calculateOrder(camera:Camera3D, object:Object3D):void {} + + // Отрисовка сзади детей + protected function drawBack(camera:Camera3D, object:Object3D, canvas:Canvas):void {} + + // Отрисовка перед детьми + protected function drawFront(camera:Camera3D, object:Object3D, canvas:Canvas):void {} + + // Отрисовка видимых детей + protected function drawVisibleChildren(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + + /*if (Debug.bounds) { + object.drawBoundBox(camera, canvas.getChildCanvas(true, false, 1, "normal", null, null)); + }*/ + + // Отрисовываем перед детьми + drawFront(camera, object, canvas); + + // Отрисовываем видимые дочерние объекты от ближних к дальним + for (var i:int = numVisibleChildren - 1; i >= 0; i--) { + var child:Object3D = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + } + + // Отрисовываем после детей + drawBack(camera, object, canvas); + + // Если не было отрисовки + if (canvas.numDraws == 0) { + parentCanvas.numDraws--; + return; //?????? + } + + // Зачищаем остатки + canvas.removeChildren(canvas.numDraws); + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var m:Matrix3D = matrix != null ? matrix.clone() : new Matrix3D(); + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчитываем баунды объектов + var childBoundBox:BoundBox = new BoundBox(); + var childMatrix:Matrix3D = new Matrix3D(); + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + childMatrix.identity(); + childMatrix.prepend(m); + childMatrix.prepend(child.matrix); + child.calculateBoundBox(childMatrix, childBoundBox); + boundBox.minX = (childBoundBox.minX < boundBox.minX) ? childBoundBox.minX : boundBox.minX; + boundBox.minY = (childBoundBox.minY < boundBox.minY) ? childBoundBox.minY : boundBox.minY; + boundBox.minZ = (childBoundBox.minZ < boundBox.minZ) ? childBoundBox.minZ : boundBox.minZ; + boundBox.maxX = (childBoundBox.maxX > boundBox.maxX) ? childBoundBox.maxX : boundBox.maxX; + boundBox.maxY = (childBoundBox.maxY > boundBox.maxY) ? childBoundBox.maxY : boundBox.maxY; + boundBox.maxZ = (childBoundBox.maxZ > boundBox.maxZ) ? childBoundBox.maxZ : boundBox.maxZ; + } + return boundBox; + } + + public function addChild(child:Object3D):void { + children[_numChildren++] = child; + child._parent = this; + } + + public function removeChild(child:Object3D):void { + var i:int = children.indexOf(child); + if (i < 0) throw new ArgumentError("Child not found"); + _numChildren--; + for (; i < _numChildren; i++) children[i] = children[int(i + 1)]; + children.length = _numChildren; + child._parent = null; + } + + public function hasChild(child:Object3D):Boolean { + return children.indexOf(child) > -1; + } + + public function getChildAt(index:uint):Object3D { + return children[index]; + } + + public function get numChildren():uint { + return _numChildren; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/core/Sorting.as b/Alternativa3D7/7.1/alternativa/engine3d/core/Sorting.as new file mode 100644 index 0000000..420ae82 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/core/Sorting.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Sorting { + + /** + * Грани не сортируются. + */ + static public const NONE:int = 0; + /** + * Грани сортируются по средним Z. + */ + static public const AVERAGE_Z:int = 1; + /** + * Грани находятся в BSP-дереве. + */ + static public const BSP:int = 2; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/BatchTextureLoader.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/BatchTextureLoader.as new file mode 100644 index 0000000..eb1a63c --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/BatchTextureLoader.as @@ -0,0 +1,275 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка пакета. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка пакета успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent.LOADER_ERROR + */ + [Event (name="loaderError", type="alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной текстуры. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной текстуры успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * @private + * Пакетный загрузчик текстур. + * + * При возникновении ошибки во время загрузки очередной текстуры, пакетный загрузчик заменяет соответствующую текстуру изображением-заглушкой и + * генерирует событие ошибки пакетного загрузчика. Пользователь пакетного загрузчика в обработчике ошибки может решить, прерывать ли процесс + * загрузки вызовом метода close() или нет. + */ + public class BatchTextureLoader extends EventDispatcher { + /** + * Текстура-заглушка для замены незагруженных текстур. + */ + private static var stubBitmapData:BitmapData; + + private static const IDLE:int = 0; + private static const LOADING:int = 1; + + // Состояние загрузчика + private var state:int = IDLE; + + // Загрузчик текстур + private var textureLoader:TextureLoader; + // Контекст безопасности загрузчика + private var loaderContext:LoaderContext; + // Базовый URL файлов текстур + private var baseURL:String; + // Пакет с описанием текстур материалов (textureName => TextureInfo) + private var batch:Object; + // Список имён текстур в пакете + private var textureNames:Vector.; + // Индекс текущего материала. + private var textureIndex:int; + // Общее количество загружаемых текстур + private var numTextures:int; + // Результирующая таблица (textureName => BitmapData) + private var _textures:Object; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function BatchTextureLoader() { + } + + /** + * Результирующая таблица битмапов. Ключами являются имена текстур, значениями -- объекты класса BitmapData. + */ + public function get textures():Object { + return _textures; + } + + /** + * Прекращает текущую загрузку. + */ + public function close():void { + if (state == LOADING) { + textureLoader.close(); + cleanup(); + _textures = null; + state = IDLE; + } + } + + /** + * Очищает ссылку на загруженный список текстур материалов. + */ + public function unload():void { + _textures = null; + } + + /** + * Запускает загрузку. + * + * @param baseURL базовый URL файлов текстур + * @param batch описание пакета текстур -- таблица textureName => TextureInfo + * @param loaderContext LoaderContext для загрузки + */ + public function load(baseURL:String, batch:Object, loaderContext:LoaderContext = null):void { + if (baseURL == null) { + throw ArgumentError("Parameter baseURL cannot be null"); + } + if (batch == null) { + throw ArgumentError("Parameter batch cannot be null"); + } + + this.baseURL = baseURL; + this.batch = batch; + this.loaderContext = loaderContext; + + if (textureLoader == null) { + textureLoader = new TextureLoader(); + } else { + close(); + } + textureLoader.addEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.addEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.addEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + + // Получение массива имён текстур + textureNames = new Vector.(); + for (var textureName:String in batch) { + textureNames.push(textureName); + } + numTextures = textureNames.length; + // Старт загрузки + textureIndex = 0; + _textures = {}; + + if (hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + + state = LOADING; + loadNextTexture(); + } + + /** + * Запускает загрузку очередной текстуры. + */ + private function loadNextTexture():void { + var info:TextureInfo = batch[textureNames[textureIndex]]; + var opacityMapFileUrl:String = info.opacityMapFileName == null || info.opacityMapFileName == "" ? null : baseURL + info.opacityMapFileName; + textureLoader.load(baseURL + info.diffuseMapFileName, opacityMapFileUrl, loaderContext); + } + + /** + * + */ + private function onTextureLoadingStart(e:Event):void { + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, numTextures, textureIndex)); + } + } + + /** + * + */ + private function onProgress(e:LoaderProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var totalProgress:Number = (textureIndex + e.totalProgress)/numTextures; + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, numTextures, textureIndex, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * Обрабатывает завершение загрузки текстуры. + */ + private function onTextureLoadingComplete(e:Event):void { + _textures[textureNames[textureIndex]] = textureLoader.bitmapData; + tryNextTexure(); + } + + /** + * Обрабатывает ошибку при загрузке текстуры. Незагруженная текстура заменяется изображением-заглушкой и + * генерируется событие ошибки пакетного загрузчика. + */ + private function onLoadingError(e:ErrorEvent):void { + var textureName:String = textureNames[textureIndex]; + _textures[textureName] = getStubBitmapData(); + dispatchEvent(new BatchTextureLoaderErrorEvent(BatchTextureLoaderErrorEvent.LOADER_ERROR, textureName, e.text)); + tryNextTexure(); + } + + /** + * + */ + private function tryNextTexure():void { + // Проверка состояния необходима, т.к. оно могло измениться в результате вызова метода close() в обработчике события ошибки загрузки + if (state == IDLE) return; + + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, numTextures, textureIndex)); + } + if (++textureIndex == numTextures) { + // Загружены все текстуры, отправляется сообщение о завершении + cleanup(); + removeEventListeners(); + state = IDLE; + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } else { + loadNextTexture(); + } + } + + /** + * + */ + private function removeEventListeners():void { + textureLoader.removeEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.removeEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.removeEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.removeEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + } + + /** + * Очищает внутренние ссылки на объекты. + */ + private function cleanup():void { + loaderContext = null; + textureNames = null; + } + + /** + * Метод для получения текстуры-заглушки. + * + * @return текстура-заглушка для замещения незагруженных текстур + */ + private function getStubBitmapData():BitmapData { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stubBitmapData.setPixel((i%2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stubBitmapData; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..3d29017 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,931 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.objects.Mesh; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + [Event (name="complete", type="flash.events.Event")] + /** + * + */ + public class Loader3DS extends EventDispatcher { + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_MODEL:int = 0; + private static const STATE_LOADING_TEXTURES:int = 1; + + private static var stubBitmapData:BitmapData; + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + private var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + private var loaderState:int = STATE_IDLE; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_MODEL) { + modelLoader.close(); + } + if (loaderState == STATE_LOADING_TEXTURES) { + textureLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + } + } + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + version = 0; + clean(); + + if (modelLoader == null) { + modelLoader = new URLLoader(); + modelLoader.dataFormat = URLLoaderDataFormat.BINARY; + modelLoader.addEventListener(Event.COMPLETE, on3DSLoad); + modelLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + modelLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + loaderState = STATE_LOADING_MODEL; + modelLoader.load(new URLRequest(url)); + } + + private function on3DSLoad(e:Event):void { + loaderState = STATE_IDLE; + data = modelLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + private function on3DSError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + private function loadBitmaps():void { + if (textureLoader == null) { + textureLoader = new TextureLoader(); + textureLoader.addEventListener(Event.COMPLETE, loadNextBitmap); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + } + + // Имена материалов с диффузными текстурами собираются в массив textureMaterialNames + bitmaps = new Array(); + textureMaterialNames = new Array(); + for each (var materialData:MaterialData in materialDatas) { + if (materialData.diffuseMap != null) { + textureMaterialNames.push(materialData.name); + } + } + + loaderState = STATE_LOADING_TEXTURES; + loadNextBitmap(); + } + + private function loadNextBitmap(e:Event = null):void { + if (e != null) { + if (!(e is IOErrorEvent)) { + bitmaps[textureMaterialNames[counter]] = textureLoader.bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[textureMaterialNames[counter]] = stubBitmapData; + } + } else { + counter = -1; + } + counter++; + if (counter < textureMaterialNames.length) { + var materialData:MaterialData = materialDatas[textureMaterialNames[counter]]; + textureLoader.load(path + materialData.diffuseMap.filename, materialData.opacityMap == null ? null : path + materialData.opacityMap.filename, context); + } else { + loaderState = STATE_IDLE; + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + object.name = animationData.objectName; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var j:uint; + var k:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + k = i << 1; + mesh.uvs[k] = uv.x; + mesh.uvs[k + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + } + } + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DSByteArray.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DSByteArray.as new file mode 100644 index 0000000..0c1e167 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Loader3DSByteArray.as @@ -0,0 +1,819 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + public class Loader3DSByteArray extends EventDispatcher { + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + alternativa3d var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function parseByteArray(data:ByteArray):void { + this.data = data; + this.data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, this.data.bytesAvailable); + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + + // Добавляем вершины + var i:uint; + var j:uint; + var k:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + k = i << 1; + mesh.uvs[k] = uv.x; + mesh.uvs[k + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + /*if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + }*/ + } + + alternativa3d function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + //loadBitmaps(); + buildContent(); // Без подгрузки битмап + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(data.readFloat(), -data.readFloat(), data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/MaterialParams.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/MaterialParams.as new file mode 100644 index 0000000..df75e1a --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/MaterialParams.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.loaders { + + public class MaterialParams { + + public var color:uint; + public var opacity:Number; + public var diffuseMap:String; + public var opacityMap:String; + + public function toString():String { + return "[MaterialParams color=" + color + ", opacity=" + opacity + ", diffuseMap=" + diffuseMap + ", opacityMap=" + opacityMap + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parsed3DSData.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parsed3DSData.as new file mode 100644 index 0000000..a5dc69d --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parsed3DSData.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3D; + + public class Parsed3DSData { + + /** + * Список объектов в порядке их появления в 3DS-данных. + **/ + public var objects:Vector.; + /** + * Список материалов каждого объекта. Если для объекта нет назначенных материалов, соответствующий элемент списка равен null. + **/ + public var objectMaterials:Vector.>; + /** + * Список материалов 3DS-файла (materialName => MaterialParams). + */ + public var materials:Object; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..5d6fd30 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,977 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.utils.ByteArray; + import flash.utils.Endian; + + /** + * + */ + public class Parser3DS extends EventDispatcher { + + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Vector.; + private var materialDatas:Object; + + private var modelLoader:URLLoader; + private var data:ByteArray; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + + /** + * + * @param data + */ + public function Parser3DS() { + } + + /** + * + * @param data + */ + public function parse(data:ByteArray):Parsed3DSData { + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + return buildContent(); + } + + /** + * + */ + private function clean():void { + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + /** + * + */ + private function buildContent():Parsed3DSData { + var result:Parsed3DSData = new Parsed3DSData(); + var i:uint; + var length:uint; + + // Формируем связи объектов + result.objects = new Vector.(); + result.objectMaterials = new Vector.>(); + result.materials = {}; + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + + var mat:MaterialParams = new MaterialParams(); + mat.color = materialData.color; + mat.opacity = 1 - 0.01*materialData.transparency; + if (materialData.diffuseMap != null) { + mat.diffuseMap = materialData.diffuseMap.filename; + } + if (materialData.opacityMap != null) { + mat.opacityMap = materialData.opacityMap.filename; + } + result.materials[materialName] = mat; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData, result); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null, result); + } + } + } + + clean(); + return result; + } + + /** + * + * @param animationData + */ + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x*units, animationData.position.y*units, animationData.position.z*units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); +// trace("transform[2]", transform[2]); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + /** + * + * @param mesh + * @param objectData + * @param animationData + */ + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, parsedData:Parsed3DSData):void { + mesh.name = objectData.name; + // Добавляем вершины + var i:uint; + var j:uint; + var k:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + k = i << 1; + mesh.uvs[k] = uv.x; + mesh.uvs[k + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + parsedData.objects.push(mesh); + + // Добавляем поверхности + if (objectData.surfaces != null) { + var meshMaterials:Vector. = new Vector.(); + for (var surfaceId:String in objectData.surfaces) { + meshMaterials.push(surfaceId); +// var materialData:MaterialData = materialDatas[surfaceId]; +// if (materialData.diffuseMap != null) { +// mesh.texture = bitmaps[materialData.name]; +// } + } + parsedData.objectMaterials.push(meshMaterials); + } else { + parsedData.objectMaterials.push(null); + } +// trace("mesh.matrix.decompose()", mesh.matrix.decompose()); + } + + /** + * + * @param index + * @param length + */ + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + * @param length + */ + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + */ + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + /** + * + * @param index + * @param length + */ + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + * @param length + */ + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + */ + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = {}; + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + /** + * + * @param materialName + * @param map + * @param index + * @param length + */ + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + /** + * + * @param index + * @param length + */ + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Vector.(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + * @param length + */ + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Считывает строку, заканчивающуюся на нулевой байт. + * + * @param index + * @return + */ + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + /** + * + * @param angle + * @param x + * @param z + * @param y + * @return + */ + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = 0.5*angle; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -0.5*Math.PI; + res.x = 0; + return res; + } + if (k <= -1) { + half = 0.5*angle; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = 0.5*Math.PI; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(k); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureFilesData.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureFilesData.as new file mode 100644 index 0000000..265859b --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureFilesData.as @@ -0,0 +1,13 @@ +package alternativa.engine3d.loaders { + public class TextureFilesData { + + public var diffuseMap:String; + public var opacityMap:String; + + public function TextureFilesData(diffuseMap:String, opacityMap:String) { + this.diffuseMap = diffuseMap; + this.opacityMap = opacityMap; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureInfo.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureInfo.as new file mode 100644 index 0000000..babb394 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureInfo.as @@ -0,0 +1,36 @@ +package alternativa.engine3d.loaders { + + /** + * Структура для хранения имён файла диффузной текстуры и файла карты прозрачности. + */ + public class TextureInfo { + /** + * Имя файла диффузной текстуры. + */ + public var diffuseMapFileName:String; + /** + * Имя файла карты прозрачности. + */ + public var opacityMapFileName:String; + + /** + * Создаёт новый экземпляр. + * + * @param diffuseMapFileName имя файла диффузной текстуры + * @param opacityMapFileName имя файла карты прозрачности + */ + public function TextureInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) { + this.diffuseMapFileName = diffuseMapFileName; + this.opacityMapFileName = opacityMapFileName; + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[TextureInfo diffuseMapFileName=" + diffuseMapFileName + ", opacityMapFileName=" + opacityMapFileName + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureLoader.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureLoader.as new file mode 100644 index 0000000..3cc7171 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/TextureLoader.as @@ -0,0 +1,268 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка ресурса. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка ресурса успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной части ресурса. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной части ресурса успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * Загрузчик текстуры, состоящей из одного или двух файлов. В случае, если указан второй файл, он используется для заполнения альфа-канала + * получаемой текстуры. + */ + public class TextureLoader extends EventDispatcher { + + private static const IDLE:int = -1; + private static const LOADING_DIFFUSE_MAP:int = 0; + private static const LOADING_ALPHA_MAP:int = 1; + + private var state:int = IDLE; + private var bitmapLoader:Loader; + private var loaderContext:LoaderContext; + private var alphaTextureUrl:String; + private var _bitmapData:BitmapData; + + /** + * Создаёт новый экземпляр. Если указан URL диффузной части текстуры, то сразу начинается загрузка. + * + * @param diffuseTextureUrl URL диффузной части текстуры + * @param alphaTextureUrl URL карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function TextureLoader() { + } + + /** + * Загруженная текстура. + */ + public function get bitmapData():BitmapData { + return _bitmapData; + } + + /** + * Загрузка текстурных карт. При успешной загрузке посылается сообщение Event.COMPLETE. + * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void { + unload(); + this.alphaTextureUrl = alphaTextureUrl == "" ? null : alphaTextureUrl; + this.loaderContext = loaderContext; + + loadPart(LOADING_DIFFUSE_MAP, diffuseTextureUrl); + } + + /** + * Прекращвет текущую загрузку. Если нет активных загрузок, не происходит ничего. + */ + public function close():void { + if (state == IDLE) return; + state = IDLE; + bitmapLoader.unload(); + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Очищает внутренние ссылки на загруженные объекты, чтобы сборщик мусора смог их удалить. + */ + public function unload():void { + close(); + _bitmapData = null; + } + + /** + * Очищает временные внутренние ссылки. + */ + private function cleanup():void { + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Запускает загрузку части текстуры. + * + * @param state фаза загрузки + * @param url URL загружаемого файла + */ + private function loadPart(state:int, url:String):void { + this.state = state; + createLoader(); + bitmapLoader.load(new URLRequest(url), loaderContext); + } + + /** + * Обрабатывает начало загрузки очередной части текстуры. + */ + private function onPartLoadingOpen(e:Event):void { + if (_bitmapData == null && hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, 2, state == LOADING_DIFFUSE_MAP ? 0 : 1)); + } + } + + /** + * + */ + private function onPartLoadingProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var partNumber:int = state == LOADING_DIFFUSE_MAP ? 0 : 1; + var totalProgress:Number = 0.5*(partNumber + e.bytesLoaded/e.bytesTotal); + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, 2, partNumber, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * + */ + private function onPartLoadingComplete(e:Event):void { + switch (state) { + case LOADING_DIFFUSE_MAP: { + // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности. + _bitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + dispatchPartComplete(0); + if (alphaTextureUrl != null) { + loadPart(LOADING_ALPHA_MAP, alphaTextureUrl); + } else { + complete(); + } + break; + } + case LOADING_ALPHA_MAP: { + // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры. + var pt:Point = new Point(); + var tmpBmd:BitmapData = _bitmapData; + _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height); + _bitmapData.copyPixels(tmpBmd, tmpBmd.rect, pt); + + var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) { + tmpBmd.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = tmpBmd; + } else { + tmpBmd.dispose(); + } + _bitmapData.copyChannel(alpha, alpha.rect, pt, BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + dispatchPartComplete(1); + complete(); + break; + } + } + } + + /** + * Создаёт событие завершения загрузки части текстуры. + * + * @param partnNumber номер загруженной части текстуры + */ + private function dispatchPartComplete(partNumber:int):void { + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, 2, partNumber)); + } + } + + /** + * + */ + private function onLoadError(e:Event):void { + state = IDLE; + cleanup(); + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + state = IDLE; + cleanup(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * + */ + private function createLoader():void { + bitmapLoader = new Loader(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.addEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.addEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.addEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } + + /** + * + */ + private function destroyLoader():void { + if (bitmapLoader == null) return; + bitmapLoader.unload(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.removeEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.removeEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.removeEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); + bitmapLoader = null; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as new file mode 100644 index 0000000..0d8da26 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.loaders.events { + import flash.events.ErrorEvent; + import flash.events.Event; + + /** + * Класс представляет событие ошибки, генерируемое пакетным загрузчиком текстур. + */ + public class BatchTextureLoaderErrorEvent extends ErrorEvent { + + /** + * + */ + public static const LOADER_ERROR:String = "loaderError"; + + // Имя текстуры, с которой произошла проблема + private var _textureName:String; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param textureName имя текстуры, с которой произошла проблема + * @param text описание ошибки + */ + public function BatchTextureLoaderErrorEvent(type:String, textureName:String, text:String) { + super(type); + this.text = text; + _textureName = textureName; + } + + /** + * Имя текстуры, с которой произошла проблема. + */ + public function get textureName():String { + return _textureName; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new BatchTextureLoaderErrorEvent(type, _textureName, text); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[BatchTextureLoaderErrorEvent textureName=" + _textureName + ", text=" + text + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..57a739d --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,68 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Событие загрузчиков ресурсов, состоящих из нескольких частей. + */ + public class LoaderEvent extends Event { + /** + * Событие начала загрузки очередной части ресурса. + */ + public static const PART_OPEN:String = "partOpen"; + /** + * Событие окончания загрузки очередной части ресурса. + */ + public static const PART_COMPLETE:String = "partComplete"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер части, к которой относится событие. Нумерация начинается с нуля + */ + public function LoaderEvent(type:String, partsTotal:int, currentPart:int) { + super(type); + _partsTotal = partsTotal; + _currentPart= currentPart; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderEvent(type, _partsTotal, _currentPart); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..273740e --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Событие прогресса загрузки ресурсов, состоящих из нескольких частей. + */ + public class LoaderProgressEvent extends ProgressEvent { + + /** + * Событие прогресса загрузки очередной части ресурса. + */ + public static const LOADER_PROGRESS:String = "loaderProgress"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + // Общий прогресс загрузки, выраженный числом в интервале [0, 1] + private var _totalProgress:Number = 0; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер загружаемой в настоящий момент части. Нумерация начинается с нуля + * @param totalProgress общий прогресс загрузки, выраженный числом в интервале [0, 1] + * @param bytesLoaded количество загруженных байт текущей части + * @param bytesTotal объём текущей части + */ + public function LoaderProgressEvent(type:String, partsTotal:int, currentPart:int, totalProgress:Number = 0, bytesLoaded:uint=0, bytesTotal:uint=0) { + super(type, false, false, bytesLoaded, bytesTotal); + _partsTotal = partsTotal; + _currentPart= currentPart; + _totalProgress = totalProgress; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Общий прогресс загрузки, выраженный числом в интервале [0, 1]. + */ + public function get totalProgress():Number { + return _totalProgress; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _partsTotal, _currentPart, _totalProgress, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", totalProgress=" + _totalProgress.toFixed(2) + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/AnimSprite.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..df5ae96 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Анимированный спрайт. + * Анимация осуществляется путём переключения изображений, + * хранящихся в списке textures + */ + public class AnimSprite extends Sprite3D { + + /** + * Список кадров изображений + */ + public var textures:Vector.; + public var mipMaps:Vector.; + /** + * Устанавливаемый кадр + */ + public var frame:uint = 0; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (mipMapping == 0) texture = textures[frame]; else mipMap = mipMaps[frame]; + super.draw(camera, object, parentCanvas); + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + if (mipMapping == 0) texture = textures[frame]; else mipMap = mipMaps[frame]; + return super.calculateBoundBox(matrix, boundBox); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Axes.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Axes.as new file mode 100644 index 0000000..3070e9c --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Axes.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.display.Graphics; + import flash.geom.Utils3D; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, иллюстрирующий систему коордщинат + */ + public class Axes extends Object3D { + + public var axisLength:Number; + public var lineThickness:Number; + public var dotRadius:Number; + public var textSize:Number; + + public function Axes(axisLength:Number = 30, lineThickness:Number = 0, dotRadius:Number = 2, textSize:Number = 10):void { + this.axisLength = axisLength; + this.lineThickness = lineThickness; + this.dotRadius = dotRadius; + this.textSize = textSize; + _boundBox = new BoundBox(); + _boundBox.setSize(-dotRadius, -dotRadius, -dotRadius, axisLength, axisLength, axisLength); + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + var p:Vector. = Vector.([0, 0, 0, axisLength, 0, 0, 0, axisLength, 0, 0, 0, axisLength]); + var d:Vector. = new Vector.(8); + object.cameraMatrix.transformVectors(p, p); + + // Центр за камерой + if (p[2] < camera.nearClipping) return; + + Utils3D.projectVectors(camera.projectionMatrix, p, d, new Vector.()); + var size:Number = camera.viewSize/p[2]; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + var gfx:Graphics = canvas.gfx; + var text:TextField; + + // Ось X + if (p[5] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0xFF0000); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[2], d[3]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[2]; + text.y = d[3]; + text.text = "X"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0xFF0000)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Y + if (p[8] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x00FF00); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[4], d[5]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[4]; + text.y = d[5]; + text.text = "Y"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x00FF00)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Z + if (p[11] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x0000FF); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[6], d[7]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[6]; + text.y = d[7]; + text.text = "Z"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x0000FF)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Начало координат + gfx.lineStyle(); + gfx.beginFill(0xFFFFFF); + gfx.drawCircle(d[0], d[1], dotRadius*size); + + //debugDrawBoundRaduis(camera, object, canvas); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Bone.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Bone.as new file mode 100644 index 0000000..02c4805 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Bone.as @@ -0,0 +1,58 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import flash.geom.Matrix3D; + import __AS3__.vec.Vector; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class Bone extends Mesh { + + public var localTransform:Vector.; + /** + * @private + */ + alternativa3d var localMatrix:Matrix3D; + + /** + * @private + */ + alternativa3d var length:Number; + /** + * @private + */ + alternativa3d var distance:Number; + /** + * @private + */ + alternativa3d var _numChildren:uint = 0; + /** + * @private + */ + alternativa3d var children:Vector. = new Vector.(); + + public function Bone(length:Number, distance:Number) { + this.length = length; + this.distance = distance; + } + + public function addChild(child:Bone):void { + children[_numChildren++] = child; + child.localTransform = child.matrix.decompose(); + child.localMatrix = new Matrix3D(); + } + + public function calculateMatrix():void { + for (var i:int = 0; i < _numChildren; i++) { + var child:Bone = children[i]; + child.matrix.identity(); + child.matrix.prepend(matrix); + child.localMatrix.recompose(child.localTransform); + child.matrix.prepend(child.localMatrix); + child.calculateMatrix(); + } + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/KDObject.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/KDObject.as new file mode 100644 index 0000000..50da409 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/KDObject.as @@ -0,0 +1,1305 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import alternativa.engine3d.core.Debug; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, использующийся для разрещения конфликтов сортировки в KDTree + */ + public class KDObject extends Object3D { + + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + static private const verticesMap:Vector. = new Vector.(); + static private const drawIndices:Vector. = new Vector.(); + static private var drawIndicesLength:int; + static private const drawMatrix:Matrix3D = new Matrix3D(); + + static private const averageZ:Vector. = new Vector.(); + static private const sortingMap:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + + /** + * @private + */ + alternativa3d var vertices:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var verticesLength:int; + /** + * @private + */ + alternativa3d var numVertices:int; + private var projectedVertices:Vector. = new Vector.(); + private var uvts:Vector. = new Vector.(); + + private var indices:Vector. = new Vector.(); + private var indicesLength:int; + + private var bsp:BSPNode; + private var priority:int; + + private var texture:BitmapData; + private var smooth:Boolean; + private var repeatTexture:Boolean; + + /** + * @private + */ + alternativa3d var viewAligned:Boolean; + private var debugResult:int; + private var textureMatrix:Matrix = new Matrix(); + private var projectionX:Number; + private var projectionY:Number; + private var z:Number; + + //private var single:Boolean; + //private var poly:Boolean; + //private var perspectiveCorrection:Boolean; + + /** + * @private + */ + alternativa3d var numCheckedOccluders:int; + + public function KDObject() { + boundBox = new BoundBox(); + } + + /** + * @private + */ + alternativa3d function create():KDObject { + return (collectorLength > 0) ? collector[--collectorLength] : new KDObject(); + } + + /** + * @private + */ + alternativa3d function destroy():void { + debugResult = 0; + indicesLength = numVertices = verticesLength = 0; + texture = null, colorTransform = null; + if (bsp != null) bsp.destroy(), bsp = null; + collector[collectorLength++] = this; + } + + /** + * @private + */ + static alternativa3d function createFrom(object:Object3D, camera:Camera3D, matrix:Matrix3D = null):KDObject { + var source:Object3D = object, result:KDObject = (collectorLength > 0) ? collector[--collectorLength] : new KDObject(); + // Поиск оригинального объекта + while (source is Reference) source = (source as Reference).referenceObject; + // Дебаг + if (camera.debugMode) result.debugResult = camera.checkInDebug(source); + // Меш + if (source is Mesh) { + var mesh:Mesh = source as Mesh; + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(mesh.vertices, mesh.cameraVertices); + // Расчёт координат камеры в меше + mesh.calculateInverseCameraMatrix(object.cameraMatrix); + // Подготовка к ремапу + for (var i:int = 0; i < mesh.numVertices;) verticesMap[i++] = -1; + // Создание BSP-дерева + if (mesh.bsp != null) { + result.bsp = result.clipNode(mesh.bsp, mesh, object.culling, camera.nearClipping, camera.farClipping); + // Установка приоритета + result.priority = (matrix == null) ? 0 : 1; + // Сбор полигонов + } else { + if (mesh.poly) { + result.clip(mesh, object.culling, camera.nearClipping, camera.farClipping); + } else { + throw new Error("Пока можно использовать только полигональные меши"); + } + // Установка приоритета + result.priority = 2; + } + // Подрезаем массивы в меше + if (object.culling > 0) mesh.cameraVertices.length = mesh.uvts.length = mesh.numVertices*3, mesh.uvs.length = mesh.numVertices << 1; + // Если объект виден + if (result.numVertices > 0) { + // Подрезка вершин + result.vertices.length = result.verticesLength; + // Перевод динамика в KD-дерево + if (matrix != null) { + // Трансформация вершин в KD-дерево + matrix.transformVectors(result.vertices, result.vertices); + // Расчёт баунда в KD-дереве + var boundBox:BoundBox = result._boundBox; + boundBox.minX = boundBox.minY = boundBox.minZ = Number.MAX_VALUE; + boundBox.maxX = boundBox.maxY = boundBox.maxZ = -Number.MAX_VALUE; + for (var v:int = 0, c:Number; v < result.verticesLength;) { + if ((c = result.vertices[v++]) < boundBox.minX) boundBox.minX = c; + if (c > boundBox.maxX) boundBox.maxX = c; + if ((c = result.vertices[v++]) < boundBox.minY) boundBox.minY = c; + if (c > boundBox.maxY) boundBox.maxY = c; + if ((c = result.vertices[v++]) < boundBox.minZ) boundBox.minZ = c; + if (c > boundBox.maxZ) boundBox.maxZ = c; + } + } else if (result.debugResult & Debug.BOUNDS) { + // Расчёт баунда для отображения конфликтной зоны в дебаге + var objectInverseCameraMatrix:Matrix3D = Mesh.inverseCameraMatrix.clone(); + objectInverseCameraMatrix.append(object.matrix); + var vs:Vector. = new Vector.(); + objectInverseCameraMatrix.transformVectors(result.vertices, vs); + boundBox = result._boundBox; + boundBox.minX = boundBox.minY = boundBox.minZ = Number.MAX_VALUE; + boundBox.maxX = boundBox.maxY = boundBox.maxZ = -Number.MAX_VALUE; + for (v = 0; v < result.verticesLength;) { + if ((c = vs[v++]) < boundBox.minX) boundBox.minX = c; + if (c > boundBox.maxX) boundBox.maxX = c; + if ((c = vs[v++]) < boundBox.minY) boundBox.minY = c; + if (c > boundBox.maxY) boundBox.maxY = c; + if ((c = vs[v++]) < boundBox.minZ) boundBox.minZ = c; + if (c > boundBox.maxZ) boundBox.maxZ = c; + } + } + // Копирование свойств меша + result.texture = (mesh.mipMapping == 0) ? mesh.texture : mesh.getMipTexture(camera, object); + result.smooth = mesh.smooth; + result.repeatTexture = mesh.repeatTexture; + result.viewAligned = false; + //res.poly = mesh.poly; + //res.perspectiveCorrection = mesh.perspectiveCorrection; + } else { + result.destroy(); + return null; + } + // Спрайт + } else if (source is Sprite3D) { + var sprite:Sprite3D = source as Sprite3D; + // Назначение текстуры для анимированных спрайтов (костыль) + if (sprite is AnimSprite) { + var animSprite:AnimSprite = (sprite as AnimSprite); + if (sprite.mipMapping == 0) sprite.texture = animSprite.textures[animSprite.frame]; else sprite.mipMap = animSprite.mipMaps[animSprite.frame]; + } + // Вершины спрайта + var spriteVertices:Vector. = Sprite3D.vertices; + var spriteVerticesLength:int = sprite.calculateVertices(object, camera); + // Если объект виден + if (spriteVerticesLength > 0) { + // Подрезка вершин + spriteVertices.length = spriteVerticesLength; + // Установка числа вершин + result.verticesLength = spriteVerticesLength, result.numVertices = spriteVerticesLength/3; + if (result.uvts.length < spriteVerticesLength) result.uvts.length = spriteVerticesLength; + // Заполнение индексов + result.indices[result.indicesLength++] = result.numVertices; + for (i = 0; i < result.numVertices; i++) result.indices[result.indicesLength++] = i; + // Копирование параметров + result.texture = sprite.drawTexture; + result.smooth = sprite.smooth; + result.viewAligned = true; + var m:Matrix = Sprite3D.textureMatrix; result.textureMatrix.a = m.a, result.textureMatrix.b = m.b, result.textureMatrix.c = m.c, result.textureMatrix.d = m.d, result.textureMatrix.tx = m.tx, result.textureMatrix.ty = m.ty; + result.projectionX = sprite.projectionX; + result.projectionY = sprite.projectionY; + result.z = spriteVertices[2]; + // Установка приоритета + result.priority = 2; + // Перевод динамика в KD-дерево + if (matrix != null) matrix.transformVectors(spriteVertices, spriteVertices); + // Копирование вершин и расчёт баунда в KD-дереве + boundBox = result._boundBox; + boundBox.minX = boundBox.minY = boundBox.minZ = Number.MAX_VALUE; + boundBox.maxX = boundBox.maxY = boundBox.maxZ = -Number.MAX_VALUE; + for (v = 0; v < spriteVerticesLength;) { + if ((c = result.vertices[v] = spriteVertices[v++]) < boundBox.minX) boundBox.minX = c; + if (c > boundBox.maxX) boundBox.maxX = c; + if ((c = result.vertices[v] = spriteVertices[v++]) < boundBox.minY) boundBox.minY = c; + if (c > boundBox.maxY) boundBox.maxY = c; + if ((c = result.vertices[v] = spriteVertices[v++]) < boundBox.minZ) boundBox.minZ = c; + if (c > boundBox.maxZ) boundBox.maxZ = c; + } + } else { + result.destroy(); + return null; + } + // Не меш и не спрайт + } else { + result.destroy(); + return null; + } + // Копирование общих свойств + result.alpha = object.alpha; + result.blendMode = object.blendMode; + result.colorTransform = object.colorTransform; + result.filters = object.filters; + result.numCheckedOccluders = 0; + return result; + } + + static private const polygon:Vector. = new Vector.(); + private function clip(mesh:Mesh, culling:int, near:Number, far:Number):void { + var meshVertices:Vector. = mesh.cameraVertices, meshUVTs:Vector. = mesh.uvts, polygons:Vector. = mesh.indices, polygonsLength:int = mesh.indices.length; + var i:int = 0, k:int = 0, n:int, m:int, num1:int, num2:int = 0, vi:int, ni:int = 0; + var infront:Boolean, behind:Boolean, inside:Boolean; + for (var v:int = mesh.numVertices; i < polygonsLength; i = k) { + k = (num1 = polygons[i++]) + i; + // Отсечение backface + if (mesh.backfaceCulling == 2) { + if (mesh.normals[ni++]*mesh.cameraX + mesh.normals[ni++]*mesh.cameraY + mesh.normals[ni++]*mesh.cameraZ <= mesh.normals[ni++]) continue; + } else { + var ax:Number = meshVertices[vi = int(polygons[i]*3)], ay:Number = meshVertices[++vi], az:Number = meshVertices[++vi], abx:Number = meshVertices[vi = int(polygons[int(i + 1)]*3)] - ax, aby:Number = meshVertices[++vi] - ay, abz:Number = meshVertices[++vi] - az, acx:Number = meshVertices[vi = int(polygons[int(i + 2)]*3)] - ax, acy:Number = meshVertices[++vi] - ay, acz:Number = meshVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Отсечение + if (culling > 0) { + var insideNear:Boolean = true; + if (culling & 1) { + for (n = i; n < k; n++) if ((inside = meshVertices[int(polygons[n]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (n = i; n < k; n++) if ((inside = meshVertices[int(polygons[n]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (n = i; n < k; n++) if ((inside = -meshVertices[vi = int(polygons[n]*3)] < meshVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (n = i; n < k; n++) if ((inside = meshVertices[vi = int(polygons[n]*3)] < meshVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (n = i; n < k; n++) if ((inside = -meshVertices[vi = int(polygons[n]*3 + 1)] < meshVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (n = i; n < k; n++) if ((inside = meshVertices[vi = int(polygons[n]*3 + 1)] < meshVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + } + // Полное вхождение + if (culling == 0 || insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + // Копирование полигона и ремап + indices[indicesLength++] = num1; + for (n = i; n < k; n++) { + if (verticesMap[m = polygons[n]] < 0) { + vertices[verticesLength] = meshVertices[vi = int(m*3)], + uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + indices[indicesLength++] = verticesMap[m] = numVertices++; + } else { + indices[indicesLength++] = verticesMap[m]; + } + } + } else { + // Заполняем полигон + for (n = i; n < k; n++) polygon[n - i] = polygons[n]; + var t:Number, au:Number, av:Number; + var a:int, b:int, c:int, ai:int, bi:int, bx:Number, by:Number, bz:Number; + // Клипинг по ниар + if (!insideNear) { + a = c = polygon[0], ai = a*3, az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bz = meshVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = near, meshUVTs[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + a = c = polygon[0], ai = a*3, az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bz = meshVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = far, meshUVTs[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + a = c = polygon[0], ai = a*3, ax = meshVertices[ai], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bx = meshVertices[bi], bz = meshVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = ax + (bx - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + a = c = polygon[0], ai = a*3, ax = meshVertices[ai], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bx = meshVertices[bi], bz = meshVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = ax + (bx - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + a = c = polygon[0], ai = a*3, ay = meshVertices[int(ai + 1)], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, by = meshVertices[int(bi + 1)], bz = meshVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = ay + (by - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + a = c = polygon[0], ai = a*3, ay = meshVertices[int(ai + 1)], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, by = meshVertices[int(bi + 1)], bz = meshVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = ay + (by - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Копирование полигона и ремап + indices[indicesLength++] = num1; + for (n = 0; n < num1; n++) { + if ((m = polygon[n]) < mesh.numVertices) { + if (verticesMap[m] < 0) { + vertices[verticesLength] = meshVertices[vi = int(m*3)], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + indices[indicesLength++] = verticesMap[m] = numVertices++; + } else { + indices[indicesLength++] = verticesMap[m]; + } + } else { + vertices[verticesLength] = meshVertices[vi = int(m*3)], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + indices[indicesLength++] = numVertices++; + } + } + } + } + } + + static private var newNode:BSPNode = BSPNode.create(); + private function clipNode(node:BSPNode, mesh:Mesh, culling:int, near:Number, far:Number):BSPNode { + if (node != null) { + // Проход по дочерним нодам + var negative:BSPNode = clipNode(node.negative, mesh, culling, near, far); + var positive:BSPNode = clipNode(node.positive, mesh, culling, near, far); + // Определение положения камеры + var cameraInfront:Boolean = mesh.cameraX*node.normalX + mesh.cameraY*node.normalY + mesh.cameraZ*node.normalZ > node.offset; + var meshVertices:Vector. = mesh.cameraVertices, meshUVTs:Vector. = mesh.uvts, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength, newPolygons:Vector. = newNode.polygons; + // Если камера спереди + if (cameraInfront) { + var i:int = 0, j:int = 0, k:int = 0, n:int, m:int, num1:int, num2:int = 0, vi:int; + var infront:Boolean, behind:Boolean, inside:Boolean; + // Если объект виден целиком + if (culling == 0) { + // Копирование полигонов и ремап + for (; i < polygonsLength; i++) { + if (i == k) num1 = polygons[i++], newPolygons[j++] = num1, k = num1 + i; + if (verticesMap[m = polygons[i]] < 0) { + vertices[verticesLength] = meshVertices[vi = int(m*3)], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], + uvts[verticesLength++] = meshUVTs[vi]; + newPolygons[j++] = verticesMap[m] = numVertices++; + } else { + newPolygons[j++] = verticesMap[m]; + } + } + } else { + // Клиппинг + for (var v:int = mesh.numVertices; i < polygonsLength; i = k) { + k = (num1 = polygons[i++]) + i; + // Отсечение + var insideNear:Boolean = true; + if (culling & 1) { + for (n = i; n < k; n++) if ((inside = meshVertices[int(polygons[n]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (n = i; n < k; n++) if ((inside = meshVertices[int(polygons[n]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (n = i; n < k; n++) if ((inside = -meshVertices[vi = int(polygons[n]*3)] < meshVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (n = i; n < k; n++) if ((inside = meshVertices[vi = int(polygons[n]*3)] < meshVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (n = i; n < k; n++) if ((inside = -meshVertices[vi = int(polygons[n]*3 + 1)] < meshVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (n = i; n < k; n++) if ((inside = meshVertices[vi = int(polygons[n]*3 + 1)] < meshVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + // Копирование полигона и ремап + newPolygons[j++] = num1; + for (n = i; n < k; n++) { + if (verticesMap[m = polygons[n]] < 0) { + vertices[verticesLength] = meshVertices[vi = int(m*3)], + uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + newPolygons[j++] = verticesMap[m] = numVertices++; + } else { + newPolygons[j++] = verticesMap[m]; + } + } + } else { + // Заполняем полигон + for (n = i; n < k; n++) polygon[n - i] = polygons[n]; + var t:Number, au:Number, av:Number; + var a:int, b:int, c:int, ai:int, bi:int, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number; + // Клипинг по ниар + if (!insideNear) { + a = c = polygon[0], ai = a*3, az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bz = meshVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = near, meshUVTs[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + a = c = polygon[0], ai = a*3, az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bz = meshVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = far, meshUVTs[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + a = c = polygon[0], ai = a*3, ax = meshVertices[ai], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bx = meshVertices[bi], bz = meshVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = ax + (bx - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + a = c = polygon[0], ai = a*3, ax = meshVertices[ai], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, bx = meshVertices[bi], bz = meshVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = ax + (bx - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = (ay = meshVertices[int(ai + 1)]) + (meshVertices[int(bi + 1)] - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + a = c = polygon[0], ai = a*3, ay = meshVertices[int(ai + 1)], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, by = meshVertices[int(bi + 1)], bz = meshVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = ay + (by - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + a = c = polygon[0], ai = a*3, ay = meshVertices[int(ai + 1)], az = meshVertices[int(ai + 2)]; + for (n = 1; n <= num1; n++) { + b = (n < num1) ? polygon[n] : c, bi = b*3, by = meshVertices[int(bi + 1)], bz = meshVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, meshVertices[vi = int(v++*3)] = (ax = meshVertices[ai]) + (meshVertices[bi] - ax)*t, meshUVTs[vi++] = (au = meshUVTs[ai]) + (meshUVTs[bi] - au)*t, meshVertices[vi] = ay + (by - ay)*t, meshUVTs[vi++] = (av = meshUVTs[int(ai + 1)]) + (meshUVTs[int(bi + 1)] - av)*t, meshVertices[vi] = az + (bz - az)*t, meshUVTs[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Копирование полигона и ремап + newPolygons[j++] = num1; + for (n = 0; n < num1; n++) { + if ((m = polygon[n]) < mesh.numVertices) { + if (verticesMap[m] < 0) { + vertices[verticesLength] = meshVertices[vi = int(m*3)], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + newPolygons[j++] = verticesMap[m] = numVertices++; + } else { + newPolygons[j++] = verticesMap[m]; + } + } else { + vertices[verticesLength] = meshVertices[vi = int(m*3)], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi++], vertices[verticesLength] = meshVertices[vi], uvts[verticesLength++] = meshUVTs[vi]; + newPolygons[j++] = numVertices++; + } + } + } + } + } + } + // Если нода видна или есть видимые дочерние ноды + if (j > 0 || negative != null && positive != null) { + var res:BSPNode = newNode; + newNode = node.create(); + res.negative = negative; + res.positive = positive; + res.polygonsLength = j; + res.cameraInfront = cameraInfront; + + ai = polygons[1]*3, res.ax = meshVertices[ai++], res.ay = meshVertices[ai++], res.az = meshVertices[ai]; + ai = polygons[2]*3, res.abx = meshVertices[ai++] - res.ax, res.aby = meshVertices[ai++] - res.ay, res.abz = meshVertices[ai] - res.az; + ai = polygons[3]*3, res.acx = meshVertices[ai++] - res.ax, res.acy = meshVertices[ai++] - res.ay, res.acz = meshVertices[ai] - res.az; + + return res; + } else { + return (negative != null) ? negative : positive; + } + } else return null; + } + + /** + * @private + */ + alternativa3d function split(axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, negative:KDObject, positive:KDObject):void { + // Сплит баунда + var negativeBoundBox:BoundBox = negative._boundBox, positiveBoundBox:BoundBox = positive._boundBox; + if (axisX) { + negativeBoundBox.minX = _boundBox.minX, negativeBoundBox.minY = _boundBox.minY, negativeBoundBox.minZ = _boundBox.minZ, negativeBoundBox.maxX = coord, negativeBoundBox.maxY = _boundBox.maxY, negativeBoundBox.maxZ = _boundBox.maxZ, positiveBoundBox.minX = coord, positiveBoundBox.minY = _boundBox.minY, positiveBoundBox.minZ = _boundBox.minZ, positiveBoundBox.maxX = _boundBox.maxX, positiveBoundBox.maxY = _boundBox.maxY, positiveBoundBox.maxZ = _boundBox.maxZ; + } else if (axisY) { + negativeBoundBox.minX = _boundBox.minX, negativeBoundBox.minY = _boundBox.minY, negativeBoundBox.minZ = _boundBox.minZ, negativeBoundBox.maxX = _boundBox.maxX, negativeBoundBox.maxY = coord, negativeBoundBox.maxZ = _boundBox.maxZ, positiveBoundBox.minX = _boundBox.minX, positiveBoundBox.minY = coord, positiveBoundBox.minZ = _boundBox.minZ, positiveBoundBox.maxX = _boundBox.maxX, positiveBoundBox.maxY = _boundBox.maxY, positiveBoundBox.maxZ = _boundBox.maxZ; + } else { + negativeBoundBox.minX = _boundBox.minX, negativeBoundBox.minY = _boundBox.minY, negativeBoundBox.minZ = _boundBox.minZ, negativeBoundBox.maxX = _boundBox.maxX, negativeBoundBox.maxY = _boundBox.maxY, negativeBoundBox.maxZ = coord, positiveBoundBox.minX = _boundBox.minX, positiveBoundBox.minY = _boundBox.minY, positiveBoundBox.minZ = coord, positiveBoundBox.maxX = _boundBox.maxX, positiveBoundBox.maxY = _boundBox.maxY, positiveBoundBox.maxZ = _boundBox.maxZ; + } + // Подготовка к ремапу + for (i = 0; i < numVertices;) verticesMap[i++] = -1; + // Разбиение BSP + if (bsp != null) { + splitNode(bsp, axisX, axisY, coord, threshold, negative, positive); + if (bsp.negative != null) negative.bsp = bsp.negative, bsp.negative = null; + if (bsp.positive != null) positive.bsp = bsp.positive, bsp.positive = null; + // Разбиение полигонов + } else { + var negativeIndices:Vector. = negative.indices, positiveIndices:Vector. = positive.indices; + var negativeVertices:Vector. = negative.vertices, negativeUVTs:Vector. = negative.uvts, positiveVertices:Vector. = positive.vertices, positiveUVTs:Vector. = positive.uvts; + for (var i:int = 0, j1:int = 0, j2:int = 0, k:int = 0, v1:int = 0, vi1:int = 0, v2:int = 0, vi2:int = 0, t:Number, uv:Number; i < indicesLength;) { + if (i == k) { + // Подготовка к разбиению + var infront:Boolean = false, behind:Boolean = false; + k = indices[i++] + i, negativeIndices[j1++] = positiveIndices[j2++] = 0; + // Первая точка ребра + var a:int = indices[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)], ac:Number = axisX ? ax : (axisY ? ay : az); + } + // Вторая точка ребра + var b:int = indices[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)], bc:Number = axisX ? bx : (axisY ? by : bz); + // Рассечение ребра + if (bc > coord + threshold && ac < coord - threshold || bc < coord - threshold && ac > coord + threshold) t = (ac - coord)/(ac - bc), negativeVertices[vi1] = positiveVertices[vi2] = ax + (bx - ax)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, negativeVertices[vi1] = positiveVertices[vi2] = ay + (by - ay)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, negativeVertices[vi1] = positiveVertices[vi2] = az + (bz - az)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = 0, negativeIndices[j1++] = v1++, positiveIndices[j2++] = v2++; + // Добавление точки + if (bc < coord - threshold) { + if (verticesMap[b] < 0) { + negativeVertices[vi1] = bx, negativeUVTs[vi1++] = uvts[bi], negativeVertices[vi1] = by, negativeUVTs[vi1++] = uvts[int(bi + 1)], negativeVertices[vi1] = bz, negativeUVTs[vi1++] = 0, negativeIndices[j1++] = verticesMap[b] = v1++; + } else { + negativeIndices[j1++] = verticesMap[b]; + } + behind = true; + } else if (bc > coord + threshold) { + if (verticesMap[b] < 0) { + positiveVertices[vi2] = bx, positiveUVTs[vi2++] = uvts[bi], positiveVertices[vi2] = by, positiveUVTs[vi2++] = uvts[int(bi + 1)], positiveVertices[vi2] = bz, positiveUVTs[vi2++] = 0, positiveIndices[j2++] = verticesMap[b] = v2++; + } else { + positiveIndices[j2++] = verticesMap[b]; + } + infront = true; + } else { + negativeVertices[vi1] = positiveVertices[vi2] = bx, negativeUVTs[vi1++] = positiveUVTs[vi2++] = uvts[bi], negativeVertices[vi1] = positiveVertices[vi2] = by, negativeUVTs[vi1++] = positiveUVTs[vi2++] = uvts[int(bi + 1)], negativeVertices[vi1] = positiveVertices[vi2] = bz, negativeUVTs[vi1++] = positiveUVTs[vi2++] = 0, negativeIndices[j1++] = v1++, positiveIndices[j2++] = v2++; + } + // Анализ разбиения + if (++i == k) { + if (behind && j1 > negative.indicesLength + 1) { + negativeIndices[negative.indicesLength] = j1 - negative.indicesLength - 1; + negative.indicesLength = j1, negative.numVertices = v1, negative.verticesLength = vi1; + } else { + j1 = negative.indicesLength, v1 = negative.numVertices, vi1 = negative.verticesLength; + } + if (infront && j2 > positive.indicesLength + 1 || !behind && !infront) { + positiveIndices[positive.indicesLength] = j2 - positive.indicesLength - 1; + positive.indicesLength = j2, positive.numVertices = v2, positive.verticesLength = vi2; + } else { + j2 = positive.indicesLength, v2 = positive.numVertices, vi2 = positive.verticesLength; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ac = bc; + } + } + } + // Копирование свойств + if (negative.numVertices > 0) { + // Копируем параметры + negative.priority = priority; + negative.texture = texture; + negative.smooth = smooth; + negative.repeatTexture = repeatTexture; + negative.debugResult = debugResult; + negative.viewAligned = viewAligned; + if (viewAligned) { + negative.textureMatrix.a = textureMatrix.a, negative.textureMatrix.b = textureMatrix.b, negative.textureMatrix.c = textureMatrix.c, negative.textureMatrix.d = textureMatrix.d, negative.textureMatrix.tx = textureMatrix.tx, negative.textureMatrix.ty = textureMatrix.ty; + negative.projectionX = projectionX; + negative.projectionY = projectionY; + negative.z = z; + } + negative.alpha = alpha; + negative.blendMode = blendMode; + negative.colorTransform = colorTransform; + negative.filters = filters; + negative.numCheckedOccluders = 0; + } + if (positive.numVertices > 0) { + // Копируем параметры + positive.priority = priority; + positive.texture = texture; + positive.smooth = smooth; + positive.repeatTexture = repeatTexture; + positive.debugResult = debugResult; + positive.viewAligned = viewAligned; + if (viewAligned) { + positive.textureMatrix.a = textureMatrix.a, positive.textureMatrix.b = textureMatrix.b, positive.textureMatrix.c = textureMatrix.c, positive.textureMatrix.d = textureMatrix.d, positive.textureMatrix.tx = textureMatrix.tx, positive.textureMatrix.ty = textureMatrix.ty; + positive.projectionX = projectionX; + positive.projectionY = projectionY; + positive.z = z; + } + positive.alpha = alpha; + positive.blendMode = blendMode; + positive.colorTransform = colorTransform; + positive.filters = filters; + positive.numCheckedOccluders = 0; + } + } + + static private var newNegativeNode:BSPNode = BSPNode.create(); + static private var newPositiveNode:BSPNode = BSPNode.create(); + private function splitNode(node:BSPNode, axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, negative:KDObject, positive:KDObject):void { + // Проход по дочерним нодам + if (node.negative != null) { + splitNode(node.negative, axisX, axisY, coord, threshold, negative, positive); + var negativeNegative:BSPNode = node.negative.negative, negativePositive:BSPNode = node.negative.positive; + node.negative.negative = null, node.negative.positive = null, node.negative.destroy(); + } + if (node.positive != null) { + splitNode(node.positive, axisX, axisY, coord, threshold, negative, positive); + var positiveNegative:BSPNode = node.positive.negative, positivePositive:BSPNode = node.positive.positive; + node.positive.negative = null, node.positive.positive = null, node.positive.destroy(); + } + // Разбиение + var nodePolygons:Vector. = node.polygons, nodePolygonsLength:int = node.polygonsLength, newNegativeNodePolygons:Vector. = newNegativeNode.polygons, newPositiveNodePolygons:Vector. = newPositiveNode.polygons; + var negativeVertices:Vector. = negative.vertices, negativeUVTs:Vector. = negative.uvts, positiveVertices:Vector. = positive.vertices, positiveUVTs:Vector. = positive.uvts; + for (var i:int = 0, j1:int = 0, j2:int = 0, k:int = 0, v1:int = negative.numVertices, vi1:int = negative.verticesLength, v2:int = positive.numVertices, vi2:int = positive.verticesLength, t:Number, uv:Number; i < nodePolygonsLength;) { + if (i == k) { + // Подготовка к разбиению + var infront:Boolean = false, behind:Boolean = false; + k = nodePolygons[i++] + i, newNegativeNodePolygons[j1++] = newPositiveNodePolygons[j2++] = 0; + // Первая точка ребра + var a:int = nodePolygons[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)], ac:Number = axisX ? ax : (axisY ? ay : az); + } + // Вторая точка ребра + var b:int = nodePolygons[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)], bc:Number = axisX ? bx : (axisY ? by : bz); + // Рассечение ребра + if (bc > coord + threshold && ac < coord - threshold || bc < coord - threshold && ac > coord + threshold) t = (ac - coord)/(ac - bc), negativeVertices[vi1] = positiveVertices[vi2] = ax + (bx - ax)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, negativeVertices[vi1] = positiveVertices[vi2] = ay + (by - ay)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, negativeVertices[vi1] = positiveVertices[vi2] = az + (bz - az)*t, negativeUVTs[vi1++] = positiveUVTs[vi2++] = 0, newNegativeNodePolygons[j1++] = v1++, newPositiveNodePolygons[j2++] = v2++; + // Добавление точки + if (bc < coord - threshold) { + if (verticesMap[b] < 0) { + negativeVertices[vi1] = bx, negativeUVTs[vi1++] = uvts[bi], negativeVertices[vi1] = by, negativeUVTs[vi1++] = uvts[int(bi + 1)], negativeVertices[vi1] = bz, negativeUVTs[vi1++] = 0, newNegativeNodePolygons[j1++] = verticesMap[b] = v1++; + } else { + newNegativeNodePolygons[j1++] = verticesMap[b]; + } + behind = true; + } else if (bc > coord + threshold) { + if (verticesMap[b] < 0) { + positiveVertices[vi2] = bx, positiveUVTs[vi2++] = uvts[bi], positiveVertices[vi2] = by, positiveUVTs[vi2++] = uvts[int(bi + 1)], positiveVertices[vi2] = bz, positiveUVTs[vi2++] = 0, newPositiveNodePolygons[j2++] = verticesMap[b] = v2++; + } else { + newPositiveNodePolygons[j2++] = verticesMap[b]; + } + infront = true; + } else { + negativeVertices[vi1] = positiveVertices[vi2] = bx, negativeUVTs[vi1++] = positiveUVTs[vi2++] = uvts[bi], negativeVertices[vi1] = positiveVertices[vi2] = by, negativeUVTs[vi1++] = positiveUVTs[vi2++] = uvts[int(bi + 1)], negativeVertices[vi1] = positiveVertices[vi2] = bz, negativeUVTs[vi1++] = positiveUVTs[vi2++] = 0, newNegativeNodePolygons[j1++] = v1++, newPositiveNodePolygons[j2++] = v2++; + } + // Анализ разбиения + if (++i == k) { + if (behind && j1 > newNegativeNode.polygonsLength + 1) { + newNegativeNodePolygons[newNegativeNode.polygonsLength] = j1 - newNegativeNode.polygonsLength - 1; + newNegativeNode.polygonsLength = j1, negative.numVertices = v1, negative.verticesLength = vi1; + } else { + j1 = newNegativeNode.polygonsLength, v1 = negative.numVertices, vi1 = negative.verticesLength; + } + if (infront && j2 > newPositiveNode.polygonsLength + 1 || !behind && !infront) { + newPositiveNodePolygons[newPositiveNode.polygonsLength] = j2 - newPositiveNode.polygonsLength - 1; + newPositiveNode.polygonsLength = j2, positive.numVertices = v2, positive.verticesLength = vi2; + } else { + j2 = newPositiveNode.polygonsLength, v2 = positive.numVertices, vi2 = positive.verticesLength; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ac = bc; + } + } + // Если в негативе от сплита есть полигоны ноды или полигоны дочерних нод + if (j1 > 0 || negativeNegative != null && positiveNegative != null) { + node.negative = newNegativeNode; + newNegativeNode = node.create(); + + node.negative.ax = node.ax, node.negative.ay = node.ay, node.negative.az = node.az; + node.negative.abx = node.abx, node.negative.aby = node.aby, node.negative.abz = node.abz; + node.negative.acx = node.acx, node.negative.acy = node.acy, node.negative.acz = node.acz; + + node.negative.negative = negativeNegative; + node.negative.positive = positiveNegative; + node.negative.cameraInfront = node.cameraInfront; + } else { + node.negative = (negativeNegative != null) ? negativeNegative : positiveNegative; + } + // Если в позитиве от сплита есть полигоны ноды или полигоны дочерних нод + if (j2 > 0 || negativePositive != null && positivePositive != null) { + node.positive = newPositiveNode; + newPositiveNode = node.create(); + + node.positive.ax = node.ax, node.positive.ay = node.ay, node.positive.az = node.az; + node.positive.abx = node.abx, node.positive.aby = node.aby, node.positive.abz = node.abz; + node.positive.acx = node.acx, node.positive.acy = node.acy, node.positive.acz = node.acz; + + node.positive.negative = negativePositive; + node.positive.positive = positivePositive; + node.positive.cameraInfront = node.cameraInfront; + } else { + node.positive = (negativePositive != null) ? negativePositive : positivePositive; + } + } + + /** + * @private + */ + alternativa3d function crop(axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, inPositive:Boolean, result:KDObject):void { + // Сплит баунда + var resultBoundBox:BoundBox = result._boundBox; + if (axisX) { + if (inPositive) resultBoundBox.minX = coord, resultBoundBox.maxX = _boundBox.maxX else resultBoundBox.minX = _boundBox.minX, resultBoundBox.maxX = coord; + resultBoundBox.minY = _boundBox.minY, resultBoundBox.maxY = _boundBox.maxY, resultBoundBox.minZ = _boundBox.minZ, resultBoundBox.maxZ = _boundBox.maxZ; + } else if (axisY) { + if (inPositive) resultBoundBox.minY = coord, resultBoundBox.maxY = _boundBox.maxY else resultBoundBox.minY = _boundBox.minY, resultBoundBox.maxY = coord; + resultBoundBox.minX = _boundBox.minX, resultBoundBox.maxX = _boundBox.maxX, resultBoundBox.minZ = _boundBox.minZ, resultBoundBox.maxZ = _boundBox.maxZ; + } else { + if (inPositive) resultBoundBox.minZ = coord, resultBoundBox.maxZ = _boundBox.maxZ else resultBoundBox.minZ = _boundBox.minZ, resultBoundBox.maxZ = coord; + resultBoundBox.minX = _boundBox.minX, resultBoundBox.maxX = _boundBox.maxX, resultBoundBox.minY = _boundBox.minY, resultBoundBox.maxY = _boundBox.maxY; + } + // Подготовка к ремапу + for (i = 0; i < numVertices;) verticesMap[i++] = -1; + // Разбиение BSP + if (bsp != null) { + result.bsp = cropNode(bsp, axisX, axisY, coord, threshold, inPositive, result); + // Разбиение полигонов + } else { + var resultIndices:Vector. = result.indices, resultVertices:Vector. = result.vertices, resultUVTs:Vector. = result.uvts; + for (var i:int = 0, j:int = 0, k:int = 0, v:int = 0, vi:int = 0, t:Number, uv:Number; i < indicesLength;) { + if (i == k) { + // Подготовка к разбиению + k = indices[i++] + i, resultIndices[j++] = 0; + // Первая точка ребра + var a:int = indices[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)], ac:Number = axisX ? ax : (axisY ? ay : az); + } + // Вторая точка ребра + var b:int = indices[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)], bc:Number = axisX ? bx : (axisY ? by : bz); + // Рассечение ребра + if (inPositive && (ac <= coord && bc > coord || bc <= coord && ac > coord) || !inPositive && (ac < coord && bc >= coord || bc < coord && ac >= coord)) t = (ac - coord)/(ac - bc), resultVertices[vi] = ax + (bx - ax)*t, resultUVTs[vi++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, resultVertices[vi] = ay + (by - ay)*t, resultUVTs[vi++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, resultVertices[vi] = az + (bz - az)*t, resultUVTs[vi++] = 0, resultIndices[j++] = v++; + // Добавление точки + if (inPositive && bc > coord || !inPositive && bc < coord) { + if (verticesMap[b] < 0) { + resultVertices[vi] = bx, resultUVTs[vi++] = uvts[bi], resultVertices[vi] = by, resultUVTs[vi++] = uvts[int(bi + 1)], resultVertices[vi] = bz, resultUVTs[vi++] = 0, resultIndices[j++] = verticesMap[b] = v++; + } else { + resultIndices[j++] = verticesMap[b]; + } + } + // Анализ разбиения + if (++i == k) { + if (j > result.indicesLength + 1) { + resultIndices[result.indicesLength] = j - result.indicesLength - 1; + result.indicesLength = j, result.numVertices = v, result.verticesLength = vi; + } else { + j = result.indicesLength; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ac = bc; + } + } + } + // Копирование свойств + if (result.numVertices > 0) { + // Копируем параметры + result.priority = priority; + result.texture = texture; + result.smooth = smooth; + result.repeatTexture = repeatTexture; + result.debugResult = debugResult; + result.viewAligned = viewAligned; + if (viewAligned) { + result.textureMatrix.a = textureMatrix.a, result.textureMatrix.b = textureMatrix.b, result.textureMatrix.c = textureMatrix.c, result.textureMatrix.d = textureMatrix.d, result.textureMatrix.tx = textureMatrix.tx, result.textureMatrix.ty = textureMatrix.ty; + result.projectionX = projectionX; + result.projectionY = projectionY; + result.z = z; + } + result.alpha = alpha; + result.blendMode = blendMode; + result.colorTransform = colorTransform; + result.filters = filters; + result.numCheckedOccluders = 0; + } + } + + private function cropNode(node:BSPNode, axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, inPositive:Boolean, result:KDObject):BSPNode { + if (node != null) { + // Проход по дочерним нодам + var negative:BSPNode = cropNode(node.negative, axisX, axisY, coord, threshold, inPositive, result); + var positive:BSPNode = cropNode(node.positive, axisX, axisY, coord, threshold, inPositive, result); + // Разбиение + var nodePolygons:Vector. = node.polygons, nodePolygonsLength:int = node.polygonsLength, newNodePolygons:Vector. = newNode.polygons; + var resultVertices:Vector. = result.vertices, resultUVTs:Vector. = result.uvts; + for (var i:int = 0, j:int = 0, k:int = 0, v:int = result.numVertices, vi:int = result.verticesLength, t:Number, uv:Number; i < nodePolygonsLength;) { + if (i == k) { + // Подготовка к разбиению + k = nodePolygons[i++] + i, newNodePolygons[j++] = 0; + // Первая точка ребра + var a:int = nodePolygons[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)], ac:Number = axisX ? ax : (axisY ? ay : az); + } + // Вторая точка ребра + var b:int = nodePolygons[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)], bc:Number = axisX ? bx : (axisY ? by : bz); + + // Рассечение ребра + if (inPositive && (ac <= coord && bc > coord || bc <= coord && ac > coord) || !inPositive && (ac < coord && bc >= coord || bc < coord && ac >= coord)) t = (ac - coord)/(ac - bc), resultVertices[vi] = ax + (bx - ax)*t, resultUVTs[vi++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, resultVertices[vi] = ay + (by - ay)*t, resultUVTs[vi++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, resultVertices[vi] = az + (bz - az)*t, resultUVTs[vi++] = 0, newNodePolygons[j++] = v++; + // Добавление точки + if (inPositive && bc > coord || !inPositive && bc < coord) { + if (verticesMap[b] < 0) { + resultVertices[vi] = bx, resultUVTs[vi++] = uvts[bi], resultVertices[vi] = by, resultUVTs[vi++] = uvts[int(bi + 1)], resultVertices[vi] = bz, resultUVTs[vi++] = 0, newNodePolygons[j++] = verticesMap[b] = v++; + } else { + newNodePolygons[j++] = verticesMap[b]; + } + } + // Анализ разбиения + if (++i == k) { + if (j > newNode.polygonsLength + 1) { + newNodePolygons[newNode.polygonsLength] = j - newNode.polygonsLength - 1; + newNode.polygonsLength = j, result.numVertices = v, result.verticesLength = vi; + } else { + j = newNode.polygonsLength; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ac = bc; + } + } + // Если нода видна или есть видимые дочерние ноды + if (j > 0 || negative != null && positive != null) { + var res:BSPNode = newNode; + newNode = node.create(); + + res.ax = node.ax, res.ay = node.ay, res.az = node.az; + res.abx = node.abx, res.aby = node.aby, res.abz = node.abz; + res.acx = node.acx, res.acy = node.acy, res.acz = node.acz; + + res.negative = negative; + res.positive = positive; + res.polygonsLength = j; + res.cameraInfront = node.cameraInfront; + return res; + } else { + return (negative != null) ? negative : positive; + } + } else return null; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Дебаг + var debugCanvas:Canvas = (debugResult > 0) ? parentCanvas.getChildCanvas(true, false) : null; + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + // Меш + if (!viewAligned) { + // Подрезка + drawIndicesLength = 0; + vertices.length = uvts.length = verticesLength; + projectedVertices.length = numVertices << 1; + // Если BSP + if (bsp != null) { + // Перевод координат в камеру + drawMatrix.identity(); + drawMatrix.prepend(object.cameraMatrix); + drawMatrix.append(camera.projectionMatrix); + Utils3D.projectVectors(drawMatrix, vertices, projectedVertices, uvts); + drawNode(bsp); + } else { + // Перевод координат в камеру + object.cameraMatrix.transformVectors(vertices, vertices); + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Сбор средних Z + for (var i:int = 0, j:int = 0, n:int = 0, num:int, k:int = 0, z:Number, a:int, b:int; i < indicesLength;) { + if (i == k) sortingMap[n] = i, k = (num = indices[i++]) + i, z = 0; + z += vertices[int(indices[i]*3 + 2)]; + if (++i == k) averageZ[n++] = z/num; + } + // Сортировка + var sortingStackIndex:int, sortingLeft:Number, sortingMedian:Number, sortingRight:Number, sortingMapIndex:int, l:int = 0, r:int = n - 1; + for (sortingStack[0] = l, sortingStack[1] = r, sortingStackIndex = 2; sortingStackIndex > 0;) { + j = r = sortingStack[--sortingStackIndex], i = l = sortingStack[--sortingStackIndex], sortingMedian = averageZ[(r + l) >> 1]; + for (;i <= j;) { + for (;(sortingLeft = averageZ[i]) > sortingMedian; i++); + for (;(sortingRight = averageZ[j]) < sortingMedian; j--); + if (i <= j) sortingMapIndex = sortingMap[i], sortingMap[i] = sortingMap[j], sortingMap[j] = sortingMapIndex, averageZ[i++] = sortingRight, averageZ[j--] = sortingLeft; + } + if (l < j) sortingStack[sortingStackIndex++] = l, sortingStack[sortingStackIndex++] = j; + if (i < r) sortingStack[sortingStackIndex++] = i, sortingStack[sortingStackIndex++] = r; + } + // Триангуляция + for (i = 0; i < n; i++) { + j = indices[k = sortingMap[i]] + ++k; + a = indices[k++], b = indices[k++]; + for (; k < j;) drawIndices[drawIndicesLength++] = a, drawIndices[drawIndicesLength++] = b, drawIndices[drawIndicesLength++] = b = indices[k++]; + } + } + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "positive"); + // Дебаг + if (debugResult & Debug.EDGES) { + debugCanvas.gfx.lineStyle(0, 0xFFFFFF); + debugCanvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "positive"); + } + // Спрайт + } else { + // Переводим координаты в камеру + vertices.length = verticesLength; + object.cameraMatrix.transformVectors(vertices, vertices); + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + canvas.gfx.moveTo(vertices[verticesLength - 3]*projectionX, vertices[verticesLength - 2]*projectionY); + for (i = 0; i < verticesLength; i++) canvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + // Дебаг + if (debugResult & Debug.EDGES) { + debugCanvas.gfx.lineStyle(0, 0xFFFFFF); + debugCanvas.gfx.moveTo(vertices[verticesLength - 3]*projectionX, vertices[verticesLength - 2]*projectionY); + for (i = 0; i < verticesLength; i++) debugCanvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + } + } + // Дебаг + if (debugResult & Debug.BOUNDS) { + var containerBoundBox:BoundBox = object._boundBox; + object._boundBox = _boundBox; + object.drawBoundBox(camera, debugCanvas, 0x99FF00); + object._boundBox = containerBoundBox; + } + } + + private function drawNode(node:BSPNode):void { + if (node != null) { + if (node.cameraInfront) { + drawNode(node.negative); + for (var i:int = 0, k:int = 0, a:int, b:int, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength;) { + if (i == k) k = polygons[i++] + i, a = polygons[i++], b = polygons[i++]; + drawIndices[drawIndicesLength++] = a, drawIndices[drawIndicesLength++] = b, drawIndices[drawIndicesLength++] = b = polygons[i++]; + } + drawNode(node.positive); + } else { + drawNode(node.positive); + drawNode(node.negative); + } + } + } + + private function drawPart(camera:Camera3D, object:Object3D, parentCanvas:Canvas, indices:Vector., begin:int, end:int):void { + // Дебаг + var debugCanvas:Canvas = (debugResult > 0) ? parentCanvas.getChildCanvas(true, false) : null; + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + // Меш + if (!viewAligned) { + drawIndicesLength = 0; + // Триангуляция + for (var i:int = begin, k:int = begin, a:int, b:int, vi:int; i < end;) { + if (i == k) k = indices[i++] + i, a = indices[i++], b = indices[i++]; + drawIndices[drawIndicesLength++] = a, drawIndices[drawIndicesLength++] = b, drawIndices[drawIndicesLength++] = b = indices[i++]; + } + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "positive"); + // Дебаг + if (debugResult & Debug.EDGES) { + debugCanvas.gfx.lineStyle(0, 0xFF9999); + debugCanvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "positive"); + } + // Спрайт + } else { + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + for (i = begin, k = begin; i < end; i++) { + if (i == k) k = indices[i++] + i, canvas.gfx.moveTo(vertices[vi = int(indices[int(k - 1)]*3)]*projectionX, vertices[++vi]*projectionY); + canvas.gfx.lineTo(vertices[vi = int(indices[i]*3)]*projectionX, vertices[++vi]*projectionY); + } + // Дебаг + if (debugResult & Debug.EDGES) { + debugCanvas.gfx.lineStyle(0, 0xFF9999); + for (i = begin, k = begin; i < end; i++) { + if (i == k) k = indices[i++] + i, debugCanvas.gfx.moveTo(vertices[vi = int(indices[int(k - 1)]*3)]*projectionX, vertices[++vi]*projectionY); + debugCanvas.gfx.lineTo(vertices[vi = int(indices[i]*3)]*projectionX, vertices[++vi]*projectionY); + } + } + } + // Дебаг + if (debugResult & Debug.BOUNDS) { + var containerBoundBox:BoundBox = object._boundBox; + object._boundBox = _boundBox; + object.drawBoundBox(camera, debugCanvas, 0xFF0000); + object._boundBox = containerBoundBox; + } + } + + /** + * @private + */ + static alternativa3d function drawConflict(camera:Camera3D, object:Object3D, canvas:Canvas, kdObjects:Vector., begin:int, end:int, threshold:Number):void { + var i:int, j:int, k:int, n:int, mi:int, num:int, v:int, z:Number, index:int; + var sortingStackIndex:int, sortingLeft:Number, sortingMedian:Number, sortingRight:Number, sortingObjectLeft:KDObject, sortingObjectMedian:int, sortingObjectRight:KDObject, sortingMapIndex:int, l:int, r:int; + // Сортировка объектов по приоритету + for (l = begin, r = end - 1, sortingStack[0] = l, sortingStack[1] = r, sortingStackIndex = 2; sortingStackIndex > 0;) { + j = r = sortingStack[--sortingStackIndex], i = l = sortingStack[--sortingStackIndex], sortingObjectMedian = (kdObjects[(r + l) >> 1] as KDObject).priority; + for (;i <= j;) { + for (;(sortingObjectLeft = kdObjects[i]).priority > sortingObjectMedian; i++); + for (;(sortingObjectRight = kdObjects[j]).priority < sortingObjectMedian; j--); + if (i <= j) kdObjects[i++] = sortingObjectRight, kdObjects[j--] = sortingObjectLeft; + } + if (l < j) sortingStack[sortingStackIndex++] = l, sortingStack[sortingStackIndex++] = j; + if (i < r) sortingStack[sortingStackIndex++] = i, sortingStack[sortingStackIndex++] = r; + } + + // Сбор фрагментов, сортирующихся по средним Z + for (i = begin, index = end, n = 0, sourceFragmentsLength = 0; i < end; i++) { + var kdObject:KDObject = kdObjects[i], indices:Vector. = kdObject.indices, indicesLength:int = kdObject.indicesLength, vertices:Vector. = kdObject.vertices; + if (kdObject.bsp == null) { + // Сбор средних Z + for (j = 0, k = 0; j < indicesLength;) { + if (j == k) sortingMap[n] = sourceFragmentsLength, k = (num = indices[j++]) + j, z = 0, sourceFragments[sourceFragmentsLength++] = (i << 16) + num; + z += vertices[int((v = indices[j])*3 + 2)], sourceFragments[sourceFragmentsLength++] = v; + if (++j == k) averageZ[n++] = z/num; + } + } else { + index = i; + break; + } + } + // Если есть фрагмены, сортирующиеся по средним Z + if (index > begin) { + // Сортировка фрагментов разных объектов + for (l = 0, r = n - 1, sortingStack[0] = l, sortingStack[1] = r, sortingStackIndex = 2; sortingStackIndex > 0;) { + j = r = sortingStack[--sortingStackIndex], i = l = sortingStack[--sortingStackIndex], sortingMedian = averageZ[(r + l) >> 1]; + for (;i <= j;) { + for (;(sortingLeft = averageZ[i]) > sortingMedian; i++); + for (;(sortingRight = averageZ[j]) < sortingMedian; j--); + if (i <= j) sortingMapIndex = sortingMap[i], sortingMap[i] = sortingMap[j], sortingMap[j] = sortingMapIndex, averageZ[i++] = sortingRight, averageZ[j--] = sortingLeft; + } + if (l < j) sortingStack[sortingStackIndex++] = l, sortingStack[sortingStackIndex++] = j; + if (i < r) sortingStack[sortingStackIndex++] = i, sortingStack[sortingStackIndex++] = r; + } + // Перестановка фрагментов по карте сортировки + for (i = 0; i < n; i++) { + num = ((resultFragments[resultFragmentsLength++] = sourceFragments[k = sortingMap[i]]) & 0xFFFF) + ++k; + for (; k < num;) resultFragments[resultFragmentsLength++] = sourceFragments[k++]; + } + } + // Итоговый сбор последовательности фрагментов + for (i = index; i < end; i++) { + sourceFragments = resultFragments, sourceFragmentsLength = resultFragmentsLength, sourceFragmentsRealLength = sourceFragments.length; + resultFragments = (sourceFragments == fragments1) ? fragments2 : fragments1, resultFragmentsLength = 0; + kdObject = kdObjects[i]; + kdObject.collectNode(kdObject.bsp, kdObjects, i << 16, threshold, 0, sourceFragmentsLength); + } + // Проецирование + for (i = begin; i < end; i++) { + kdObject = kdObjects[i]; + if (!kdObject.viewAligned) { + kdObject.uvts.length = kdObject.verticesLength; + kdObject.projectedVertices.length = kdObject.numVertices << 1; + Utils3D.projectVectors(camera.projectionMatrix, kdObject.vertices, kdObject.projectedVertices, kdObject.uvts); + } + } + /* + // Сбор отрисовочных вызовов + for (k = 0, i = -1, sourceFragmentsLength = 0; k < resultFragmentsLength;) { + mi = resultFragments[k], j = mi >> 16, num = mi & 0xFFFF; + if (i != j) i = j, sourceFragments[sourceFragmentsLength++] = (j << 16) + k; + resultFragments[k] = num, k = num + ++k; + } + // Отрисовка + for (i = sourceFragmentsLength - 1; i >= 0; i--) (kdObjects[(mi = sourceFragments[i]) >> 16] as KDObject).drawPart(camera, object, canvas, resultFragments, k = mi & 0xFFFF, resultFragmentsLength), resultFragmentsLength = k; + */ + // Сбор отрисовочных вызовов + for (k = 0, i = -1, sourceFragmentsLength = 0; k < resultFragmentsLength;) { + mi = resultFragments[k], j = mi >> 16, num = mi & 0xFFFF; + if (i != j) i = j, sourceFragments[sourceFragmentsLength++] = k, sourceFragments[sourceFragmentsLength++] = j; + resultFragments[k] = num, k = num + ++k; + } + // Отрисовка + for (i = sourceFragmentsLength - 1; i >= 0;) { + j = sourceFragments[i--]; + (kdObjects[j] as KDObject).drawPart(camera, object, canvas, resultFragments, k = sourceFragments[i--], resultFragmentsLength); + resultFragmentsLength = k; + } + // Зачистка + for (i = begin; i < end; i++) (kdObjects[i] as KDObject).destroy(), kdObjects[i] = null; + } + + static private const fragments1:Vector. = new Vector.(); + static private const fragments2:Vector. = new Vector.(); + + static private var sourceFragments:Vector. = fragments1; + static private var sourceFragmentsLength:int = 0; + static private var sourceFragmentsRealLength:int = 0; + + static private var resultFragments:Vector. = fragments2; + static private var resultFragmentsLength:int = 0; + + private function collectNode(node:BSPNode, kdObjects:Vector., index:int, threshold:Number, begin:int, end:int):void { + if (node != null) { + // Разделение кучи + if (end > begin) { + var normalX:Number = node.acz*node.aby - node.acy*node.abz; + var normalY:Number = node.acx*node.abz - node.acz*node.abx; + var normalZ:Number = node.acy*node.abx - node.acx*node.aby; + var normalL:Number = Math.sqrt(normalX*normalX + normalY*normalY + normalZ*normalZ); + if (normalL > 0) normalX /= normalL, normalY /= normalL, normalZ /= normalL; + var offset:Number = node.ax*normalX + node.ay*normalY + node.az*normalZ; + var reserve:int = end - begin + ((end - begin) >> 2); + var negativeBegin:int = sourceFragmentsLength; + var negativeEnd:int = negativeBegin; + var positiveBegin:int = sourceFragmentsLength + reserve; + var positiveEnd:int = positiveBegin; + if ((sourceFragmentsLength = positiveBegin + reserve) > sourceFragmentsRealLength) sourceFragments.length = sourceFragmentsRealLength = sourceFragmentsLength; + // Перебираем грани разных объектов + for (var i:int = begin, j1:int = negativeEnd, j2:int = positiveEnd, k:int = begin, t:Number, uv:Number; i < end;) { + if (i == k) { + // Подготовка к разбиению + var mi:int = sourceFragments[i], oi:int = mi >> 16, num:int = mi & 0xFFFF; + var kdObject:KDObject = kdObjects[oi], kdObjectVertices:Vector. = kdObject.vertices, kdObjectUVTs:Vector. = kdObject.uvts; + var v:int = kdObject.numVertices, vi:int = kdObject.verticesLength; + var infront:Boolean = false, behind:Boolean = false; + k = num + ++i, j1++, j2++, oi = oi << 16; + // Первая точка ребра + var a:int = sourceFragments[int(k - 1)], ai:int = a*3; + var ax:Number = kdObjectVertices[ai], ay:Number = kdObjectVertices[int(ai + 1)], az:Number = kdObjectVertices[int(ai + 2)]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + } + // Вторая точка ребра + var b:int = sourceFragments[i], bi:int = b*3; + var bx:Number = kdObjectVertices[bi], by:Number = kdObjectVertices[int(bi + 1)], bz:Number = kdObjectVertices[int(bi + 2)]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) t = ao/(ao - bo), kdObjectVertices[vi] = ax + (bx - ax)*t, kdObjectUVTs[vi++] = (uv = kdObjectUVTs[ai]) + (kdObjectUVTs[bi] - uv)*t, kdObjectVertices[vi] = ay + (by - ay)*t, kdObjectUVTs[vi++] = (uv = kdObjectUVTs[int(ai + 1)]) + (kdObjectUVTs[int(bi + 1)] - uv)*t, kdObjectVertices[vi] = az + (bz - az)*t, kdObjectUVTs[vi++] = 0, sourceFragments[j1++] = sourceFragments[j2++] = v++; + // Добавление точки + if (bo < -threshold) { + sourceFragments[j1++] = b, behind = true; + } else if (bo > threshold) { + sourceFragments[j2++] = b, infront = true; + } else { + sourceFragments[j1++] = sourceFragments[j2++] = b; + } + // Анализ разбиения + if (++i == k) { + if (behind && infront) kdObject.numVertices = v, kdObject.verticesLength = vi; + if (behind && j1 > negativeEnd + 1) { + sourceFragments[negativeEnd] = oi + j1 - negativeEnd - 1, negativeEnd = j1; + } else { + j1 = negativeEnd; + } + if (infront && j2 > positiveEnd + 1 || !behind && !infront) { + sourceFragments[positiveEnd] = oi + j2 - positiveEnd - 1, positiveEnd = j2; + } else { + j2 = positiveEnd; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ao = bo; + } + } + } + // Проход по дочерним нодам + if (node.cameraInfront) { + collectNode(node.negative, kdObjects, index, threshold, negativeBegin, negativeEnd); + // Сбор фрагментов ноды + var polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; + for (i = 0, k = 0; i < polygonsLength; i++) { + if (i == k) num = polygons[i], k = num + ++i, resultFragments[resultFragmentsLength++] = index + num; + resultFragments[resultFragmentsLength++] = polygons[i]; + } + collectNode(node.positive, kdObjects, index, threshold, positiveBegin, positiveEnd); + } else { + collectNode(node.positive, kdObjects, index, threshold, positiveBegin, positiveEnd); + collectNode(node.negative, kdObjects, index, threshold, negativeBegin, negativeEnd); + } + } else { + for (i = begin, k = begin; i < end; i++) resultFragments[resultFragmentsLength++] = sourceFragments[i]; + } + } + + } +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/LOD.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..c19bed3 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,56 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект, имеющий набор объектов с разной детализацией. + * При отрисовке, он выбирает в зависимости от расстояния от камеры + * объект с нужной детализацией и отрисовывает его вместо себя. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class LOD extends Object3D { + + /** + * Объекты с разной детализацией + */ + public var lodObjects:Vector.; + /** + * Расстояния до камеры соответствующие объектам с разной детализацией + */ + public var lodDistances:Vector.; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var cameraDistance:Number = object.cameraMatrix.position.length; + + // Поиск ближайшего лода + var min:Number = Infinity; + var length:uint = lodObjects.length; + var lod:Object3D; + for (var i:uint = 0; i < length; i++) { + var d:Number = Math.abs(cameraDistance - lodDistances[i]); + if (d < min) { + min = d; + lod = lodObjects[i]; + } + } + if (camera.debugMode) lod.debug(camera, object, parentCanvas); + lod.draw(camera, object, parentCanvas); + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return (lodObjects[0] as Object3D).calculateBoundBox(matrix, boundBox); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Mesh.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..ea2ef77 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,2042 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BSPNode; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import flash.utils.Dictionary; + import alternativa.engine3d.core.Debug; + import flash.utils.getTimer; + + use namespace alternativa3d; + + /** + * Полигональный объект + */ + public class Mesh extends Object3D { + + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; // Режим полигонов + + /** + * Количество вершин + */ + public var numVertices:int = 0; + /** + * Количество граней + */ + public var numFaces:int = 0; + /** + * Вершины в виде x, y, z + */ + public var vertices:Vector.; + /** + * Индексы вершин + */ + public var indices:Vector.; + /** + * UV-координаты в виде u, v, t + */ + public var uvts:Vector.; + /** + * UV-координаты в виде u, v + */ + public var uvs:Vector.; // Нужен только для отрисовки без перспективной коррекции + + public var texture:BitmapData; + public var smooth:Boolean = false; + public var repeatTexture:Boolean = true; + public var perspectiveCorrection:Boolean = false; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 1 - по граням + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; // 0 - весь объект, 1 - по граням, 2 - с обрезкой + /** + * Режим отсечения граней по направлению к камере + * 0 - отсечение происходит только на этапе отрисовки треугольников внутри drawTriangles + * 1 - отсечение по предрасчитанным нормалям. Для расчёта нормалей нужен calculateNormals() + * 2 - отсечение по динамически расчитываемым временным нормалям + */ + public var backfaceCulling:int = 0; // 0 - нативный, 1 - по предрасчитанным нормалям, 2 - динамический + /** + * Режим сортировки полигонов + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + */ + public var sorting:int = 0; // 0 - без сортировки, 1 - Z-сортировка, 2 - BSP + /** + * Применение мипмаппинга + * 0 - без мипмаппинга + * 1 - мипмаппинг по удалённости от камеры. Требуется установка свойства mipMap + */ + public var mipMapping:int = 0; // 0 - без мипмаппинга, 1 - по дальности от центра + + public var mipMap:MipMap; + /** + * Нормали в виде: x, y, z, offset + */ + public var normals:Vector.; // На каждую нормаль - 4 значения, XYZ + offset + /** + * Корневой узел BSP-дерева + */ + public var bsp:BSPNode; + /** + * Геометрическая погрешность при расчёте BSP-дерева + */ + public var threshold:Number = 0.1; + + /** + * @private + */ + alternativa3d var cameraVertices:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var projectedVertices:Vector. = new Vector.(); + + // Вспомогательные вектора + static private const polygon:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private const sortingMap:Vector. = new Vector.(); + static private const sortingAverageZ:Vector. = new Vector.(); + static private const indices1:Vector. = new Vector.(); + static private const indices2:Vector. = new Vector.(); + static private const fragments:Vector. = new Vector.(); + static private var fragmentsRealLength:int = 0; + static private var fragmentsLength:int; + protected var sourceIndices:Vector.; + protected var sourceIndicesLength:int; + protected var resultIndices:Vector.; + protected var resultIndicesLength:int; + + public function createEmptyGeometry(numVertices:uint, numFaces:uint):void { + this.numVertices = numVertices; + this.numFaces = numFaces; + vertices = new Vector.(numVertices*3); + indices = new Vector.(numFaces*3); + uvts = new Vector.(numVertices*3); + uvs = new Vector.(numVertices << 1); + } + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return (texture != null || mipMap != null) && numFaces > 0; + } + + static alternativa3d const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraCenter:Vector. = new Vector.(3, true); + /** + * @private + */ + alternativa3d var cameraX:Number; + /** + * @private + */ + alternativa3d var cameraY:Number; + /** + * @private + */ + alternativa3d var cameraZ:Number; + /** + * @private + */ + alternativa3d function calculateInverseCameraMatrix(matrix:Matrix3D):void { + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(matrix); + inverseCameraMatrix.invert(); + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + inverseCameraMatrix.transformVectors(cameraCenter, cameraCenter); + cameraX = cameraCenter[0], cameraY = cameraCenter[1], cameraZ = cameraCenter[2]; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Выход по объектному клиппингу + if (clipping == 0 && (object.culling & 1)) return; + // Подготовка к отсечению по предрасчитанным нормалям + if (backfaceCulling == 2 || sorting == 2) calculateInverseCameraMatrix(object.cameraMatrix); + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Текущий тип клиппинга: 0 - целиком, 1 - по граням, 2 - подрезка + var clippingType:int = (object.culling == 0 || clipping == 0) ? 0 : clipping; + // Полная отрисовка + if (clippingType == 0) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (backfaceCulling == 0 || backfaceCulling == 1) { + if (poly) triangulate(); + } else { + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) triangulate(); + } else { + backfaceCullTriangles(); + } + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (backfaceCulling == 0 || backfaceCulling == 1) { + if (poly) sortPolygons() else sortTriangles(); + } else { + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) sortPolygons(); + } else { + backfaceCullTriangles(); + if (resultIndicesLength > 0) sortTriangles(); + } + } + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodeTriangles(bsp); + } + // Куллинг + } else if (clippingType == 1) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + cullNode(bsp, object.culling, camera.nearClipping, camera.farClipping); + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + clipNode(bsp, object.culling, camera.nearClipping, camera.farClipping, numVertices); + } + } + // Отрисовка + if (resultIndicesLength > 0) { + // Подрезка + resultIndices.length = resultIndicesLength; + // Количество отрисовываемых треугольников + camera.numTriangles += resultIndicesLength/3; + // Коррекция области перерисовки + //correctRedrawRegion(object.culling, camera); + // Проецирование + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Коррекция области перерисовки + if (clippingType > 0) cropVertices(object.culling, camera); + // Отрисовка графики + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.gfx.beginBitmapFill((mipMapping == 0) ? texture : getMipTexture(camera, object), null, repeatTexture, smooth); + canvas.gfx.drawTriangles(projectedVertices, resultIndices, perspectiveCorrection ? uvts : uvs, (backfaceCulling == 1) ? "positive" : "none"); + } + // Подрезка вершин и UV + if (clippingType == 2) { + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = uvs.length = numVertices << 1; + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + var i:int, j:int, k:int, n:int, vi:int, a:int, b:int, c:int, x:Number, y:Number, z:Number, indicesLength:int, backface:int = backfaceCulling; + if (debugResult & Debug.EDGES || debugResult & Debug.VERTICES || debugResult & Debug.NORMALS) { + // Трансформация + calculateInverseCameraMatrix(object.cameraMatrix); + object.cameraMatrix.transformVectors(vertices, cameraVertices); + if (clipping != 0 || !(object.culling & 1)) { + // Полная отрисовка + if (object.culling == 0 || clipping == 0) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (backfaceCulling > 0) if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + } + // Куллинг + } else if (clipping == 1) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (backfaceCulling == 1) if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + if (poly) cullPolygons(object.culling, camera.nearClipping, camera.farClipping) else cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 1; + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (backfaceCulling == 1) if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + if (poly) clipPolygons(object.culling, camera.nearClipping, camera.farClipping) else clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 1; + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + } + // Отрисовка + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Рёбра + if (debugResult & Debug.EDGES) { + // Полигоны + if (poly || sorting == 2) { + for (i = 0; i < resultIndicesLength; i = k) { + k = resultIndices[i++] + i; + canvas.gfx.lineStyle(0, 0xFFFFFF, 0.3); + for (j = i + 2; j < k - 1; j++) { + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[i] << 1], projectedVertices[++vi]); + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + canvas.gfx.lineStyle(0, 0xFFFFFF); + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[int(k - 1)] << 1], projectedVertices[++vi]); + for (j = i; j < k; j++) { + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + } + // Треугольники + } else { + canvas.gfx.lineStyle(0, 0xFFFFFF); + for (i = 0; i < resultIndicesLength;) { + a = resultIndices[i++], b = resultIndices[i++], c = resultIndices[i++]; + canvas.gfx.moveTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[b << 1], projectedVertices[(b << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[c << 1], projectedVertices[(c << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + } + } + } + // Вершины + if (debugResult & Debug.VERTICES) { + canvas.gfx.lineStyle(); + for (i = 0; i < numVertices; i++) sortingMap[i] = 0; + for (i = 0, k = 0; i < resultIndicesLength; i++) { + if (i == k) k = (poly || sorting == 2) ? (resultIndices[i++] + i) : (i + 3); + if (resultIndices[i] < numVertices) sortingMap[resultIndices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] != 0) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + // Изолированные вершины + for (i = 0, k = 0, indicesLength = indices.length; i < indicesLength; i++) { + if (i == k) k = (poly) ? (indices[i++] + i) : (i + 3); + if (indices[i] < numVertices) sortingMap[indices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] == 0 && (cameraVertices[i*3 + 2]) > camera.nearClipping) { + canvas.gfx.beginFill(0xFF0000); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 3); + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + } + // Нормали + if (debugResult & Debug.NORMALS) { + if (object.culling != 0 && clipping == 2) resultIndices = sourceIndices, resultIndicesLength = sourceIndicesLength; + for (i = 0, k = 0, n = 0; i < resultIndicesLength; i = k) { + k = (poly || sorting == 2) ? (resultIndices[i++] + i) : (i + 3); + x = y = z = 0; + for (j = i; j < k; j++) { + x += cameraVertices[vi = int(resultIndices[j]*3)]; + y += cameraVertices[++vi]; + z += cameraVertices[++vi]; + } + x /= (k - i); + y /= (k - i); + z /= (k - i); + if (clipping == 2 && (z < camera.nearClipping || z > camera.farClipping || z < -x || z < x || z < -y || z < y)) continue; + projectedVertices[n++] = x; + projectedVertices[n++] = y; + projectedVertices[n++] = z; + var ax:Number = cameraVertices[vi = int(resultIndices[i]*3)]; + var ay:Number = cameraVertices[++vi]; + var az:Number = cameraVertices[++vi]; + var abx:Number = cameraVertices[vi = int(resultIndices[int(i + 1)]*3)] - ax; + var aby:Number = cameraVertices[++vi] - ay; + var abz:Number = cameraVertices[++vi] - az; + var acx:Number = cameraVertices[vi = int(resultIndices[int(i + 2)]*3)] - ax; + var acy:Number = cameraVertices[++vi] - ay; + var acz:Number = cameraVertices[++vi] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + if (nl != 0) { + nx /= nl; + ny /= nl; + nz /= nl; + } + var projectionZ:Number = camera.focalLength/z; + projectedVertices[n++] = x + 15*nx*camera.perspectiveScaleX/projectionZ; + projectedVertices[n++] = y + 15*ny*camera.perspectiveScaleY/projectionZ; + projectedVertices[n++] = z + 15*nz/projectionZ; + } + projectedVertices.length = n; + Utils3D.projectVectors(camera.projectionMatrix, projectedVertices, projectedVertices, uvts); + n = n/3 << 1; + for (i = 0; i < n;) { + canvas.gfx.lineStyle(); + canvas.gfx.beginFill(0x00FFFF); + canvas.gfx.drawCircle(projectedVertices[i], projectedVertices[int(i + 1)], 1.5); + canvas.gfx.lineStyle(0, 0x00FFFF); + canvas.gfx.moveTo(projectedVertices[i++], projectedVertices[i++]); + canvas.gfx.lineTo(projectedVertices[i++], projectedVertices[i++]); + } + } + } + // Подрезка + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = uvs.length = numVertices << 1; + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + // Сбор треугольников BSP-дерева + private function collectNodeTriangles(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodeTriangles(node.negative); + for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.positive != null) collectNodeTriangles(node.positive); + } else { + if (node.positive != null) collectNodeTriangles(node.positive); + if (node.negative != null) collectNodeTriangles(node.negative); + } + } + + /*private function collectBSPTriangles():void { + var direction:Boolean = true, node:BSPNode = bsp, negative:BSPNode, positive:BSPNode, prev:BSPNode; + while (node != null) { + if (direction) { + if (node.cameraInfront = cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + negative = node.negative; + if (negative != null) negative.prev = node, node = negative else direction = false; + } else { + positive = node.positive; + if (positive != null) positive.prev = node, node = positive else direction = false; + } + } else { + //if (node.cameraInfront || backfaceCulling == 0) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.cameraInfront) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + prev = node.prev, node.prev = null; + if (node.cameraInfront) { + positive = node.positive; + if (positive != null) positive.prev = prev, node = positive, direction = true else node = prev; + } else { + negative = node.negative; + if (negative != null) negative.prev = prev, node = negative, direction = true else node = prev; + } + } + } + }*/ + + // Сбор полигонов BSP-дерева + private function collectNodePolygons(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodePolygons(node.negative); + for (var i:int = 0, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength;) resultIndices[resultIndicesLength++] = polygons[i++]; + if (node.positive != null) collectNodePolygons(node.positive); + } else { + if (node.positive != null) collectNodePolygons(node.positive); + if (node.negative != null) collectNodePolygons(node.negative); + } + } + + // Куллинг треугольников BSP-дерева + private function cullNode(node:BSPNode, culling:int, near:Number, far:Number):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) cullNode(node.negative, culling, near, far); + // Проход по ноде + for (var i:int = 0, x:Boolean = (culling & 12) > 0, y:Boolean = (culling & 48) > 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) { + var a:int = triangles[i++], b:int = triangles[i++], c:int = triangles[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || az <= -ax && bz <= -bx && cz <= -cx || az <= ax && bz <= bx && cz <= cx || az <= -ay && bz <= -by && cz <= -cy || az <= ay && bz <= by && cz <= cy) continue; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + if (node.positive != null) cullNode(node.positive, culling, near, far); + } else { + if (node.positive != null) cullNode(node.positive, culling, near, far); + if (node.negative != null) cullNode(node.negative, culling, near, far); + } + } + + // Клиппинг полигонов BSP-дерева + private function clipNode(node:BSPNode, culling:int, near:Number, far:Number, v:int):int { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + // Проход по ноде + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, num1:int, num2:int = 0, vi:int, vj:int, t:Number, au:Number, av:Number, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength; i = k) { + k = (num1 = polygons[i++]) + i; + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + // Триангуляция + for (j = i, a = polygons[j++], b = polygons[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygons[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = polygons[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + } else { + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + } + return v; + } + + // Отсечение треугольников по нормалям + private function backfaceCullTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0; + if (backfaceCulling == 2) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++] else i += 3; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++] else i += 3; + } + } + } + + // Отсечение полигонов по нормалям + private function backfaceCullPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0, k:int = 0; + if (backfaceCulling == 2) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } + } + + // Отсечение треугольников по пирамиде видимости + private function cullTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 3, y:Boolean = (culling & 48) > 0 || backfaceCulling == 3; + for (var i:int = 0, ni:int = 0; i < sourceIndicesLength;) { + if (backfaceCulling == 2 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || az <= -ax && bz <= -bx && cz <= -cx || az <= ax && bz <= bx && cz <= cx || az <= -ay && bz <= -by && cz <= -cy || az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 3) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + } + + // Отсечение полигонов по пирамиде видимости + private function cullPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, j:int, ni:int, num:int, vi:int; i < sourceIndicesLength; i = k) { + k = (num = sourceIndices[i++]) + i; + if (backfaceCulling == 2 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 3) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + if (culling & 1) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] <= near) break; + if (j < k) continue; + } + if (culling & 2) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] < far) break; + if (j == k) continue; + } + if (culling & 4) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 8) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 16) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + if (culling & 32) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + resultIndices[resultIndicesLength++] = num; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } + } + + private function clipTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 3, y:Boolean = (culling & 48) > 0 || backfaceCulling == 3; + for (var i:int = 0, j:int, ni:int = 0, v:int = numVertices, vi:int, vj:int, t:Number, au:Number, av:Number, num1:int, num2:int; i < sourceIndicesLength;) { + if (backfaceCulling == 2 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + // За пределами пирамиды видимости + if (az <= near && bz <= near && cz <= near || az >= far && bz >= far && cz >= far || az <= -ax && bz <= -bx && cz <= -cx || az <= ax && bz <= bx && cz <= cx || az <= -ay && bz <= -by && cz <= -cy || az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 3) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Полностью в пирамиде видимости + var insideNear:Boolean = !(culling & 1) || az > near && bz > near && cz > near, insideFar:Boolean = !(culling & 2) || az < far && bz < far && cz < far, insideLeft:Boolean = !(culling & 4) || az > -ax && bz > -bx && cz > -cx, insideRight:Boolean = !(culling & 8) || az > ax && bz > bx && cz > cx, insideTop:Boolean = !(culling & 16) || az > -ay && bz > -by && cz > -cy, insideBottom:Boolean = !(culling & 32) || az > ay && bz > by && cz > cy; + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } else { + // Заполняем полигон + polygon[0] = a, polygon[1] = b, polygon[2] = c, num1 = 3, num2 = 0; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + } + + private function clipPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, ni:int = 0, num1:int, num2:int = 0, v:int = numVertices, vi:int, vj:int, t:Number, au:Number, av:Number; i < sourceIndicesLength; i = k) { + k = (num1 = sourceIndices[i++]) + i; + if (backfaceCulling == 2 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 3) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Отсечение + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = num1; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = sourceIndices[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = uvs[vj = v++ << 1] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[++vj] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Копирование полигона + resultIndices[resultIndicesLength++] = num1; + for (j = 0; j < num1;) resultIndices[resultIndicesLength++] = polygon[j++]; + } + } + } + + // Триангуляция полигонов для отрисовки + private function triangulate():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, a:int, b:int; i < sourceIndicesLength;) { + if (i == k) k = sourceIndices[i++] + i, a = sourceIndices[i++], b = sourceIndices[i++]; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[i++]; + } + } + + private function sortTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, n:int, k:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) map[n = int(i/3)] = i, averageZ[n] = cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)]; + // Сортировка + sortAverageZ(n++); + // Перестановка граней по карте сортировки + for (i = 0; i < n;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i++]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + //flashSort(++n); + //for (i = n - 1; i >= 0;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i--]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + } + + private function sortPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, j:int, n:int = 0, num:int, k:int = 0, z:Number, a:int, b:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) { + if (i == k) map[n] = i, k = (num = sourceIndices[i++]) + i, z = 0; + z += cameraVertices[int(sourceIndices[i]*3 + 2)]; + if (++i == k) averageZ[n++] = z/num; + } + // Сортировка + sortAverageZ(n - 1); + // Перестановка граней по карте сортировки и триангуляция + for (i = 0; i < n; i++) for (j = map[i], k = sourceIndices[j] + ++j, a = sourceIndices[j++], b = sourceIndices[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[j++]; + } + + public function flashSort(length:int):void { + var i:int = 0, j:int = 0, k:int = 0, t:int; + // Одна восьмая часть длины вектора + var len:int = length >> 3; + // Массив значений Z + var averageZ:Vector. = sortingAverageZ; + // Карта перестановок + var map:Vector. = sortingMap; + var mapIndex:int; + // Вспомогательный вектор + var ind:Vector. = new Vector.(len); + //if (ind.length < len) ind.length = len; + // Минимальное значение + var minValue:Number = averageZ[0]; + // Максимальное значение + var maxValue:Number = minValue; + // Индекс максимального элемента + var maxIndex:int = 0; + // Значение текущего элемента + var value:Number; + + // Нахождение минимального значения и индекса максимального элемента + for (i = 1; i < length; ++i) { + value = averageZ[i]; + if (value < minValue) minValue = value; + else if (value > maxValue) maxValue = value, maxIndex = i; + } + + // Если все элементы одинаковы + if (minValue == maxValue) return; + + // Классификация + var c1:Number = (len - 1)/(maxValue - minValue); + + for (i = 0; i < length; i++) { + k = c1*(averageZ[i] - minValue); + ind[k]++; + } + + for (k = 1; k < len; k++) { + t = k - 1; + ind[k] += ind[t]; + } + + // Обмен максимального и нулевого элемента + averageZ[maxIndex] = averageZ[0]; + averageZ[0] = maxValue; + + j = 0; + k = len - 1; + i = length - 1; + + var swap:Number; + var nmove:int = 0; + while (nmove < i) { + while (j > (ind[k] - 1)) { + k = c1*(averageZ[++j] - minValue); + } + + value = averageZ[j]; + + while (j != ind[k]) { + k = c1*(value - minValue); + t = ind[k] - 1; + swap = averageZ[t]; + averageZ[t] = value; + mapIndex = map[t]; + map[t] = map[j]; + map[j] = mapIndex; + value = swap; + ind[k]--; + nmove++; + } + } + + for(i = 1; i < length; i++) { + swap = averageZ[i]; + mapIndex = map[i]; + j = i - 1; + while(j >= 0 && averageZ[j] > swap) { + t = j + 1; + averageZ[t] = averageZ[j]; + map[t] = map[j]; + j--; + } + t = j + 1; + averageZ[t] = swap; + map[t] = mapIndex; + } + } + + private function sortAverageZ(r:int):void { + var l:int = 0; + var i:int, j:int, stack:Vector. = sortingStack, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ, stackIndex:int, left:Number, median:Number, right:Number, mapIndex:int; + for (stack[0] = l, stack[1] = r, stackIndex = 2; stackIndex > 0;) { + j = r = stack[--stackIndex], i = l = stack[--stackIndex], median = averageZ[(r + l) >> 1]; + for (;i <= j;) { + for (;(left = averageZ[i]) > median; i++); + for (;(right = averageZ[j]) < median; j--); + if (i <= j) mapIndex = map[i], map[i] = map[j], map[j] = mapIndex, averageZ[i++] = right, averageZ[j--] = left; + } + if (l < j) stack[stackIndex++] = l, stack[stackIndex++] = j; + if (i < r) stack[stackIndex++] = i, stack[stackIndex++] = r; + } + } + + /*private function correctRedrawRegion(culling:int, camera:Camera3D):void { + var ix:int, iy:int, iz:int, verticesLength:int = cameraVertices.length, vi:int = resultIndices[1]*3; + var tx:Number = cameraVertices[vi++], ty:Number = cameraVertices[vi++], tz:Number = cameraVertices[vi]; + var x:Number, y:Number, z:Number, t:Number = 0.01, near:Number = camera.nearClipping, far:Number = camera.farClipping + t + t; + for (ix = 0, iy = 1, iz = 2; iz < verticesLength; ix += 3, iy += 3, iz += 3) { + x = cameraVertices[ix], y = cameraVertices[iy], z = cameraVertices[iz] + t; + if (z < near || z > far || z < -x || z < x || z < -y || z < y) cameraVertices[ix] = tx, cameraVertices[iy] = ty, cameraVertices[iz] = tz; + } + if (culling & 12) { + if (culling & 48) { + if (culling & 3) { + // xyz + } else { + // xy + } + } else { + if (culling & 3) { + // xz + + } else { + // x + + } + } + } else { + if (culling & 48) { + if (culling & 3) { + // yz + + } else { + // y + + } + } else { + if (culling & 3) { + // z + + } + } + } + }*/ + + // Коррекция области перерисовки + private function cropVertices(culling:int, camera:Camera3D):void { + // Коррекция ширины и высоты с учётом ошибки вычислений + var w:Number = camera.width/2 + 0.1, h:Number = camera.height/2 + 0.1, nearDist:Number = camera.nearClipping; + var i:int, j:int, projectedVerticesLength:int = projectedVertices.length, c:Number; + if (clipping == 1) { + if (culling & 1) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= nearDist) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } + } + } + } else { + if (culling & 1) { + if (culling & 12 && culling & 48) { + for (i = 0, j = 2; i < projectedVerticesLength; i++, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[++i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + if ((c = projectedVertices[++i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } else if (culling & 12) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } + } else if (culling & 48) { + for (i = 1, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[int(i - 1)] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } + } else { + if (culling & 4 && culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } else if (culling & 4) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -w) projectedVertices[i] = -w; + } else if (culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > w) projectedVertices[i] = w; + } + if (culling & 16 && culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } else if (culling & 16) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -h) projectedVertices[i] = -h; + } else if (culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > h) projectedVertices[i] = h; + } + } + } + } + + /** + * @private + */ + alternativa3d function getMipTexture(camera:Camera3D, object:Object3D):BitmapData { + // Находим расстояние до объекта + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + object.cameraMatrix.transformVectors(cameraCenter, cameraCenter); + return mipMap.textures[mipMap.getLevel(cameraCenter[2], camera)]; + } + + /** + * Расчёт нормалей + * @param normalize Флаг нормализации + */ + public function calculateNormals(normalize:Boolean = false):void { + // Подготавливаем массив нормалей + if (normals == null) normals = new Vector.() else normals.length = numFaces << 2; + // Расчитываем нормали + for (var i:int = 0, j:int = 0, num:int = 3, vi:int, nl:Number, indicesLength:int = indices.length; i < indicesLength; i += num) { + if (poly) num = indices[i++]; + // Получаем координаты A + var ax:Number = vertices[vi = int(indices[i]*3)]; + var ay:Number = vertices[++vi]; + var az:Number = vertices[++vi]; + // Получаем вектор AB + var abx:Number = vertices[vi = int(indices[int(i + 1)]*3)] - ax; + var aby:Number = vertices[++vi] - ay; + var abz:Number = vertices[++vi] - az; + // Получаем вектор AC + var acx:Number = vertices[vi = int(indices[int(i + 2)]*3)] - ax; + var acy:Number = vertices[++vi] - ay; + var acz:Number = vertices[++vi] - az; + // Считаем нормаль + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + // Нормализуем + if (normalize && (nl = Math.sqrt(nx*nx + ny*ny + nz*nz)) > 0) { + nx /= nl; + ny /= nl; + nz /= nl; + } + // Сохраняем нормаль и смещение + normals[j++] = nx; + normals[j++] = ny; + normals[j++] = nz; + normals[j++] = ax*nx + ay*ny + az*nz; + } + } + + /** + * Расчёт локального BSP-дерева + * @param splitAnalysis Флаг сплит-анализа. + * Если он включен, дерево построится с наименьшим количеством распилов, но построение будет медленнее + */ + public function calculateBSP(splitAnalysis:Boolean = false):void { + // Отправка старого дерева в коллектор + if (bsp != null) { + bsp.destroy(); + bsp = null; + } + if (numFaces == 0) return; + // Подготовка к построению нового дерева + fragmentsLength = 0; + for (var i:int = 0, k:int = 0, ni:int = 0, num:int, indicesLength:int = indices.length; i < indicesLength; i++) { + if (i == k) k = (num = poly ? indices[i++] : 3) + i, fragments[fragmentsLength++] = (ni << 16) + num, ni += 4; + fragments[fragmentsLength++] = indices[i]; + } + // Построение дерева + bsp = split(0, fragmentsLength, splitAnalysis ? findSplitter(0, fragmentsLength) : 0, splitAnalysis); + } + + private function split(begin:int, end:int, splitter:int, splitAnalysis:Boolean):BSPNode { + // Построение ноды + var mi:int = fragments[splitter], ni:int = mi >> 16, num:int = mi & 0xFFFF; + var node:BSPNode = BSPNode.create(); + node.normalX = normals[ni++], node.normalY = normals[ni++], node.normalZ = normals[ni++], node.offset = normals[ni]; + node.addFragment(fragments, splitter + 1, splitter + num + 1); + // Если в куче только сплиттер + if (end - begin == num + 1) return node; + // Подготовка к разделению + var reserve:int = end - begin + ((end - begin) >> 2); + var negativeBegin:int = fragmentsLength, negativeEnd:int = negativeBegin, positiveBegin:int = fragmentsLength + reserve, positiveEnd:int = positiveBegin; + if ((fragmentsLength = positiveBegin + reserve) > fragmentsRealLength) fragments.length = fragmentsRealLength = fragmentsLength; + // Перебираем грани + for (var i:int = begin, j1:int = negativeEnd, j2:int = positiveEnd, k:int = begin, vi:int = numVertices*3, vj:int = numVertices << 1, infront:Boolean, behind:Boolean, t:Number, uv:Number; i < end;) { + if (i == k) { + // Пропуск сплиттера + if (i == splitter) { + i += (fragments[i] & 0xFFFF) + 1; + if (i == end) break; + } + // Подготовка к разбиению + mi = fragments[i], ni = mi >> 16, num = mi & 0xFFFF, k = num + ++i, j1++, j2++, infront = false, behind = false; + // Первая точка ребра + var a:int = fragments[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)]; + var ao:Number = ax*node.normalX + ay*node.normalY + az*node.normalZ - node.offset; + } + // Вторая точка ребра + var b:int = fragments[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)]; + var bo:Number = bx*node.normalX + by*node.normalY + bz*node.normalZ - node.offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) t = ao/(ao - bo), vertices[vi] = ax + (bx - ax)*t, uvts[vi++] = uvs[vj++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, vertices[vi] = ay + (by - ay)*t, uvts[vi++] = uvs[vj++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, vertices[vi] = az + (bz - az)*t, uvts[vi++] = 0, fragments[j1++] = fragments[j2++] = numVertices++; + // Добавление точки + if (bo < -threshold) { + fragments[j1++] = b, behind = true; + } else if (bo > threshold) { + fragments[j2++] = b, infront = true; + } else { + fragments[j1++] = fragments[j2++] = b; + } + // Анализ разбиения + if (++i == k) { + if (infront && behind) { + // Фрагмент распилился + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1; + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2; + } else if (infront) { + // Фрагмент спереди + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2, j1 = negativeEnd; + } else if (behind || node.normalX*normals[ni] + node.normalY*normals[int(ni + 1)] + node.normalZ*normals[int(ni + 2)] < 0) { + // Фрагмент сзади или противонаправлен ноде + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1, j2 = positiveEnd; + } else { + // Фрагмент в плоскости ноды и сонаправлен с ней + node.addFragment(fragments, k - num, k); + j1 = negativeEnd, j2 = positiveEnd; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ao = bo; + } + } + // Разделение заднй части + if (negativeEnd > negativeBegin) node.negative = split(negativeBegin, negativeEnd, splitAnalysis ? findSplitter(negativeBegin, negativeEnd) : negativeBegin, splitAnalysis); + // Разделение передней части + if (positiveEnd > positiveBegin) node.positive = split(positiveBegin, positiveEnd, splitAnalysis ? findSplitter(positiveBegin, positiveEnd) : positiveBegin, splitAnalysis); + return node; + } + + private function findSplitter(begin:int, end:int):int { + var splitter:int, bestSplits:int = int.MAX_VALUE; + // Перебираем нормали + for (var i:int = begin, vi:int; i < end; i += (mi & 0xFFFF) + 1) { + var currentSplits:int = 0, mi:int = fragments[i], ni:int = mi >> 16, normalX:Number = normals[ni++], normalY:Number = normals[ni++], normalZ:Number = normals[ni++], offset:Number = normals[ni]; + // Перебираем точки граней + for (var j:int = begin, k:int = begin, num:int, infront:Boolean, behind:Boolean; j < end;) { + if (j == k) k = (fragments[j] & 0xFFFF) + ++j, infront = false, behind = false; + var o:Number = vertices[vi = int(fragments[j]*3)]*normalX + vertices[++vi]*normalY + vertices[++vi]*normalZ - offset; + if (o < -threshold) { + behind = true; + if (infront) j = k - 1; + } else if (o > threshold) { + infront = true; + if (behind) j = k - 1; + } + if (++j == k) { + if (behind && infront) { + currentSplits++; + if (currentSplits >= bestSplits) break; + } + } + } + // Если найдена плоскость лучше текущей + if (currentSplits < bestSplits) { + splitter = i, bestSplits = currentSplits; + // Если плоскость ничего не распиливает + if (bestSplits == 0) break; + } + } + return splitter; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = numVertices*3; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } +/* + public function calculateRadius(axis:Vector3D):Number { + var i:int, length:int = vertices.length, c:Number, radius:Number, maxRadius:Number; + if (axis == Vector3D.X_AXIS) { + + } else if (axis == Vector3D.Y_AXIS) { + + } else if (axis == Vector3D.Z_AXIS) { + for (i = 0; i < length; i++) { + if ((radius = (c = vertices[i++])*c + (c = vertices[i++])*c) > maxRadius) maxRadius = radius; + } + } + return Math.sqrt(maxRadius); + } +*/ + /** + * Копирование свойств другого меша. Осторожно, свойства будут иметь прямые ссылки на свойства копируемого меша. + * @param mesh Объект копирования + */ + public function copyFrom(mesh:Mesh):void { + + alpha = mesh.alpha; + blendMode = mesh.blendMode; + + poly = mesh.poly; + + numVertices = mesh.numVertices; + numFaces = mesh.numFaces; + vertices = mesh.vertices; + indices = mesh.indices; + uvts = mesh.uvts; + uvs = mesh.uvs; + + texture = mesh.texture; + mipMap = mesh.mipMap; + + smooth = mesh.smooth; + repeatTexture = mesh.repeatTexture; + perspectiveCorrection = mesh.perspectiveCorrection; + + backfaceCulling = mesh.backfaceCulling; + clipping = mesh.clipping; + sorting = mesh.sorting; + mipMapping = mesh.mipMapping; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + + normals = mesh.normals; + bsp = mesh.bsp; + } + + public function generateClass(className:String = "GeneratedMesh", packageName:String = "", textureName:String = null):String { + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["__AS3__.vec.Vector"] = true; + importSet["alternativa.engine3d.core.Mesh"] = true; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + + constructor += "\t\t\tnumVertices = " + numVertices +";\r"; + constructor += "\t\t\tnumFaces = " + numFaces +";\r"; + constructor += "\t\t\tvertices = Vector.(["; + var length:uint = numVertices*3; + var n:int = 0; + + var i:int; + + for (i = 0; i < length; i++) { + constructor += vertices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tindices = Vector.(["; + length = numFaces*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += indices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + + constructor += "\t\t\tuvts = Vector.(["; + length = numVertices*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvts[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tuvs = Vector.(["; + length = numVertices << 1; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvs[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + var embeds:String = ""; + if (textureName != null) { + importSet["flash.display.BitmapData"] = true; + var bmpName:String = textureName.charAt(0).toUpperCase() + textureName.substr(1); + embeds += "\t\t[Embed(source=\"" + textureName + "\")] private static const bmp" + bmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + textureName + ":BitmapData = new bmp" + bmpName + "().bitmapData;\r\r"; + constructor += "\t\t\ttexture = " + textureName + ";\r\r"; + } + + constructor += "\t\t\tclipping = " + clipping +";\r"; + constructor += "\t\t\tperspectiveCorrection = " + (perspectiveCorrection ? "true" : "false") +";\r"; + constructor += "\t\t\trepeatTexture = " + (repeatTexture ? "true" : "false") +";\r\r"; + + constructor += "\t\t\tmatrix.rawData = Vector.([" + matrix.rawData + "]);\r"; + if (_boundBox != null) { + importSet["alternativa.engine3d.bounds.BoundBox"] = true; + constructor += "\t\t\t_boundBox = new BoundBox(" + _boundBox.minX + ", " + _boundBox.minY + ", " + _boundBox.minZ + ", " + _boundBox.maxX + ", " + _boundBox.maxY + ", " + _boundBox.maxZ + ");\r"; + } + + var imports:String = ""; + + var importArray:Array = new Array(); + for (var key:* in importSet) { + importArray.push(key); + } + importArray.sort(); + + var newLine:Boolean = false; + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + // Объединение вершин + /** + * Объединение вершин с одинаковыми координатами + * @param distanceThreshold Погрешность, в пределах которой координаты считаются одинаковыми + * @param uvThreshold Погрешность, в пределах которой UV-координаты считаются одинаковыми + */ + public function weldVertices(distanceThreshold:Number = 0, uvThreshold:Number = 0):void { + var i:int, j:int, k:int, t:int; + + // Карта соответствий + var weld:Vector. = new Vector.(numVertices = vertices.length/3); + for (i = 0; i < numVertices; i++) weld[i] = i; + + // Ненужные индексы + var uselessIndices:Vector. = new Vector.(); + var numUselessIndices:uint = 0; + + // Сравнение вершин по координатам и UV + for (i = 0; i < numVertices - 1; i++) { + if (weld[i] == i) { + var ax:Number = vertices[k = i*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + for (j = i + 1; j < numVertices; j++) { + if (weld[j] == j) { + var bx:Number = vertices[k = j*3]; + var by:Number = vertices[k + 1]; + var bz:Number = vertices[k + 2]; + var bu:Number = uvts[k]; + var bv:Number = uvts[k + 1]; + if ((ax - bx <= distanceThreshold) && (ax - bx >= -distanceThreshold) && (ay - by <= distanceThreshold) && (ay - by >= -distanceThreshold) && (az - bz <= distanceThreshold) && (az - bz >= -distanceThreshold) && (au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + weld[j] = i; + uselessIndices[numUselessIndices++] = j; + } + } + } + } + } + + // Удаление ненужных вершин и UV + for (i = 0, j = 0; j < numVertices; j++) { + if (weld[j] == j) { + if (i != j) { + vertices[k = i*3] = vertices[t = j*3]; + vertices[k + 1] = vertices[t + 1]; + vertices[k + 2] = vertices[t + 2]; + uvts[k] = uvts[t]; + uvts[k + 1] = uvts[t + 1]; + uvs[k = i*2] = uvs[t = j*2]; + uvs[k + 1] = uvs[t + 1]; + } + i++; + } + } + vertices.length = i*3; + uvts.length = i*3; + uvs.length = i*2; + numVertices = i; + + // Корректировка индексов + uselessIndices.sort(function compare(x:int, y:int):Number {return x - y;}); + var numIndices:int = indices.length; + for (i = 0, j = 0; i < numIndices; i++) { + j = (poly && i == j) ? (indices[i++] + i) : j; + k = t = weld[indices[i]]; + for (var u:int = 0; u < numUselessIndices; u++) { + if (k > uselessIndices[u]) { + t--; + } else { + break; + } + } + indices[i] = t; + } + } + + /** + * Объединение треугольников в многоугольники + * После вызова этого метода флаг poly становится true + * @param angleThreshold Допустимый угол в радианах между нормалями, чтобы считать, что объединяемые грани в одной плоскости + * @param uvThreshold Допустимая разница uv-координат, чтобы считать, что объединяемые грани состыковываются по UV + * @param convexThreshold Величина, уменьшающая допустимый угол между смежными рёбрами объединяемых граней + */ + public function convertToPoly(angleThreshold:Number = 0, uvThreshold:Number = 0, convexThreshold:Number = 0):void { + + if (poly) return; + + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + uvThreshold += digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + + var i:int, j:int, n:int, k:int, t:int; + + // Вспомогательные флаги + var contains:Vector. = new Vector.(numFaces = indices.length/3); + + // Расчёт нормалей и матриц uv-трансформации + var normals:Vector. = new Vector.(numFaces*3); + var matrices:Vector. = new Vector.(numFaces*8); + for (i = 0; i < numFaces; i++) { + // Нахождение нормали + var ax:Number = vertices[k = indices[t = i*3]*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + var abx:Number = vertices[k = indices[t + 1]*3] - ax; + var aby:Number = vertices[k + 1] - ay; + var abz:Number = vertices[k + 2] - az; + var abu:Number = uvts[k] - au; + var abv:Number = uvts[k + 1] - av; + var acx:Number = vertices[k = indices[t + 2]*3] - ax; + var acy:Number = vertices[k + 1] - ay; + var acz:Number = vertices[k + 2] - az; + var acu:Number = uvts[k] - au; + var acv:Number = uvts[k + 1] - av; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + // Если грань не вырождена + if (nl > digitThreshold) { + contains[i] = 1; + // Нормализация и сохранение нормали + normals[t] = nx /= nl; + normals[t + 1] = ny /= nl; + normals[t + 2] = nz /= nl; + // Нахождение обратной матрицы грани + var det:Number = -nx*acy*abz + acx*ny*abz + nx*aby*acz - abx*ny*acz - acx*aby*nz + abx*acy*nz; + var ma:Number = (-ny*acz + acy*nz)/det; + var mb:Number = (nx*acz - acx*nz)/det; + var mc:Number = (-nx*acy + acx*ny)/det; + var md:Number = (ax*ny*acz - nx*ay*acz - ax*acy*nz + acx*ay*nz + nx*acy*az - acx*ny*az)/det; + var me:Number = (ny*abz - aby*nz)/det; + var mf:Number = (-nx*abz + abx*nz)/det; + var mg:Number = (nx*aby - abx*ny)/det; + var mh:Number = (nx*ay*abz - ax*ny*abz + ax*aby*nz - abx*ay*nz - nx*aby*az + abx*ny*az)/det; + // Умножение прямой uv-матрицы на обратную матрицу грани и сохранение матрицы uv-трансформации + matrices[t = i*8] = abu*ma + acu*me; + matrices[t + 1] = abu*mb + acu*mf; + matrices[t + 2] = abu*mc + acu*mg; + matrices[t + 3] = abu*md + acu*mh + au; + matrices[t + 4] = abv*ma + acv*me; + matrices[t + 5] = abv*mb + acv*mf; + matrices[t + 6] = abv*mc + acv*mg; + matrices[t + 7] = abv*md + acv*mh + av; + } + } + + // Разбиение граней на группы по углу, UV и соседству + var islands:Vector.>> = new Vector.>>(); + var numIslands:int = 0; + var island:Vector.>; + var islandLength:int; + var f:Vector., fLen:int, fi:int, fj:int; + var s:Vector., sLen:int, si:int, sj:int; + for (i = 0; i < numFaces; i++) { + if (contains[i] > 0) { + contains[i] = 0; + // Создание группы + island = new Vector.>(); + islands[numIslands] = island; + // Создание грани и добавление в группу + f = new Vector.(3); + f[0] = indices[k = i*3]; + f[1] = indices[k + 1]; + f[2] = indices[k + 2]; + island[0] = f; + islandLength = 1; + normals[t = numIslands++*3] = nx = normals[k = i*3]; + normals[t + 1] = ny = normals[k + 1]; + normals[t + 2] = nz = normals[k + 2]; + ma = matrices[k = i*8]; + mb = matrices[k + 1]; + mc = matrices[k + 2]; + md = matrices[k + 3]; + me = matrices[k + 4]; + mf = matrices[k + 5]; + mg = matrices[k + 6]; + mh = matrices[k + 7]; + // Перебор и дополнение группы + for (n = 0; n < islandLength; n++) { + f = island[n]; + var a1:int = f[0]; + var b1:int = f[1]; + var c1:int = f[2]; + for (j = i + 1; j < numFaces; j++) { + if (contains[j] > 0) { + // Если грани сонаправлены + if (nx*normals[k = j*3] + ny*normals[k + 1] + nz*normals[k + 2] >= angleThreshold) { + var a2:int = indices[k]; + var b2:int = indices[k + 1]; + var c2:int = indices[k + 2]; + // Если грани соседние + if ((k = (a1 == c2 && b1 == b2 || b1 == c2 && c1 == b2 || c1 == c2 && a1 == b2) ? a2 : ((a1 == a2 && b1 == c2 || b1 == a2 && c1 == c2 || c1 == a2 && a1 == c2) ? b2 : ((a1 == b2 && b1 == a2 || b1 == b2 && c1 == a2 || c1 == b2 && a1 == a2) ? c2 : -1))) >= 0) { + ax = vertices[k *= 3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + au = uvts[k]; + av = uvts[k + 1]; + var bu:Number = ma*ax + mb*ay + mc*az + md; + var bv:Number = me*ax + mf*ay + mg*az + mh; + // Если совпадают по UV + if ((au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + contains[j] = 0; + s = new Vector.(3); + s[0] = a2; + s[1] = b2; + s[2] = c2; + island[islandLength++] = s; + } + } + } + } + } + } + } + } + + poly = true; + numFaces = 0; + + // Объединение + var numIndices:int = 0; + var faces1:Vector.> = new Vector.>(); + var faces2:Vector.> = new Vector.>(); + for (n = 0; n < numIslands; n++) { + island = islands[n]; + islandLength = island.length; + nx = normals[k = n*3]; + ny = normals[k + 1]; + nz = normals[k + 2]; + // Дополнение вспомогательных списков, если нужно + for (i = faces1.length; i < islandLength; i++) { + faces1[i] = new Vector.(); + faces2[i] = new Vector.(); + } + var numFaces1:int = islandLength; + var numFaces2:int = 0; + // Копирование граней из группы в первый список + for (i = 0; i < islandLength; i++) { + f = island[i]; + fLen = f.length; + s = faces1[i]; + for (j = 0; j < fLen; j++) s[j] = f[j]; + s.length = fLen; + } + // Объединение + do { + // Подготовка к итерации + var weld:Boolean = false; + for (i = 0; i < numFaces1; i++) contains[i] = 1; + // Попытки объединить текущую грань со всеми следующими + for (i = 0; i < numFaces1; i++) { + if (contains[i] > 0) { + f = faces1[i]; + fLen = f.length; + for (j = i + 1; j < numFaces1; j++) { + if (contains[j] > 0) { + s = faces1[j]; + sLen = s.length; + // Проверка на соседство + for (fi = 0; fi < fLen; fi++) { + a1 = f[fi]; + b1 = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]; + for (si = 0; si < sLen; si++) { + a2 = s[si]; + b2 = s[sj = (si < sLen - 1) ? (si + 1) : 0]; + if (a1 == b2 && b1 == a2) break; + } + if (si < sLen) break; + } + // Если грань соседняя + if (fi < fLen) { + // Расширение граней объединеия + while (true) { + fj = (fj < fLen - 1) ? (fj + 1) : 0; + si = (si > 0) ? (si - 1) : (sLen - 1); + b2 = f[fj]; + c2 = s[si]; + if (b2 == c2) a2 = c2 else break; + } + while (true) { + sj = (sj < sLen - 1) ? (sj + 1) : 0; + fi = (fi > 0) ? (fi - 1) : (fLen - 1); + b1 = s[sj]; + c1 = f[fi]; + if (b1 == c1) a1 = c1 else break; + } + // Первый перегиб + ax = vertices[k = a1*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b1*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c1*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + var crx:Number = aby*acz - abz*acy; + var cry:Number = abz*acx - abx*acz; + var crz:Number = abx*acy - aby*acx; + var zeroCross:Boolean = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Второй перегиб + ax = vertices[k = a2*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b2*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c2*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + zeroCross = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Объединение + var fs:Vector. = faces2[numFaces2++]; + var fsLen:int = 0; + fs[fsLen++] = a2; + while (true) { + fs[fsLen++] = f[fj]; + if (fj != fi) fj = (fj < fLen - 1) ? (fj + 1) : 0 else break; + } + fs[fsLen++] = a1; + while (true) { + fs[fsLen++] = s[sj]; + if (sj != si) sj = (sj < sLen - 1) ? (sj + 1) : 0 else break; + } + fs.length = fsLen; + contains[j] = 0; + weld = true; + break; + } + } + } + // Если не было объединения + if (j == numFaces1) { + s = faces2[numFaces2++]; + for (fi = 0; fi < fLen; fi++) s[fi] = f[fi]; + s.length = fLen; + } + } + } + // Переброс списков + island = faces1; + faces1 = faces2; + numFaces1 = numFaces2; + faces2 = island; + numFaces2 = 0; + } while (weld); + // Запись индексов в полигональной форме + for (i = 0; i < numFaces1; i++) { + f = faces1[i]; + fLen = f.length; + indices[numIndices++] = fLen; + if (fLen > 3) { + // Определение наилучшей последовательности + var max:Number = -Number.MAX_VALUE; + for (fi = 0; fi < fLen; fi++) { + ax = vertices[k = f[fi]*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = f[(fj < fLen - 1) ? (fj + 1) : 0]*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + nl = Math.sqrt(crx*crx + cry*cry + crz*crz); + if (nl > max) { + max = nl; + j = fi; + } + } + for (fi = j; fi < fLen; fi++) indices[numIndices++] = f[fi]; + for (fi = 0; fi < j; fi++) indices[numIndices++] = f[fi]; + } else { + for (fi = 0; fi < fLen; fi++) indices[numIndices++] = f[fi]; + } + numFaces++; + } + } + indices.length = numIndices; + } + + } +} diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Occluder.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Occluder.as new file mode 100644 index 0000000..fa9c9fc --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Occluder.as @@ -0,0 +1,404 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import alternativa.engine3d.core.Debug; + + use namespace alternativa3d; + + /** + * Полигональный объект-перекрытие. + * Объекты, которые он перекрывает от видимости камеры, исключаются из отрисовки. + * Сам окклюдер не отрисовывается. + * Должен быть конвексным + */ + public class Occluder extends Object3D { + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; + public var vertices:Vector.; + public var edges:Vector.; // Два индекса - вершины, два - грани + public var indices:Vector.; + public var normals:Vector.; + + // Отношение площади перекрытия к площади вьюпорта (0 - 1) + /** + * Минимальное отношение площади перекрытия окклюдером вьюпорта к площади вьюпорта (от 0 до 1) + * Если окклюдер перекрывает больше, он помещается в очередь и учитывается + * при дальнейшей отрисовке в пределах кадра, иначе игнорируется + */ + public var minSize:Number = 0; + + private const cameraVertices:Vector. = new Vector.(); + private const visibilityMap:Vector. = new Vector.; + + /** + * Коприрование геометрии меша + * @param mesh Объект копирования + * Меш, геометрия которого копируется, обязан быть конвексным, иначе окклюдер будет некорректно работать + */ + public function copyFrom(mesh:Mesh):void { + poly = mesh.poly; + vertices = mesh.vertices; + indices = mesh.indices; + normals = mesh.normals; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = vertices.length; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } + + /** + * Расчёт рёбер по имеющимся вершинам и граням + */ + public function calculateEdges():void { + // Подготавливаем массив рёбер + if (edges == null) edges = new Vector.(); + + // Собираем рёбра + for (var i:int = 0, j:int = 0, n:int = 0, k:int = 0, a:int, b:int, length:int = indices.length; i < length;) { + if (i == k) { + k = poly ? (indices[i++] + i) : (i + 3); + a = indices[int(k - 1)]; + } + b = indices[i]; + edges[j++] = a; + edges[j++] = b; + edges[j++] = n; + edges[j++] = -1; + if (++i == k) n++; else a = b; + } + edges.length = j; + + // Убираем дубли + length = j, i = 0; k = 0; + var ac:int, bc:int; + while (i < length) { + if ((a = edges[i++]) >= 0) { + b = edges[i++]; + edges[k++] = a; + edges[k++] = b; + edges[k++] = edges[i++]; + j = ++i; + while (j < length) { + ac = edges[j++]; + bc = edges[j++]; + if (ac == a && bc == b || ac == b && bc == a) { + edges[int(j - 2)] = -1; + edges[k] = edges[j]; + break; + } + j += 2; + } + k++; + } else i += 3; + } + edges.length = k; + } + + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const center:Vector. = new Vector.(3, true); + private var cameraX:Number; + private var cameraY:Number; + private var cameraZ:Number; + static private const projectedEdges:Vector. = new Vector.(); + static private const uvts:Vector. = new Vector.(); + static private const viewEdges:Vector. = new Vector.(); + static private const debugEdges:Vector. = new Vector.(); + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (cameraInside) return; + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var planeOccluder:Vector., edgeOccluder:Vector., planeOccluderLength:int = 0, edgeOccluderLength:int = 0, viewEdgesLength:int = 0; + if (camera.occlusionPlanes.length > camera.numOccluders) planeOccluder = camera.occlusionPlanes[camera.numOccluders], edgeOccluder = camera.occlusionEdges[camera.numOccluders] else planeOccluder = camera.occlusionPlanes[camera.numOccluders] = new Vector.(), edgeOccluder = camera.occlusionEdges[camera.numOccluders] = new Vector.(); + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n] else bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + // Расчёт нормали плоскости отсечения + planeOccluder[planeOccluderLength++] = bz*ay - by*az, planeOccluder[planeOccluderLength++] = bx*az - bz*ax, planeOccluder[planeOccluderLength++] = by*ax - bx*ay; + // Сохранение рёбер + edgeOccluder[edgeOccluderLength++] = ax, edgeOccluder[edgeOccluderLength++] = ay, edgeOccluder[edgeOccluderLength++] = az, edgeOccluder[edgeOccluderLength++] = bx, edgeOccluder[edgeOccluderLength++] = by, edgeOccluder[edgeOccluderLength++] = bz; + } else i -= 2; + } + if (planeOccluderLength > 0) { + // Проверка размера на экране + if (minSize > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((edgeOccluder.length = edgeOccluderLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, edgeOccluder, projectedEdges, uvts); + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + var square:Number = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + if (square/(camera.viewSizeX*camera.viewSizeY*8) < minSize) return; + } + // Добавление окклюдера + camera.numOccluders++, planeOccluder.length = planeOccluderLength; + } else { + if (occludeAll) camera.numOccluders = 0, camera.occludedAll = true else return; + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var inDebug:int = camera.checkInDebug(this); + if (inDebug == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + + // Рёбра + if (inDebug & Debug.EDGES) { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (!cameraInside) { + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var debugEdgesLength:int = 0, viewEdgesLength:int = 0; + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n] else bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + debugEdges[debugEdgesLength++] = ax; + debugEdges[debugEdgesLength++] = ay; + debugEdges[debugEdgesLength++] = az; + debugEdges[debugEdgesLength++] = bx; + debugEdges[debugEdgesLength++] = by; + debugEdges[debugEdgesLength++] = bz; + } else i -= 2; + } + if (debugEdgesLength > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((debugEdges.length = debugEdgesLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, debugEdges, projectedEdges, uvts); + // Проверка размера на экране + var square:Number = Number.MAX_VALUE; + if (minSize > 0) { + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + square = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + } + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + var color:int, thickness:Number; + if (square/(camera.viewSizeX*camera.viewSizeY*8) >= minSize) { + color = 0x0000FF, thickness = 3; + } else { + color = 0x0077AA, thickness = 1; + } + for (i = 0; i < projectedEdges.length;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + canvas.gfx.moveTo(ax, ay); + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.lineTo(ax + (bx - ax)*0.8, ay + (by - ay)*0.8); + canvas.gfx.lineStyle(thickness, 0xFF0000); + canvas.gfx.lineTo(bx, by); + } + for (i = 0; i < viewEdgesLength;) { + canvas.gfx.moveTo(viewEdges[i++], viewEdges[i++]); + canvas.gfx.lineTo(viewEdges[i++], viewEdges[i++]); + } + } else { + if (occludeAll) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(6, 0xFF0000); + canvas.gfx.moveTo(-camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, -camera.viewSizeY); + } + } + } + } + // Оси, центры, имена, баунды + if (inDebug & Debug.AXES) object.drawAxes(camera, canvas); + if (inDebug & Debug.CENTERS) object.drawCenter(camera, canvas); + if (inDebug & Debug.NAMES) object.drawName(camera, canvas); + if (inDebug & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Reference.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Reference.as new file mode 100644 index 0000000..dc5b772 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Reference.as @@ -0,0 +1,60 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект-ссылка. + * Может ссылаться на любой трёхмерный объект, в том числе контейнер с любой вложенностью или Reference. + * При отрисовке он отрисовывает вместо себя объект, + * на который ссылается, подставляя только свою трансформацию, alpha, blendMode, colorTransform и filters. + */ + public class Reference extends Object3D { + + /** + * Объект, который подставляется при отрисовке вместо себя + */ + public var referenceObject:Object3D; + + public function Reference(referenceObject:Object3D = null) { + this.referenceObject = referenceObject; + } + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return referenceObject.canDraw; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.draw(camera, object, parentCanvas); + } + + /** + * @private + */ + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.debug(camera, object, parentCanvas); + } + + override public function get boundBox():BoundBox { + return referenceObject.boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return referenceObject.calculateBoundBox(matrix, boundBox); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/SkeletalMesh.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/SkeletalMesh.as new file mode 100644 index 0000000..8677542 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/SkeletalMesh.as @@ -0,0 +1,148 @@ +package alternativa.engine3d.objects { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SkeletalMesh extends Mesh { + + alternativa3d var _numBones:uint = 0; + alternativa3d var bones:Vector. = new Vector.(); + private var weights:Vector.>; + private var originalVertices:Vector.; + private var originalBonesMatrices:Vector.; + private var boneVertices:Vector.; + + public function initBones():void { + // Инициализируем массив весов и сохраняем оригинальные матрицы костей + originalBonesMatrices = new Vector.(_numBones, true); + weights = new Vector.>(_numBones, true); + for (var j:int = 0; j < _numBones; j++) { + originalBonesMatrices[j] = new Matrix3D(); + originalBonesMatrices[j].prepend(bones[j].matrix); + originalBonesMatrices[j].invert(); + weights[j] = new Vector.(numVertices, true); + } + // Формируем вспомогательные массивы для вершин + originalVertices = new Vector.(numVertices*3, true); + boneVertices = new Vector.(numVertices*3, true); + + // Обрабатываем вершины + var v:Vector3D = new Vector3D(); + for (var i:int = 0; i < numVertices; i++) { + var k:int = i*3; + // Сохраняем оригинальные координаты вершин + originalVertices[k] = vertices[k]; + originalVertices[k + 1] = vertices[k + 1]; + originalVertices[k + 2] = vertices[k + 2]; + + // Находим веса для каждой кости + var sumWeight:Number = 0; + for (j = 0; j < _numBones; j++) { + // Находим расстояние от вершины до кости + var b1:Vector3D = bones[j].matrix.transformVector(new Vector3D()); + var b2:Vector3D = bones[j].matrix.transformVector(new Vector3D(0, 0, bones[j].length)); + v.x = originalVertices[k]; + v.y = originalVertices[k + 1]; + v.z = originalVertices[k + 2]; + var w:Number = 1 - distanceToBone(b1, b2, v)/bones[j].distance; + //trace(w); + w = (w > 0) ? w : 0; + weights[j][i] = w; + sumWeight += w; + } + + // Нормализуем веса + if (sumWeight > 0) { + for (j = 0; j < _numBones; j++) { + weights[j][i] /= sumWeight; + } + } else { + // Если вершина не относится ни к какой кости, помечаем + for (j = 0; j < _numBones; j++) { + weights[j][i] = -1; + } + } + + } + } + + private function distanceToBone(b1:Vector3D, b2:Vector3D, p:Vector3D):Number { + var v:Vector3D = b2.subtract(b1); + var w:Vector3D = p.subtract(b1); + + var c1:Number = w.dotProduct(v); + if ( c1 <= 0 ) + return Vector3D.distance(p, b1); + + var c2:Number = v.dotProduct(v); + if ( c2 <= c1 ) + return Vector3D.distance(p, b2); + + v.scaleBy(c1 / c2); + var Pb:Vector3D = b1.add(v); + return Vector3D.distance(p, Pb); + + + } + + private var m:Matrix3D = new Matrix3D(); + public function calculateBones():void { + // Обнуление координат + for (var i:int = 0; i < numVertices*3; i++) { + vertices[i] = 0; + } + // Добавление трансформации через кости + for (var j:int = 0; j < _numBones; j++) { + m.identity(); + m.prepend(bones[j].matrix); + m.prepend(originalBonesMatrices[j]); + m.transformVectors(originalVertices, boneVertices); + var boneWeights:Vector. = weights[j]; + for (i = 0; i < numVertices; i++) { + var weight:Number = boneWeights[i]; + var k1:int = i*3; + var k2:int = k1 + 1; + var k3:int = k1 + 2; + if (weight >= 0) { + vertices[k1] += boneVertices[k1]*weight; + vertices[k2] += boneVertices[k2]*weight; + vertices[k3] += boneVertices[k3]*weight; + } else { + vertices[k1] = originalVertices[k1]; + vertices[k2] = originalVertices[k2]; + vertices[k3] = originalVertices[k3]; + } + } + } + } + + public function addBone(bone:Bone):void { + bones[_numBones++] = bone; + } +/* + public function removeChild(bone:Bone):void { + var i:int = bones.indexOf(bone); + if (i < 0) throw new ArgumentError(); + children.splice(i, 1); + _numBones--; + var len:uint = drawChildren.length; + for (i = 0; i < len; i++) { + if (drawChildren[i] == bone) { + drawChildren.splice(i, 1); + break; + } + } + bone._parent = null; + bone.setStage(null); + } +*/ + + + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/Sprite3D.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..99cb5d7 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,470 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import alternativa.engine3d.core.Debug; + + use namespace alternativa3d; + + /** + * Плоский, всегда развёрнутый к камере трёхмерный объект + */ + public class Sprite3D extends Object3D { + + static private const axes:Vector. = Vector.([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]); + static private const cameraAxes:Vector. = new Vector.(12, true); + /** + * @private + */ + static alternativa3d const textureMatrix:Matrix = new Matrix(); + /** + * @private + */ + static alternativa3d const vertices:Vector. = new Vector.(); + /** + * @private + */ + alternativa3d var drawTexture:BitmapData; + /** + * @private + */ + alternativa3d var projectionX:Number; + /** + * @private + */ + alternativa3d var projectionY:Number; + + public var texture:BitmapData; + public var mipMap:MipMap; + /** + * X точки привязки + */ + public var originX:Number = 0.5; + /** + * Y точки привязки + */ + public var originY:Number = 0.5; + public var smooth:Boolean = false; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; // 0 - весь объект, 2 - с обрезкой + public var mipMapping:int = 0; // 0 - без мипмаппинга, 1 - по дальности + /** + * Угол поворота в радианах в плоскости экрана + */ + public var rotation:Number = 0; + /** + * Зависимость размера на экране от удалённости от камеры + */ + public var perspectiveScale:Boolean = true; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + var length:int; + if ((length = calculateVertices(object, camera)) == 0) return; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + canvas.gfx.beginBitmapFill(drawTexture, textureMatrix, false, smooth); + + if (rotation == 0) { + // Простая отрисовка + var x:Number = vertices[0]*projectionX, y:Number = vertices[1]*projectionY; + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + // Отрисовка + canvas.gfx.moveTo(vertices[length - 3]*projectionX, vertices[length - 2]*projectionY); + for (var i:int = 0; i < length; i++) canvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var inDebug:int = camera.checkInDebug(this); + if (inDebug == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + + var i:int, length:int, x:Number, y:Number, t:Number = 0.1; + // Рёбра + if (inDebug & Debug.EDGES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(0, 0xFFFFFF); + if (rotation == 0) { + x = vertices[0]*projectionX, y = vertices[1]*projectionY; + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + // Отрисовка + canvas.gfx.moveTo(vertices[length - 3]*projectionX, vertices[length - 2]*projectionY); + for (i = 0; i < length; i++) canvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + } + } + } + // Вершины + if (inDebug & Debug.VERTICES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(); + if (rotation == 0) { + var x1:Number = vertices[0]*projectionX, y1:Number = vertices[1]*projectionY; + var x2:Number = vertices[6]*projectionX, y2:Number = vertices[7]*projectionY; + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y1, 2); + } + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y1, 2); + } + } else { + for (i = 0; i < length; i++) { + x = vertices[i++]*projectionX, y = vertices[i++]*projectionY; + if (x > -camera.viewSizeX + t && x < camera.viewSizeX - t && y > -camera.viewSizeY + t && y < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x, y, 2); + } + } + } + } + } + // Оси, центры, имена, баунды + if (inDebug & Debug.AXES) object.drawAxes(camera, canvas); + if (inDebug & Debug.CENTERS) object.drawCenter(camera, canvas); + if (inDebug & Debug.NAMES) object.drawName(camera, canvas); + if (inDebug & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + /** + * @private + */ + alternativa3d function calculateVertices(object:Object3D, camera:Camera3D):int { + + // Получение матрицы и позиции + object.cameraMatrix.transformVectors(axes, cameraAxes); + var cz:Number = cameraAxes[2]; + + if (cz < camera.nearClipping || cz > camera.farClipping) return 0; + var cx:Number = cameraAxes[0]; + var cy:Number = cameraAxes[1]; + + // Нахождение среднего размера спрайта + var ax:Number = (cameraAxes[3] - cx)*camera.invertPerspectiveScaleX; + var ay:Number = (cameraAxes[4] - cy)*camera.invertPerspectiveScaleY; + var az:Number = cameraAxes[5] - cz; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[6] - cx)*camera.invertPerspectiveScaleX; + ay = (cameraAxes[7] - cy)*camera.invertPerspectiveScaleY; + az = cameraAxes[8] - cz; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[9] - cx)*camera.invertPerspectiveScaleX; + ay = (cameraAxes[10] - cy)*camera.invertPerspectiveScaleY; + az = cameraAxes[11] - cz; + size = (size + Math.sqrt(ax*ax + ay*ay + az*az))/3; + + // Определение текстуры и коррекция размера + var level:int; + if (mipMapping == 0) { + drawTexture = texture; + } else { + size *= Math.pow(2, level = mipMap.getLevel(cz/size, camera)); + drawTexture = mipMap.textures[level]; + } + + // Проекция на экран + var projectionZ:Number = camera.focalLength/cz; + projectionX = camera.viewSizeX/cz; + projectionY = camera.viewSizeY/cz; + + if (!perspectiveScale) size /= projectionZ; + + var x1:Number, y1:Number, x2:Number, y2:Number; + + if (rotation == 0) { + + // Размеры спрайта в матрице камеры + var cameraWidth:Number = drawTexture.width*camera.perspectiveScaleX*size; + var cameraHeight:Number = drawTexture.height*camera.perspectiveScaleY*size; + + // Расчёт вершин в матрице камеры + x1 = cx - originX*cameraWidth; + y1 = cy - originY*cameraHeight; + x2 = x1 + cameraWidth; + y2 = y1 + cameraHeight; + + // Отсечение по вьюпорту + if (object.culling > 0 && (x1 > cz || y1 > cz || x2 < -cz || y2 < -cz)) return 0; + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = size*projectionZ; + textureMatrix.b = textureMatrix.c = 0; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + // Подрезка + if (clipping == 2) { + if (x1 < -cz) x1 = -cz; + if (y1 < -cz) y1 = -cz; + if (x2 > cz) x2 = cz; + if (y2 > cz) y2 = cz; + } + + // Заполняем вершины + vertices[0] = x1; + vertices[1] = y1; + vertices[2] = cz; + vertices[3] = x1; + vertices[4] = y2; + vertices[5] = cz; + vertices[6] = x2; + vertices[7] = y2; + vertices[8] = cz; + vertices[9] = x2; + vertices[10] = y1; + vertices[11] = cz; + + return 12; + + } else { + + // Размер спрайта в камере без коррекции под FOV90 + var textureWidth:Number = drawTexture.width; + var textureHeight:Number = drawTexture.height; + + // Расчёт векторов ширины и высоты + var sin:Number = Math.sin(rotation)*size; + var cos:Number = Math.cos(rotation)*size; + var cameraWidthX:Number = cos*textureWidth*camera.perspectiveScaleX; + var cameraWidthY:Number = -sin*textureWidth*camera.perspectiveScaleY; + var cameraHeightX:Number = sin*textureHeight*camera.perspectiveScaleX; + var cameraHeightY:Number = cos*textureHeight*camera.perspectiveScaleY; + + // Заполняем вершины + var length:int = 12; + vertices[0] = x1 = cx - originX*cameraWidthX - originY*cameraHeightX; + vertices[1] = y1 = cy - originX*cameraWidthY - originY*cameraHeightY; + vertices[2] = cz; + vertices[3] = x1 + cameraHeightX; + vertices[4] = y1 + cameraHeightY; + vertices[5] = cz; + vertices[6] = x1 + cameraWidthX + cameraHeightX; + vertices[7] = y1 + cameraWidthY + cameraHeightY; + vertices[8] = cz; + vertices[9] = x1 + cameraWidthX; + vertices[10] = y1 + cameraWidthY; + vertices[11] = cz; + + if (object.culling > 0) { + // Отсечение по вьюпорту + var i:int, infront:Boolean, behind:Boolean, inside:Boolean; + var clipLeft:Boolean = false; + if (object.culling & 4) { + for (i = 0; i < length; i += 3) if ((inside = -vertices[i] < cz) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipLeft = true; + infront = false; behind = false; + } + var clipRight:Boolean = false; + if (object.culling & 8) { + for (i = 0; i < length; i += 3) if ((inside = vertices[i] < cz) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipRight = true; + infront = false; behind = false; + } + var clipTop:Boolean = false; + if (object.culling & 16) { + for (i = 1; i < length; i += 3) if ((inside = -vertices[i] < cz) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipTop = true; + infront = false; behind = false; + } + var clipBottom:Boolean = false; + if (object.culling & 32) { + for (i = 1; i < length; i += 3) if ((inside = vertices[i] < cz) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipBottom = true; + } + // Подрезка + if (clipping == 2) { + var n:int = 0, t:Number, bx:Number, by:Number; + if (clipLeft) { + ax = cx = vertices[0]; ay = cy = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = cx; by = cy; + } + if (-bx < cz && -ax >= cz || -bx >= cz && -ax < cz) { + t = (ax + cz)/(ax - bx); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = cz; + } + if (-bx < cz) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = cz; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipRight) { + ax = cx = vertices[0]; ay = cy = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = cx; by = cy; + } + if (bx < cz && ax >= cz || bx >= cz && ax < cz) { + t = (cz - ax)/(bx - ax); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = cz; + } + if (bx < cz) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = cz; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipTop) { + ax = cx = vertices[0]; ay = cy = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = cx; by = cy; + } + if (-by < cz && -ay >= cz || -by >= cz && -ay < cz) { + t = (ay + cz)/(ay - by); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = cz; + } + if (-by < cz) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = cz; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipBottom) { + ax = cx = vertices[0]; ay = cy = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = cx; by = cy; + } + if (by < cz && ay >= cz || by >= cz && ay < cz) { + t = (cz - ay)/(by - ay); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = cz; + } + if (by < cz) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = cz; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + } + } + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = cos*projectionZ; + textureMatrix.b = -sin*projectionZ; + textureMatrix.c = sin*projectionZ; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + return length; + + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчёт локального радиуса + var t:BitmapData = (mipMapping == 0) ? texture : mipMap.textures[0]; + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*t.width; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*t.height; + var radius:Number = Math.sqrt(w*w + h*h); + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(axes, cameraAxes); + var cz:Number = cameraAxes[2]; + var cx:Number = cameraAxes[0]; + var cy:Number = cameraAxes[1]; + // Нахождение среднего размера спрайта + var ax:Number = cameraAxes[3] - cx; + var ay:Number = cameraAxes[4] - cy; + var az:Number = cameraAxes[5] - cz; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[6] - cx; + ay = cameraAxes[7] - cy; + az = cameraAxes[8] - cz; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[9] - cx; + ay = cameraAxes[10] - cy; + az = cameraAxes[11] - cz; + size = radius*(size + Math.sqrt(ax*ax + ay*ay + az*az))/3; + boundBox.setSize(cx - size, cy - size, cz - size, cx + size, cy + size, cz + size); + } else { + boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + return boundBox; + } + + public function copyFrom(source:Sprite3D):void { + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + originX = source.originX; + originY = source.originY; + smooth = source.smooth; + clipping = source.clipping; + rotation = source.rotation; + perspectiveScale = source.perspectiveScale; + texture = source.texture; + mipMapping = source.mipMapping; + mipMap = source.mipMap; + matrix.identity(); + matrix.append(source.matrix); + if (source.boundBox != null) { + if (boundBox == null) boundBox = new BoundBox(); + boundBox.copyFrom(source.boundBox); + } else boundBox = null; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/WireBoundBox.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/WireBoundBox.as new file mode 100644 index 0000000..bb1b913 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/WireBoundBox.as @@ -0,0 +1,93 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireBoundBox extends Object3D { + + static private const cameraVertices:Vector. = new Vector.(24, true); + static private const projectedVertices:Vector. = new Vector.(16, true); + static private const uvts:Vector. = new Vector.(24, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + cameraVertices[0] = _boundBox.minX; + cameraVertices[1] = _boundBox.minY; + cameraVertices[2] = _boundBox.minZ; + + cameraVertices[3] = _boundBox.minX; + cameraVertices[4] = _boundBox.minY; + cameraVertices[5] = _boundBox.maxZ; + + cameraVertices[6] = _boundBox.minX; + cameraVertices[7] = _boundBox.maxY; + cameraVertices[8] = _boundBox.minZ; + + cameraVertices[9] = _boundBox.minX; + cameraVertices[10] = _boundBox.maxY; + cameraVertices[11] = _boundBox.maxZ; + + cameraVertices[12] = _boundBox.maxX; + cameraVertices[13] = _boundBox.minY; + cameraVertices[14] = _boundBox.minZ; + + cameraVertices[15] = _boundBox.maxX; + cameraVertices[16] = _boundBox.minY; + cameraVertices[17] = _boundBox.maxZ; + + cameraVertices[18] = _boundBox.maxX; + cameraVertices[19] = _boundBox.maxY; + cameraVertices[20] = _boundBox.minZ; + + cameraVertices[21] = _boundBox.maxX; + cameraVertices[22] = _boundBox.maxY; + cameraVertices[23] = _boundBox.maxZ; + + object.cameraMatrix.transformVectors(cameraVertices, cameraVertices); + for (var i:int = 0; i < 8; i++) { + if (cameraVertices[int(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.moveTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.moveTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.moveTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/objects/WireQuad.as b/Alternativa3D7/7.1/alternativa/engine3d/objects/WireQuad.as new file mode 100644 index 0000000..62441d0 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/objects/WireQuad.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireQuad extends Object3D { + + public var vertices:Vector.; + static private const cameraVertices:Vector. = new Vector.(12, true); + static private const projectedVertices:Vector. = new Vector.(8, true); + static private const uvts:Vector. = new Vector.(12, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + for (var i:int = 0; i < 4; i++) { + if (cameraVertices[(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/primitives/Box.as b/Alternativa3D7/7.1/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..7ce1ae8 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,246 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class Box extends Mesh { + + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + var hp:uint = heightSegments + 1; + + createEmptyGeometry((wp*(lp + hp) + lp*hp) << 1, (widthSegments*(lengthSegments + heightSegments) + lengthSegments*heightSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var hh:Number = height*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var u:uint = 0; + var f:uint = 0; + + // Нижняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v] = (widthSegments - x)*wd; + uvs[u++] = uvts[v++]; + vertices[v] = y*ls - lh; + uvts[v] = (lengthSegments - y)*ld; + uvs[u++] = uvts[v++]; + vertices[v++] = -hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + indices[f++] = x*lp + y; + + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y; + } else { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = (x + 1)*lp + y; + + indices[f++] = x*lp + y; + indices[f++] = x*lp + y + 1; + indices[f++] = (x + 1)*lp + y + 1; + } + } + } + } + var o:uint = wp*lp; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v] = x*wd; + uvs[u++] = uvts[v++]; + vertices[v] = y*ls - lh; + uvts[v] = (lengthSegments - y)*ld; + uvs[u++] = uvts[v++]; + vertices[v++] = hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = o + x*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y; + + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + x*lp + y; + } else { + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y + 1; + } + } + } + } + o += wp*lp; + + // Передняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v] = x*wd; + uvs[u++] = uvts[v++]; + vertices[v] = -lh; + uvts[v] = (heightSegments - z)*hd; + uvs[u++] = uvts[v++]; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z; + + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + } + } + } + } + o += wp*hp; + + // Задняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v] = (widthSegments - x)*wd; + uvs[u++] = uvts[v++]; + vertices[v] = lh; + uvts[v] = (heightSegments - z)*hd; + uvs[u++] = uvts[v++]; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + } + } + } + } + o += wp*hp; + + // Левая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = -wh; + uvts[v] = (lengthSegments - y)*ld; + uvs[u++] = uvts[v++]; + vertices[v] = y*ls - lh; + uvts[v] = (heightSegments - z)*hd; + uvs[u++] = uvts[v++]; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + } + } + } + } + o += lp*hp; + + // Правая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = wh; + uvts[v] = y*ld; + uvs[u++] = uvts[v++]; + vertices[v] = y*ls - lh; + uvts[v] = (heightSegments - z)*hd; + uvs[u++] = uvts[v++]; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z; + + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, -hh, wh, lh, hh); + + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D7/7.1/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..4f02581 --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,246 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class GeoSphere extends Mesh { + + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + + const sections:uint = 20; + + var i:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + + var v:uint = 0; + + var f:uint = sections*segments*segments; + createEmptyGeometry(f/2 + 2, f); + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = radius; + + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*2*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = subz; + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = -subz; + } + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = -radius; + + for (i = 1; i < 6; i++) { + v = interpolate(0, i, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i % 5 + 1, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i + 5, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, (i + 3) % 5 + 6, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i + 5, i % 5 + 6, segments, v); + } + for (i = 6; i < 11; i++) { + v = interpolate(11, i, segments, v); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, v); + } + } + + for (i = 0; i < numVertices; i++) { + var j:uint = i*3; + uvts[j] = Math.atan2(vertices[j + 1], vertices[j])/(Math.PI*2); + uvts[j] = 0.5 + (reverse ? -uvts[j] : uvts[j]); + uvts[j + 1] = 0.5 + Math.asin(vertices[j + 2]/radius)/Math.PI; + uvs[i << 1] = uvts[j]; + uvs[(i << 1) + 1] = uvts[j + 1]; + } + + var num:uint = 0; + for (f = 0; f <= sections - 1; f++) { + for (var row:uint = 0; row <= segments - 1; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + + if (reverse) { + indices[num++] = a; + indices[num++] = c; + indices[num++] = b; + } else { + indices[num++] = a; + indices[num++] = b; + indices[num++] = c; + } + + if (column < row) { + var d:uint = findVertices(segments, f, row, column + 1); + if (reverse) { + indices[num++] = a; + indices[num++] = d; + indices[num++] = c; + } else { + indices[num++] = a; + indices[num++] = c; + indices[num++] = d; + } + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + + private function interpolate(a:uint, b:uint, num:uint, v:uint):uint { + if (num < 2) { + return v; + } + a *= 3; + b *= 3; + var ax:Number = vertices[a]; + var ay:Number = vertices[a + 1]; + var az:Number = vertices[a + 2]; + var bx:Number = vertices[b]; + var by:Number = vertices[b + 1]; + var bz:Number = vertices[b + 2]; + var cos:Number = (ax*bx + ay*by + az*bz)/(ax*ax + ay*ay + az*az); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + vertices[v++] = (ax*st2 + bx*st1)/sin; + vertices[v++] = (ay*st2 + by*st1)/sin; + vertices[v++] = (az*st2 + bz*st1)/sin; + } + return v; + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.1/alternativa/engine3d/primitives/Plane.as b/Alternativa3D7/7.1/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..519885d --- /dev/null +++ b/Alternativa3D7/7.1/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,70 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * + */ + public class Plane extends Mesh { + + /** + * + * @param width + * @param length + * @param widthSegments + * @param lengthSegments + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + + createEmptyGeometry(wp*lp, (widthSegments*lengthSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var u:uint = 0; + var f:uint = 0; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v] = x*wd; + uvs[u++] = uvts[v++]; + vertices[v] = y*ls - lh; + uvts[v] = (lengthSegments - y)*ld; + uvs[u++] = uvts[v++]; + vertices[v++] = 0; + + if (x < widthSegments && y < lengthSegments) { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + } + } + } + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, 0, wh, lh, 0); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/Alternativa3D.as b/Alternativa3D7/7.2/alternativa/Alternativa3D.as new file mode 100644 index 0000000..b6a7b32 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "7.0.0"; + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/alternativa3d.as b/Alternativa3D7/7.2/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/AverageZContainer.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/AverageZContainer.as new file mode 100644 index 0000000..688303d --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/AverageZContainer.as @@ -0,0 +1,77 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого отрисовываются по удалённости от камеры + */ + public class AverageZContainer extends Object3DContainer { + + static private const averageZ:Vector. = new Vector.(); + static private const center:Vector. = Vector.([0, 0, 0]); + static private const cameraCenter:Vector. = new Vector.(3, true); + static private const sortingStack:Vector. = new Vector.(); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var j:int; + var l:int = 0; + var r:int = numVisibleChildren - 1; + var child:Object3D; + var sortingStackIndex:int; + var sortingLeft:Number; + var sortingMedian:Number; + var sortingRight:Number; + var sortingChild:Object3D; + // Сортировка + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + child.cameraMatrix.transformVectors(center, cameraCenter); + averageZ[i] = cameraCenter[0]*cameraCenter[0] + cameraCenter[1]*cameraCenter[1] + cameraCenter[2]*cameraCenter[2]; + } + sortingStack[0] = l; + sortingStack[1] = r; + sortingStackIndex = 2; + while (sortingStackIndex > 0) { + j = r = sortingStack[--sortingStackIndex]; + i = l = sortingStack[--sortingStackIndex]; + sortingMedian = averageZ[(r + l) >> 1]; + do { + while ((sortingLeft = averageZ[i]) > sortingMedian) i++; + while ((sortingRight = averageZ[j]) < sortingMedian) j--; + if (i <= j) { + sortingChild = visibleChildren[i]; + visibleChildren[i] = visibleChildren[j]; + visibleChildren[j] = sortingChild; + averageZ[i++] = sortingRight; + averageZ[j--] = sortingLeft; + } + } while (i <= j); + if (l < j) { + sortingStack[sortingStackIndex++] = l; + sortingStack[sortingStackIndex++] = j; + } + if (i < r) { + sortingStack[sortingStackIndex++] = i; + sortingStack[sortingStackIndex++] = r; + } + } + // Отрисовка + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/BSPTree.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/BSPTree.as new file mode 100644 index 0000000..e283f47 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/BSPTree.as @@ -0,0 +1,14 @@ +package alternativa.engine3d.containers { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class BSPTree extends ConflictContainer { + + + + + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/ConflictContainer.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/ConflictContainer.as new file mode 100644 index 0000000..c83e0b7 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/ConflictContainer.as @@ -0,0 +1,1437 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class ConflictContainer extends Object3DContainer { + + public var resolveByAABB:Boolean = false; + public var resolveByOOBB:Boolean = false; + + //public var isolateAABBConflicts:Boolean = false; + //public var isolateOOBBConflicts:Boolean = false; + + public var threshold:Number = 0.1; + + // Вспомогательные + static private const sortingFragments:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private var negativeReserve:Fragment = Fragment.create(); + static private var positiveReserve:Fragment = Fragment.create(); + + // Камера в контейнере + static private const coords:Vector. = new Vector.(3, true); + protected var inverseCameraMatrix:Matrix3D = new Matrix3D(); + protected var cameraX:Number; + protected var cameraY:Number; + protected var cameraZ:Number; + + // Плоскости отсечения камеры в контейнере + static private const cameraPlanes:Vector. = new Vector.(21, true); + private var nearPlaneX:Number; + private var nearPlaneY:Number; + private var nearPlaneZ:Number; + private var nearPlaneOffset:Number; + private var farPlaneX:Number; + private var farPlaneY:Number; + private var farPlaneZ:Number; + private var farPlaneOffset:Number; + private var leftPlaneX:Number; + private var leftPlaneY:Number; + private var leftPlaneZ:Number; + private var leftPlaneOffset:Number; + private var rightPlaneX:Number; + private var rightPlaneY:Number; + private var rightPlaneZ:Number; + private var rightPlaneOffset:Number; + private var topPlaneX:Number; + private var topPlaneY:Number; + private var topPlaneZ:Number; + private var topPlaneOffset:Number; + private var bottomPlaneX:Number; + private var bottomPlaneY:Number; + private var bottomPlaneZ:Number; + private var bottomPlaneOffset:Number; + + // Перекрытия + static private const edgeOccluder:Vector. = new Vector.(); + private var occluders:Vector.> = new Vector.>(); + protected var numOccluders:int; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + // Если есть видимая геометрия + if (geometry != null) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Если объектов несколько + if (geometry.next != null) { + var current:Geometry; + // Расчёт инверсной матрицы камеры и позицци камеры в контейнере + calculateInverseCameraMatrix(object.cameraMatrix); + // AABB + if (resolveByAABB) { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + inverseCameraMatrix.transformVectors(current.vertices, current.vertices); + current.calculateAABB(); + current = current.next; + } + drawAABBGeometry(camera, object, canvas, geometry); + // OOBB + } else if (resolveByOOBB) { + current = geometry; + while (current != null) { + if (!current.viewAligned) { + current.calculateOOBB(); + } + current = current.next; + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } else { + if (camera.debugMode) geometry.debug(camera, object, canvas, threshold, 0); + geometry.draw(camera, canvas, threshold); + geometry.destroy(); + } + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function calculateInverseCameraMatrix(matrix:Matrix3D):void { + coords[0] = 0; + coords[1] = 0; + coords[2] = 0; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(matrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(coords, coords); + cameraX = coords[0]; + cameraY = coords[1]; + cameraZ = coords[2]; + } + + protected function calculateCameraPlanes(camera:Camera3D):void { + // Перевод плоскостей камеры в пространство контейнера + cameraPlanes[0] = cameraPlanes[1] = cameraPlanes[2] = cameraPlanes[3] = cameraPlanes[4] = cameraPlanes[6] = cameraPlanes[7] = 0; + cameraPlanes[5] = camera.nearClipping; + cameraPlanes[8] = camera.farClipping; + cameraPlanes[9] = cameraPlanes[10] = cameraPlanes[13] = cameraPlanes[18] = -1; + cameraPlanes[11] = cameraPlanes[12] = cameraPlanes[14] = cameraPlanes[15] = cameraPlanes[16] = cameraPlanes[17] = cameraPlanes[19] = cameraPlanes[20] = 1; + inverseCameraMatrix.transformVectors(cameraPlanes, cameraPlanes); + // Ближняя плоскость + var bax:Number = cameraPlanes[9] - cameraPlanes[12]; + var bay:Number = cameraPlanes[10] - cameraPlanes[13]; + var baz:Number = cameraPlanes[11] - cameraPlanes[14]; + var bcx:Number = cameraPlanes[15] - cameraPlanes[12]; + var bcy:Number = cameraPlanes[16] - cameraPlanes[13]; + var bcz:Number = cameraPlanes[17] - cameraPlanes[14]; + nearPlaneX = bcy*baz - bcz*bay; + nearPlaneY = bcz*bax - bcx*baz; + nearPlaneZ = bcx*bay - bcy*bax; + nearPlaneOffset = cameraPlanes[3]*nearPlaneX + cameraPlanes[4]*nearPlaneY + cameraPlanes[5]*nearPlaneZ; + // Дальняя плоскость + farPlaneX = -nearPlaneX; + farPlaneY = -nearPlaneY; + farPlaneZ = -nearPlaneZ; + farPlaneOffset = cameraPlanes[6]*farPlaneX + cameraPlanes[7]*farPlaneY + cameraPlanes[8]*farPlaneZ; + // Рёбра пирамиды + var ax:Number = cameraPlanes[9] - cameraX; + var ay:Number = cameraPlanes[10] - cameraY; + var az:Number = cameraPlanes[11] - cameraZ; + var bx:Number = cameraPlanes[12] - cameraX; + var by:Number = cameraPlanes[13] - cameraY; + var bz:Number = cameraPlanes[14] - cameraZ; + var cx:Number = cameraPlanes[15] - cameraX; + var cy:Number = cameraPlanes[16] - cameraY; + var cz:Number = cameraPlanes[17] - cameraZ; + var dx:Number = cameraPlanes[18] - cameraX; + var dy:Number = cameraPlanes[19] - cameraY; + var dz:Number = cameraPlanes[20] - cameraZ; + // Левая плоскость + leftPlaneX = dy*az - dz*ay; + leftPlaneY = dz*ax - dx*az; + leftPlaneZ = dx*ay - dy*ax; + leftPlaneOffset = cameraX*leftPlaneX + cameraY*leftPlaneY + cameraZ*leftPlaneZ; + // Правая плоскость + rightPlaneX = by*cz - bz*cy; + rightPlaneY = bz*cx - bx*cz; + rightPlaneZ = bx*cy - by*cx; + rightPlaneOffset = cameraX*rightPlaneX + cameraY*rightPlaneY + cameraZ*rightPlaneZ; + // Верхняя плоскость + topPlaneX = ay*bz - az*by; + topPlaneY = az*bx - ax*bz; + topPlaneZ = ax*by - ay*bx; + topPlaneOffset = cameraX*topPlaneX + cameraY*topPlaneY + cameraZ*topPlaneZ; + // Нижняя плоскость + bottomPlaneX = cy*dz - cz*dy; + bottomPlaneY = cz*dx - cx*dz; + bottomPlaneZ = cx*dy - cy*dx; + bottomPlaneOffset = cameraX*bottomPlaneX + cameraY*bottomPlaneY + cameraZ*bottomPlaneZ; + } + + protected function updateOccluders(camera:Camera3D):void { + for (var o:int = numOccluders, occluder:Vector.; o < camera.numOccluders; o++) { + var cameraEdgeOccluder:Vector. = camera.occlusionEdges[o], edgeOccluderLength:int = cameraEdgeOccluder.length; + edgeOccluder.length = edgeOccluderLength; + // Перевод точек рёбер окклюдеров в пространство контейнера + inverseCameraMatrix.transformVectors(cameraEdgeOccluder, edgeOccluder); + // Создание окклюдера в контейнере + if (occluders.length > numOccluders) occluder = occluders[numOccluders++] else occluder = occluders[numOccluders++] = new Vector.(); + // Построение плоскостей отсечения + for (var i:int = 0, ni:int = 0, nx:Number, ny:Number, nz:Number; i < edgeOccluderLength;) { + var ax:Number = edgeOccluder[i++] - cameraX, ay:Number = edgeOccluder[i++] - cameraY, az:Number = edgeOccluder[i++] - cameraZ, bx:Number = edgeOccluder[i++] - cameraX, by:Number = edgeOccluder[i++] - cameraY, bz:Number = edgeOccluder[i++] - cameraZ; + occluder[ni++] = nx = bz*ay - by*az, occluder[ni++] = ny = bx*az - bz*ax, occluder[ni++] = nz = by*ax - bx*ay, occluder[ni++] = cameraX*nx + cameraY*ny + cameraZ*nz; + } + occluder.length = ni; + } + } + + protected function cullingInContainer(camera:Camera3D, boundBox:BoundBox, culling:int):int { + if (camera.occludedAll) return -1; + if (culling > 0) { + // Отсечение по ниар + if (culling & 1) { + if (nearPlaneX >= 0) if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.maxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.minZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundBox.minX*nearPlaneX + boundBox.minY*nearPlaneY + boundBox.minZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundBox.maxX*nearPlaneX + boundBox.maxY*nearPlaneY + boundBox.maxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } + } + // Отсечение по фар + if (culling & 2) { + if (farPlaneX >= 0) if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.maxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.minZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundBox.minX*farPlaneX + boundBox.minY*farPlaneY + boundBox.minZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundBox.maxX*farPlaneX + boundBox.maxY*farPlaneY + boundBox.maxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } + } + // Отсечение по левой стороне + if (culling & 4) { + if (leftPlaneX >= 0) if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.maxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.minZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundBox.minX*leftPlaneX + boundBox.minY*leftPlaneY + boundBox.minZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundBox.maxX*leftPlaneX + boundBox.maxY*leftPlaneY + boundBox.maxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } + } + // Отсечение по правой стороне + if (culling & 8) { + if (rightPlaneX >= 0) if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.maxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.minZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundBox.minX*rightPlaneX + boundBox.minY*rightPlaneY + boundBox.minZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundBox.maxX*rightPlaneX + boundBox.maxY*rightPlaneY + boundBox.maxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } + } + // Отсечение по верхней стороне + if (culling & 16) { + if (topPlaneX >= 0) if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.maxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.minZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundBox.minX*topPlaneX + boundBox.minY*topPlaneY + boundBox.minZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundBox.maxX*topPlaneX + boundBox.maxY*topPlaneY + boundBox.maxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } + } + // Отсечение по нижней стороне + if (culling & 32) { + if (bottomPlaneX >= 0) if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.minZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundBox.minX*bottomPlaneX + boundBox.minY*bottomPlaneY + boundBox.minZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundBox.maxX*bottomPlaneX + boundBox.maxY*bottomPlaneY + boundBox.maxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } + } + } + // Отсечение по окклюдерам + for (var o:int = 0; o < numOccluders; o++) { + var occluder:Vector. = occluders[o], occluderLength:int = occluder.length; + for (var ni:int = 0; ni < occluderLength; ni += 4) { + var nx:Number = occluder[ni], ny:Number = occluder[int(ni + 1)], nz:Number = occluder[int(ni + 2)], no:Number = occluder[int(ni + 3)]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.maxX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.maxY*ny + boundBox.minZ*nz > no) break; + } else if (nz >= 0) { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.maxZ*nz > no) break; + } else { + if (boundBox.minX*nx + boundBox.minY*ny + boundBox.minZ*nz > no) break; + } + } + if (ni == occluderLength) return -1; + } + return culling; + } + + protected function occludeGeometry(camera:Camera3D, geometry:Geometry):Boolean { + if (camera.occludedAll) return true; + for (var i:int = geometry.numOccluders; i < numOccluders; i++) { + var occluder:Vector. = occluders[i]; + var occluderLength:int = occluder.length; + var j:int = 0; + for (; j < occluderLength; j++) { + var nx:Number = occluder[j]; j++; + var ny:Number = occluder[j]; j++; + var nz:Number = occluder[j]; j++; + var no:Number = occluder[j]; + if (nx >= 0) if (ny >= 0) if (nz >= 0) { + if (geometry.maxX*nx + geometry.maxY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.maxX*nx + geometry.maxY*ny + geometry.minZ*nz > no) break; + } else if (nz >= 0) { + if (geometry.maxX*nx + geometry.minY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.maxX*nx + geometry.minY*ny + geometry.minZ*nz > no) break; + } else if (ny >= 0) if (nz >= 0) { + if (geometry.minX*nx + geometry.maxY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.minX*nx + geometry.maxY*ny + geometry.minZ*nz > no) break; + } else if (nz >= 0) { + if (geometry.minX*nx + geometry.minY*ny + geometry.maxZ*nz > no) break; + } else { + if (geometry.minX*nx + geometry.minY*ny + geometry.minZ*nz > no) break; + } + } + if (j == occluderLength) return true; + } + geometry.numOccluders = numOccluders; + return false; + } + + protected function drawAABBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var coord:Number; + var coordMin:Number; + var coordMax:Number; + var axisX:Boolean; + var axisY:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + // Поиск сплита + while (current != null) { + // Сплиты по оси X + coord = current.minX; + coordMin = coord - threshold; + coordMax = coord + threshold; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxX <= coordMax) { + outside = true; + } else if (compared.minX < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + coord = current.maxX; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minX >= coordMin) { + outside = true; + } else if (compared.maxX > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + // Сплиты по оси Y + coord = current.minY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxY <= coordMax) { + outside = true; + } else if (compared.minY < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + coord = current.maxY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minY >= coordMin) { + outside = true; + } else if (compared.maxY > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + // Сплиты по оси Z + coord = current.minZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.maxZ <= coordMax) { + outside = true; + } else if (compared.minZ < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + coord = current.maxZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.minZ >= coordMin) { + outside = true; + } else if (compared.maxZ > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + while (geometry != null) { + next = geometry.next; + var min:Number = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + var max:Number = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max > coordMax) { + geometry.next = positive; + positive = geometry; + } else if (min < coordMin) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Определение положения камеры + if (axisX && cameraX > coord || axisY && cameraY > coord || !axisX && !axisY && cameraZ > coord) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + positive.draw(camera, canvas, threshold, object.cameraMatrix); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + negative.draw(camera, canvas, threshold, object.cameraMatrix); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + negative.draw(camera, canvas, threshold, object.cameraMatrix); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + positive.draw(camera, canvas, threshold, object.cameraMatrix); + positive.destroy(); + } + } + } + // Если не найден сплит + } else if (resolveByOOBB) { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + object.cameraMatrix.transformVectors(current.vertices, current.vertices); + if (!current.viewAligned) { + current.calculateOOBB(); + } + current = current.next; + } + drawOOBBGeometry(camera, object, canvas, geometry); + } else { + current = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + object.cameraMatrix.transformVectors(current.vertices, current.vertices); + current = current.next; + } + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawOOBBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var j:int; + var x:Number; + var y:Number; + var z:Number; + var o:Number; + var planeX:Number; + var planeY:Number; + var planeZ:Number; + var planeOffset:Number; + var behind:Boolean; + var infront:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + var points:Vector.; + var pointsLength:int; + // Поиск сплита + while (current != null) { + if (current.viewAligned) { + planeOffset = current.vertices[2]; + compared = geometry; + while (compared != null) { + if (!compared.viewAligned) { + behind = false; + infront = false; + // Перебор точек + for (j = 2; j < 24; j += 3) { + o = compared.points[j] - planeOffset; + if (o > 0) { + if (behind) { + break; + } else { + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (j < 24) break; + } + compared = compared.next; + } + // Если не встретилось препятствий + if (compared == null) break; + } else { + // Перебор плоскостей + for (i = 0; i < 24; i++) { + planeX = current.planes[i]; i++; + planeY = current.planes[i]; i++; + planeZ = current.planes[i]; i++; + planeOffset = current.planes[i]; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + behind = false; + infront = false; + if (compared.viewAligned) { + points = compared.vertices; + pointsLength = compared.verticesLength; + } else { + points = compared.points; + pointsLength = 24; + } + // Перебор точек + for (j = 0; j < pointsLength; j++) { + x = points[j]; j++; + y = points[j]; j++; + z = points[j]; + o = x*planeX + y*planeY + z*planeZ - planeOffset; + if (o >= -threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (j < pointsLength) break; + } + compared = compared.next; + } + // Если не встретилось препятствий и есть объекты по обе стороны + if (compared == null && outside) break; + } + // Если найдена разделяющая плоскость + if (i < 24) break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (current.viewAligned) { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + o = geometry.vertices[2] - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else { + for (j = 2; j < 24; j += 3) { + o = geometry.points[j] - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + break; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + break; + } + } + if (j == 24) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } else { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + points = geometry.vertices; + pointsLength = geometry.verticesLength; + } else { + points = geometry.points; + pointsLength = 24; + } + for (j = 0; j < pointsLength; j++) { + x = points[j]; j++; + y = points[j]; j++; + z = points[j]; + o = x*planeX + y*planeY + z*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (j == pointsLength) { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + } + // Определение положения камеры + if (current.viewAligned || planeOffset < 0) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 2); + positive.draw(camera, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 2); + middle.draw(camera, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 2); + negative.draw(camera, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + if (camera.debugMode) negative.debug(camera, object, canvas, threshold, 2); + negative.draw(camera, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 2); + middle.draw(camera, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + if (camera.debugMode) positive.debug(camera, object, canvas, threshold, 2); + positive.draw(camera, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawConflictGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var j:int; + var next:Geometry; + var fragment:Fragment; + // Геометрия с сортировкой предрасчитанное BSP + var bspGeometry:Geometry; + // Геометрия, которая присутствует в конфликте + var conflict:Geometry; + // Фрагменты с сортировкой динамическое BSP + var dynamicBSPFirst:Fragment; + var dynamicBSPLast:Fragment; + // Фрагменты с сортировкой по средним Z + var averageZFirst:Fragment; + var averageZLast:Fragment; + // Перебор геометрических объектов + while (geometry != null) { + next = geometry.next; + // Сортировка по предрасчитанному BSP + if (geometry.sorting == 2) { + geometry.next = bspGeometry; + bspGeometry = geometry; + } else { + // Сортировка по динамическому BSP + if (geometry.sorting == 3) { + if (dynamicBSPFirst != null) { + dynamicBSPLast.next = geometry.fragment; + } else { + dynamicBSPFirst = geometry.fragment; + dynamicBSPLast = dynamicBSPFirst; + dynamicBSPLast.geometry = geometry; + } + while (dynamicBSPLast.next != null) { + dynamicBSPLast = dynamicBSPLast.next; + dynamicBSPLast.geometry = geometry; + } + // Сортировка по средним Z + } else { + if (averageZFirst != null) { + averageZLast.next = geometry.fragment; + } else { + averageZFirst = geometry.fragment; + averageZLast = averageZFirst; + averageZLast.geometry = geometry; + } + while (averageZLast.next != null) { + averageZLast = averageZLast.next; + averageZLast.geometry = geometry; + } + } + geometry.fragment = null; + geometry.next = conflict; + conflict = geometry; + } + geometry = next; + } + // Соединение списков + if (conflict != null) { + geometry = conflict; + while (geometry.next != null) { + geometry = geometry.next; + } + geometry.next = bspGeometry; + } else { + conflict = bspGeometry; + } + // Разрешение конфликта + if (conflict != null) { + // Сбор первоначальной кучи фрагментов + var result:Fragment; + if (dynamicBSPFirst != null) { + result = dynamicBSPFirst; + if (averageZFirst != null) { + dynamicBSPLast.next = averageZFirst; + } + } else { + if (averageZFirst != null) { + result = averageZFirst; + } + } + // Если есть статические BSP + if (bspGeometry != null) { + // Встройка кучи в первый bsp с внутренней сортировкой + result = collectNode(bspGeometry.fragment, result, bspGeometry, true); + result.positive = null; + bspGeometry = bspGeometry.next; + // Встройка кучи в остальные bsp без внутренней сортировки + while (bspGeometry != null) { + result = collectNode(bspGeometry.fragment, result, bspGeometry, false); + result.positive = null; + bspGeometry.fragment = null; + bspGeometry = bspGeometry.next; + } + // Если есть динамические BSP + } else if (dynamicBSPFirst != null) { + result = result.next; + dynamicBSPFirst.next = null; + result = collectNode(dynamicBSPFirst, result, null, true); + result.positive = null; + // Если есть сортировка по средним Z + } else if (averageZFirst != null) { + result = sortFragments(result); + result.positive = null; + } + // Проецирование + geometry = conflict; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + geometry.uvts.length = geometry.verticesLength; + geometry.projectedVertices.length = geometry.numVertices << 1; + Utils3D.projectVectors(camera.projectionMatrix, geometry.vertices, geometry.projectedVertices, geometry.uvts); + geometry = geometry.next; + } + // Сбор отрисовочных вызовов + geometry = result.geometry; + fragment = result; + while (fragment.next != null) { + if (fragment.next.geometry != geometry) { + fragment.next.negative = result; + result = fragment.next; + fragment.next = null; + geometry = result.geometry; + fragment = result; + } else { + fragment = fragment.next; + } + } + // Дебаг + if (camera.debugMode) { + var debugCanvas:Canvas = canvas.getChildCanvas(true, false); + debugCanvas.gfx.lineStyle(0, 0xFF0000); + fragment = result; + while (fragment != null) { + fragment.geometry.debugPart(camera, debugCanvas, fragment); + fragment = fragment.negative; + } + } + // Отрисовка + fragment = result; + do { + result = fragment; + fragment = result.negative; + result.negative = null; + result.geometry.drawPart(camera, canvas, result); + } while (fragment != null); + // Зачистка + while (conflict != null) { + next = conflict.next; + conflict.destroy(); + conflict = next; + } + } + } + + // На выходе список, positive первого элемента которого указывает на последний элемент + private function collectNode(splitter:Fragment, source:Fragment, geometry:Geometry, sort:Boolean):Fragment { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var nodeFirst:Fragment; + var nodeLast:Fragment; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + // Сбор фрагментов ноды + if (geometry == null) { + nodeFirst = splitter; + nodeLast = splitter; + } else if (splitter.num > 0) { + nodeFirst = splitter; + nodeLast = splitter; + nodeLast.geometry = geometry; + while (nodeLast.next != null) { + nodeLast = nodeLast.next; + nodeLast.geometry = geometry; + } + } + // Перебор входной последовательности + while (source != null) { + next = source.next; + var sourceGeometry:Geometry = source.geometry; + var vertices:Vector. = sourceGeometry.vertices; + var uvts:Vector. = sourceGeometry.uvts; + var v:int = sourceGeometry.numVertices; + var vi:int = sourceGeometry.verticesLength; + var indices:Vector. = source.indices; + var num:int = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + var a:int = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (var i:int = 0; i < num; i++) { + // Вторая точка ребра + var b:int = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + vertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + vertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; vi++; + negativeIndices[negativeNum] = v; + negativeNum++; + positiveIndices[positiveNum] = v; + positiveNum++; + v++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + sourceGeometry.numVertices = v; + sourceGeometry.verticesLength = vi; + negative.num = negativeNum; + positive.num = positiveNum; + negative.geometry = sourceGeometry; + positive.geometry = sourceGeometry; + if (sourceGeometry.sorting == 3) { + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + } + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.geometry = null; + source.destroy(); + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = null; + if (nodeFirst != null) { + nodeLast.next = source; + } else { + nodeFirst = source; + } + nodeLast = source; + } + source = next; + } + // Сбор задней части + if (splitter.negative != null) { + negativeFirst = collectNode(splitter.negative, negativeFirst, geometry, sort); + negativeLast = negativeFirst.positive; + negativeFirst.positive = null; + splitter.negative = null; + } else if (sort && negativeFirst != negativeLast) { + if (negativeFirst.geometry.sorting == 3) { + next = negativeFirst.next; + negativeFirst.next = null; + negativeFirst = collectNode(negativeFirst, next, null, sort); + } else { + negativeFirst = sortFragments(negativeFirst); + } + negativeLast = negativeFirst.positive; + negativeFirst.positive = null; + } + // Сбор передней части + if (splitter.positive != null) { + positiveFirst = collectNode(splitter.positive, positiveFirst, geometry, sort); + positiveLast = positiveFirst.positive; + positiveFirst.positive = null; + splitter.positive = null; + } else if (sort && positiveFirst != positiveLast) { + if (positiveFirst.geometry.sorting == 3) { + next = positiveFirst.next; + positiveFirst.next = null; + positiveFirst = collectNode(positiveFirst, next, null, sort); + } else { + positiveFirst = sortFragments(positiveFirst); + } + positiveLast = positiveFirst.positive; + positiveFirst.positive = null; + } + // Если камера спереди + if (splitter.offset < 0) { + if (negativeFirst != null) { + if (nodeFirst != null) { + negativeLast.next = nodeFirst; + if (positiveFirst != null) { + nodeLast.next = positiveFirst; + negativeFirst.positive = positiveLast; + } else { + negativeFirst.positive = nodeLast; + } + } else { + if (positiveFirst != null) { + negativeLast.next = positiveFirst; + negativeFirst.positive = positiveLast; + } else { + negativeFirst.positive = negativeLast; + } + } + return negativeFirst; + } else if (nodeFirst != null) { + if (positiveFirst != null) { + nodeLast.next = positiveFirst; + nodeFirst.positive = positiveLast; + } else { + nodeFirst.positive = nodeLast; + } + return nodeFirst; + } else { + positiveFirst.positive = positiveLast; + return positiveFirst; + } + } else { + if (positiveFirst != null) { + if (nodeFirst != null) { + positiveLast.next = nodeFirst; + if (negativeFirst != null) { + nodeLast.next = negativeFirst; + positiveFirst.positive = negativeLast; + } else { + positiveFirst.positive = nodeLast; + } + } else { + if (negativeFirst != null) { + positiveLast.next = negativeFirst; + positiveFirst.positive = negativeLast; + } else { + positiveFirst.positive = positiveLast; + } + } + return positiveFirst; + } else if (nodeFirst != null) { + if (negativeFirst != null) { + nodeLast.next = negativeFirst; + nodeFirst.positive = negativeLast; + } else { + nodeFirst.positive = nodeLast; + } + return nodeFirst; + } else { + negativeFirst.positive = negativeLast; + return negativeFirst; + } + } + } + + // На выходе список, positive первого элемента которого указывает на последний элемент + private function sortFragments(source:Fragment):Fragment { + var i:int; + var j:int; + var next:Fragment; + var first:Fragment; + var last:Fragment; + var fragments:Vector. = sortingFragments; + var fragmentsLength:int = 0; + // Заполнение вектора + while (source != null) { + next = source.next; + source.next = null; + var vertices:Vector. = source.geometry.vertices; + var indices:Vector. = source.indices; + var num:int = source.num; + var sum:Number = 0; + for (i = 0; i < num; i++) { + var vi:int = indices[i]*3 + 2; + sum += vertices[vi]; + } + source.offset = sum/num; + fragments[fragmentsLength] = source; + fragmentsLength++; + source = next; + } + // Сортировка + var stack:Vector. = sortingStack; + stack[0] = 0; + stack[1] = fragmentsLength - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + var k:int = r + l; + var t:int = k >> 1; + next = fragments[t]; + var median:Number = next.offset; + while (i <= j) { + var left:Fragment = fragments[i]; + while (left.offset > median) { + i++; + left = fragments[i]; + } + var right:Fragment = fragments[j]; + while (right.offset < median) { + j--; + right = fragments[j]; + } + if (i <= j) { + fragments[i] = right; + fragments[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Сбор + for (i = 0; i < fragmentsLength; i++) { + next = fragments[i]; + if (first != null) { + last.next = next; + } else { + first = next; + } + last = next; + fragments[i] = null; + } + first.positive = last; + return first; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/DirectionContainer.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/DirectionContainer.as new file mode 100644 index 0000000..0a1d15e --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/DirectionContainer.as @@ -0,0 +1,38 @@ +package alternativa.engine3d.containers { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class DirectionContainer extends Object3DContainer { + + public var direction:Vector3D = new Vector3D(0, 0, -1); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var child:Object3D; + if (Vector3D.Z_AXIS.dotProduct(object.cameraMatrix.deltaTransformVector(direction)) < 0) { + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } else { + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/KDTree.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/KDTree.as new file mode 100644 index 0000000..4384615 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/KDTree.as @@ -0,0 +1,826 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Node; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Occluder; + import alternativa.engine3d.objects.Reference; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого помещены в бинарную древовидную структуру. + * Для построения дерева нужно добавить статические дочерние объекты + * с помощью addStaticChild(), затем вызвать createTree(). По баундам статических объектов + * построится ориентированная по осям бинарная древовидная структура (KD - частный случай BSP). + * Динамические объекты, можно в любое время добавлять addDynamicChild() и удалять removeDynamicChild(). + * Объекты, добавленные с помощью addChild() будут отрисовываться поверх всего в порядке добавления. + */ + public class KDTree extends ConflictContainer { + + public var debugAlphaFade:Number = 0.8; + + private var rootNode:Node; + + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0 || rootNode != null; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Если есть корневая нода + if (rootNode != null) { + // Расчёт инверсной матрицы камеры и позицци камеры в контейнере + calculateInverseCameraMatrix(object.cameraMatrix); + // Расчёт плоскостей камеры в контейнере + calculateCameraPlanes(camera); + // Проверка на видимость рутовой ноды + var culling:int = cullingInContainer(camera, rootNode.boundBox, object.culling); + if (culling >= 0) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Окклюдеры + numOccluders = 0; + if (camera.numOccluders > 0) { + updateOccluders(camera); + } + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + var current:Geometry = geometry; + while (current != null) { + current.vertices.length = current.verticesLength; + inverseCameraMatrix.transformVectors(current.vertices, current.vertices); + current.calculateAABB(); + current = current.next; + } + // Отрисовка дерева + drawNode(rootNode, culling, camera, object, canvas, geometry); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } else { + super.draw(camera, object, parentCanvas); + } + } else { + super.draw(camera, object, parentCanvas); + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + // Ноды + if (debugResult & Debug.NODES) { + if (rootNode != null) { + var culling:int = cullingInContainer(camera, rootNode.boundBox, object.culling); + if (culling >= 0) { + debugNode(rootNode, culling, camera, object, canvas, 1); + } + } + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + // Отрисовка ноды + private function drawNode(node:Node, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + var negativePart:Geometry; + var positivePart:Geometry; + if (camera.occludedAll) { + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + return; + } + var nodeObjects:Vector. = node.objects; + var nodeNumObjects:int = node.numObjects; + var nodeNumNonOccluders:int = node.numNonOccluders; + var staticChild:Object3D; + // Узловая нода + if (node.negative != null) { + var negativeCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.negative.boundBox, culling) : 0; + var positiveCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(camera, node.positive.boundBox, culling) : 0; + var axisX:Boolean = node.normalX != 0; + var axisY:Boolean = node.normalY != 0; + var min:Number; + var max:Number; + // Если видны обе дочерние ноды + if (negativeCulling >= 0 && positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max <= node.offsetMax) { + if (min < node.offsetMin) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else { + if (min >= node.offsetMin) { + geometry.next = positive; + positive = geometry; + } else { + negativePart = geometry.create(); + positivePart = geometry.create(); + geometry.split(axisX, axisY, node.offset, threshold, negativePart, positivePart); + geometry.destroy(); + // Если негативный не пустой + if (negativePart.fragment != null) { + negativePart.next = negative; + negative = negativePart; + } else { + negativePart.destroy(); + } + // Если позитивный не пустой + if (positivePart.fragment != null) { + positivePart.next = positive; + positive = positivePart; + } else { + positivePart.destroy(); + } + } + } + } + geometry = next; + } + // Отрисовка дочерних нод и объектов в плоскости + if (axisX && cameraX > node.offset || axisY && cameraY > node.offset || !axisX && !axisY && cameraZ > node.offset) { + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) { + updateOccluders(camera); + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + } else { + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + } + middle.destroy(); + middle = next; + } + // Отрисовка статических объектов в ноде + for (i = 0; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + if (nodeNumObjects > nodeNumNonOccluders) { + updateOccluders(camera); + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + } + // Если видна только негативная + } else if (negativeCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (max <= node.offsetMax) { + geometry.next = negative; + negative = geometry; + } else if (min < node.offsetMin) { + negativePart = geometry.create(); + geometry.split(axisX, axisY, node.offset, threshold, negativePart, null); + geometry.destroy(); + // Если негативный не пустой + if (negativePart.fragment != null) { + negativePart.next = negative; + negative = negativePart; + } else { + negativePart.destroy(); + } + } + } + geometry = next; + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Если видна только позитивная + } else if (positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.minX : (axisY ? geometry.minY : geometry.minZ); + max = axisX ? geometry.maxX : (axisY ? geometry.maxY : geometry.maxZ); + if (min >= node.offsetMin) { + geometry.next = positive; + positive = geometry; + } else if (max > node.offsetMax) { + positivePart = geometry.create(); + geometry.split(axisX, axisY, node.offset, threshold, null, positivePart); + geometry.destroy(); + // Если позитивный не пустой + if (positivePart.fragment != null) { + positivePart.next = positive; + positive = positivePart; + } else { + positivePart.destroy(); + } + } + } + geometry = next; + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + } + // Конечная нода + } else { + // Если есть статические объекты, не считая окклюдеры + if (nodeNumNonOccluders > 0) { + // Если есть конфликт + if (nodeNumNonOccluders > 1 || geometry != null) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Превращение статиков в геометрию + for (i = 0; i < nodeNumNonOccluders; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + geometry = staticChild.getGeometry(camera, staticChild); + while (geometry != null) { + next = geometry.next; + geometry.next = middle; + middle = geometry; + geometry = next; + } + } + } + // Разруливаем конфликт + if (middle != null) { + if (middle.next != null) { + drawConflictGeometry(camera, object, canvas, middle); + } else { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1); + middle.draw(camera, canvas, threshold); + middle.destroy(); + } + } + } else { + // Если только один статик + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + staticChild.culling = culling; + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Если нет статических объектов + } else { + // Если есть динамические объекты + if (geometry != null) { + // Если динамических объектов несколько + if (geometry.next != null) { + // Если есть окклюдеры + if (numOccluders > 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Если остались объекты + if (middle != null) { + if (middle.next != null) { + // Разруливание + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + if (!geometry.viewAligned) { + geometry.calculateOOBB(); + } + geometry = geometry.next; + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry = geometry.next; + } + drawConflictGeometry(camera, object, canvas, middle); + } + } else { + if (camera.debugMode) middle.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + middle.draw(camera, canvas, threshold, object.cameraMatrix); + middle.destroy(); + } + } + } else { + // Разруливание + middle = geometry; + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + if (!geometry.viewAligned) { + geometry.calculateOOBB(); + } + geometry = geometry.next; + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + geometry = middle; + while (geometry != null) { + geometry.vertices.length = geometry.verticesLength; + object.cameraMatrix.transformVectors(geometry.vertices, geometry.vertices); + geometry = geometry.next; + } + drawConflictGeometry(camera, object, canvas, middle); + } + } + } else { + // Проверка с окклюдерами и отрисовка + if (geometry.numOccluders >= numOccluders || !occludeGeometry(camera, geometry)) { + if (camera.debugMode) geometry.debug(camera, object, canvas, threshold, 1, object.cameraMatrix); + geometry.draw(camera, canvas, threshold, object.cameraMatrix); + } + geometry.destroy(); + } + } + } + // Если в ноде есть окклюдеры + if (nodeNumObjects > nodeNumNonOccluders) { + for (i = nodeNumNonOccluders; i < nodeNumObjects; i++) { + staticChild = nodeObjects[i]; + if (staticChild.visible && staticChild.canDraw && ((staticChild.culling = culling) == 0 && numOccluders == 0 || (staticChild.culling = cullingInContainer(camera, node.bounds[i], culling)) >= 0)) { + staticChild.cameraMatrix.identity(); + staticChild.cameraMatrix.prepend(object.cameraMatrix); + staticChild.cameraMatrix.prepend(staticChild.matrix); + if (camera.debugMode) staticChild.debug(camera, staticChild, canvas); + staticChild.draw(camera, staticChild, canvas); + } + } + // Обновление окклюдеров + updateOccluders(camera); + } + } + } + + static private const nodeVertices:Vector. = new Vector.(12, true); + static private const nodeProjectedVertices:Vector. = new Vector.(8, true); + static private const nodeUVTs:Vector. = new Vector.(12, true); + + private function debugNode(node:Node, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, alpha:Number):void { + if (node.negative != null) { + var negativeCulling:int = (culling > 0) ? cullingInContainer(camera, node.negative.boundBox, culling) : 0; + var positiveCulling:int = (culling > 0) ? cullingInContainer(camera, node.positive.boundBox, culling) : 0; + if (negativeCulling >= 0) { + debugNode(node.negative, negativeCulling, camera, object, canvas, alpha*debugAlphaFade); + } + if (positiveCulling >= 0) { + debugNode(node.positive, positiveCulling, camera, object, canvas, alpha*debugAlphaFade); + } + + if (node.normalX) { + nodeVertices[0] = node.offset; + nodeVertices[1] = node.boundBox.minY; + nodeVertices[2] = node.boundBox.maxZ; + + nodeVertices[3] = node.offset; + nodeVertices[4] = node.boundBox.maxY; + nodeVertices[5] = node.boundBox.maxZ; + + nodeVertices[6] = node.offset; + nodeVertices[7] = node.boundBox.maxY; + nodeVertices[8] = node.boundBox.minZ; + + nodeVertices[9] = node.offset; + nodeVertices[10] = node.boundBox.minY; + nodeVertices[11] = node.boundBox.minZ; + } else if (node.normalY) { + nodeVertices[0] = node.boundBox.maxX; + nodeVertices[1] = node.offset; + nodeVertices[2] = node.boundBox.maxZ; + + nodeVertices[3] = node.boundBox.minX; + nodeVertices[4] = node.offset; + nodeVertices[5] = node.boundBox.maxZ; + + nodeVertices[6] = node.boundBox.minX; + nodeVertices[7] = node.offset; + nodeVertices[8] = node.boundBox.minZ; + + nodeVertices[9] = node.boundBox.maxX; + nodeVertices[10] = node.offset; + nodeVertices[11] = node.boundBox.minZ; + } else { + nodeVertices[0] = node.boundBox.minX; + nodeVertices[1] = node.boundBox.minY; + nodeVertices[2] = node.offset; + + nodeVertices[3] = node.boundBox.maxX; + nodeVertices[4] = node.boundBox.minY; + nodeVertices[5] = node.offset; + + nodeVertices[6] = node.boundBox.maxX; + nodeVertices[7] = node.boundBox.maxY; + nodeVertices[8] = node.offset; + + nodeVertices[9] = node.boundBox.minX; + nodeVertices[10] = node.boundBox.maxY; + nodeVertices[11] = node.offset; + } + object.cameraMatrix.transformVectors(nodeVertices, nodeVertices); + var i:int; + for (i = 0; i < 12; i += 3) { + if (nodeVertices[int(i + 2)] <= 0) break; + } + if (i == 12) { + Utils3D.projectVectors(camera.projectionMatrix, nodeVertices, nodeProjectedVertices, nodeUVTs); + canvas.gfx.lineStyle(0, node.normalX ? 0xFF0000 : (node.normalY ? 0x00FF00 : 0x0000FF)); + canvas.gfx.moveTo(nodeProjectedVertices[0], nodeProjectedVertices[1]); + canvas.gfx.lineTo(nodeProjectedVertices[2], nodeProjectedVertices[3]); + canvas.gfx.lineTo(nodeProjectedVertices[4], nodeProjectedVertices[5]); + canvas.gfx.lineTo(nodeProjectedVertices[6], nodeProjectedVertices[7]); + canvas.gfx.lineTo(nodeProjectedVertices[0], nodeProjectedVertices[1]); + } + } + } + + public function createTree(staticObjects:Vector., boundBox:BoundBox = null):void { + var numStaticChildren:int = staticObjects.length; + if (numStaticChildren > 0) { + // Создаём корневую ноду + rootNode = new Node(); + rootNode.objects = new Vector.(); + rootNode.bounds = new Vector.(); + rootNode.numObjects = 0; + // Расчитываем баунды объектов и рутовой ноды + rootNode.boundBox = (boundBox != null) ? boundBox : new BoundBox(); + // Сначала добавляем не окклюдеры + var staticOccluders:Vector. = new Vector.(); + var staticOccludersLength:int = 0; + var object:Object3D; + var objectBoundBox:BoundBox; + for (var i:int = 0; i < numStaticChildren; i++) { + object = staticObjects[i]; + // Поиск оригинального объекта + var source:Object3D = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + // Если окклюдер + if (source is Occluder) { + staticOccluders[staticOccludersLength++] = object; + } else { + objectBoundBox = object.calculateBoundBox(object.matrix); + rootNode.objects[rootNode.numObjects] = object; + rootNode.bounds[rootNode.numObjects++] = objectBoundBox; + rootNode.boundBox.addBoundBox(objectBoundBox); + } + } + // Добавляем окклюдеры + for (i = 0; i < staticOccludersLength; i++) { + object = staticOccluders[i]; + objectBoundBox = object.calculateBoundBox(object.matrix); + rootNode.objects[rootNode.numObjects] = object; + rootNode.bounds[rootNode.numObjects++] = objectBoundBox; + rootNode.boundBox.addBoundBox(objectBoundBox); + } + // Разделяем рутовую ноду + splitNode(rootNode); + } + } + + private var splitAxis:int; + private var splitCoord:Number; + private var splitCost:Number; + static private const nodeBoundBoxThreshold:BoundBox = new BoundBox(); + static private const splitCoordsX:Vector. = new Vector.(); + static private const splitCoordsY:Vector. = new Vector.(); + static private const splitCoordsZ:Vector. = new Vector.(); + private function splitNode(node:Node):void { + + var object:Object3D, boundBox:BoundBox, i:int, j:int, k:int, c1:Number, c2:Number, coordMin:Number, coordMax:Number, area:Number, areaNegative:Number, areaPositive:Number, numNegative:int, numPositive:int, conflict:Boolean, cost:Number; + var nodeBoundBox:BoundBox = node.boundBox; + + // Подготовка баунда с погрешностями + nodeBoundBoxThreshold.minX = nodeBoundBox.minX + threshold; + nodeBoundBoxThreshold.minY = nodeBoundBox.minY + threshold; + nodeBoundBoxThreshold.minZ = nodeBoundBox.minZ + threshold; + nodeBoundBoxThreshold.maxX = nodeBoundBox.maxX - threshold; + nodeBoundBoxThreshold.maxY = nodeBoundBox.maxY - threshold; + nodeBoundBoxThreshold.maxZ = nodeBoundBox.maxZ - threshold; + var doubleThreshold:Number = threshold + threshold; + + // Собираем опорные координаты + var numSplitCoordsX:int = 0, numSplitCoordsY:int = 0, numSplitCoordsZ:int = 0; + for (i = 0; i < node.numObjects; i++) { + boundBox = node.bounds[i]; + if (boundBox.maxX - boundBox.minX <= doubleThreshold) { + if (boundBox.minX <= nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.minX; + else if (boundBox.maxX >= nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = nodeBoundBox.maxX; + else splitCoordsX[numSplitCoordsX++] = (boundBox.minX + boundBox.maxX)*0.5; + } else { + if (boundBox.minX > nodeBoundBoxThreshold.minX) splitCoordsX[numSplitCoordsX++] = boundBox.minX; + if (boundBox.maxX < nodeBoundBoxThreshold.maxX) splitCoordsX[numSplitCoordsX++] = boundBox.maxX; + } + if (boundBox.maxY - boundBox.minY <= doubleThreshold) { + if (boundBox.minY <= nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.minY; + else if (boundBox.maxY >= nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = nodeBoundBox.maxY; + else splitCoordsY[numSplitCoordsY++] = (boundBox.minY + boundBox.maxY)*0.5; + } else { + if (boundBox.minY > nodeBoundBoxThreshold.minY) splitCoordsY[numSplitCoordsY++] = boundBox.minY; + if (boundBox.maxY < nodeBoundBoxThreshold.maxY) splitCoordsY[numSplitCoordsY++] = boundBox.maxY; + } + if (boundBox.maxZ - boundBox.minZ <= doubleThreshold) { + if (boundBox.minZ <= nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.minZ; + else if (boundBox.maxZ >= nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = nodeBoundBox.maxZ; + else splitCoordsZ[numSplitCoordsZ++] = (boundBox.minZ + boundBox.maxZ)*0.5; + } else { + if (boundBox.minZ > nodeBoundBoxThreshold.minZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.minZ; + if (boundBox.maxZ < nodeBoundBoxThreshold.maxZ) splitCoordsZ[numSplitCoordsZ++] = boundBox.maxZ; + } + } + + // Убираем дубликаты координат, ищем наилучший сплит + splitAxis = -1; splitCost = Number.MAX_VALUE; + i = 0; area = (nodeBoundBox.maxY - nodeBoundBox.minY)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsX) { + if (!isNaN(c1 = splitCoordsX[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minX); + areaPositive = area*(nodeBoundBox.maxX - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxX <= coordMax) { + if (boundBox.minX < coordMin) numNegative++; + } else { + if (boundBox.minX >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 0; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsX) if ((c2 = splitCoordsX[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsX[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxZ - nodeBoundBox.minZ); + while (i < numSplitCoordsY) { + if (!isNaN(c1 = splitCoordsY[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minY); + areaPositive = area*(nodeBoundBox.maxY - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxY <= coordMax) { + if (boundBox.minY < coordMin) numNegative++; + } else { + if (boundBox.minY >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 1; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsY) if ((c2 = splitCoordsY[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsY[j] = NaN; + } + } + i = 0; area = (nodeBoundBox.maxX - nodeBoundBox.minX)*(nodeBoundBox.maxY - nodeBoundBox.minY); + while (i < numSplitCoordsZ) { + if (!isNaN(c1 = splitCoordsZ[i++])) { + coordMin = c1 - threshold; + coordMax = c1 + threshold; + areaNegative = area*(c1 - nodeBoundBox.minZ); + areaPositive = area*(nodeBoundBox.maxZ - c1); + numNegative = numPositive = 0; + conflict = false; + // Проверяем объекты + for (j = 0; j < node.numObjects; j++) { + boundBox = node.bounds[j]; + if (boundBox.maxZ <= coordMax) { + if (boundBox.minZ < coordMin) numNegative++; + } else { + if (boundBox.minZ >= coordMin) numPositive++; else {conflict = true; break;} + } + } + // Если хороший сплит, сохраняем + if (!conflict && (cost = areaNegative*numNegative + areaPositive*numPositive) < splitCost) { + splitCost = cost; + splitAxis = 2; + splitCoord = c1; + } + j = i; + while (++j < numSplitCoordsZ) if ((c2 = splitCoordsZ[j]) >= c1 - threshold && c2 <= c1 + threshold) splitCoordsZ[j] = NaN; + } + } + + // Если сплит не найден, выходим + if (splitAxis < 0) { + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + return; + } + + // Разделяем ноду + if (splitAxis == 0) { + node.normalX = 1; + node.normalY = 0; + node.normalZ = 0; + } else if (splitAxis == 1) { + node.normalX = 0; + node.normalY = 1; + node.normalZ = 0; + } else { + node.normalX = 0; + node.normalY = 0; + node.normalZ = 1; + } + node.offset = splitCoord; + node.offsetMin = splitCoord - threshold; + node.offsetMax = splitCoord + threshold; + + // Создаём дочерние ноды + node.negative = new Node(); + node.positive = new Node(); + node.negative.boundBox = nodeBoundBox.clone(); + node.positive.boundBox = nodeBoundBox.clone(); + node.negative.numObjects = 0; + node.positive.numObjects = 0; + if (node.normalX) { + node.negative.boundBox.maxX = node.positive.boundBox.minX = splitCoord; + } else if (node.normalY) { + node.negative.boundBox.maxY = node.positive.boundBox.minY = splitCoord; + } else { + node.negative.boundBox.maxZ = node.positive.boundBox.minZ = splitCoord; + } + // Распределяем объекты по дочерним нодам + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + boundBox = node.bounds[i]; + var min:Number = node.normalX ? boundBox.minX : (node.normalY ? boundBox.minY : boundBox.minZ); + var max:Number = node.normalX ? boundBox.maxX : (node.normalY ? boundBox.maxY : boundBox.maxZ); + if (max <= node.offsetMax) { + if (min < node.offsetMin) { + // Объект в негативной стороне + if (node.negative.objects == null) node.negative.objects = new Vector.(), node.negative.bounds = new Vector.(); + node.negative.objects[node.negative.numObjects] = object, node.negative.bounds[node.negative.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Остаётся в ноде + } + } else { + if (min >= node.offsetMin) { + // Объект в положительной стороне + if (node.positive.objects == null) node.positive.objects = new Vector.(), node.positive.bounds = new Vector.(); + node.positive.objects[node.positive.numObjects] = object, node.positive.bounds[node.positive.numObjects++] = boundBox; + node.objects[i] = null, node.bounds[i] = null; + } else { + // Распилился + } + } + } + + // Очистка списка объектов + for (i = 0, j = 0; i < node.numObjects; i++) if (node.objects[i] != null) node.objects[j] = node.objects[i], node.bounds[j++] = node.bounds[i]; + if (j > 0) { + node.numObjects = node.objects.length = node.bounds.length = j; + // Находим, откуда начинаются окклюдеры + for (i = 0; i < node.numObjects; i++) { + object = node.objects[i]; + // Поиск оригинального объекта + while (object is Reference) object = (object as Reference).referenceObject; + if (object is Occluder) break; + } + node.numNonOccluders = i; + } else { + node.numObjects = node.numNonOccluders = 0, node.objects = null, node.bounds = null; + } + + // Разделение дочерних нод + if (node.negative.objects != null) splitNode(node.negative); + if (node.positive.objects != null) splitNode(node.positive); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/SkyBox.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/SkyBox.as new file mode 100644 index 0000000..7b65ee4 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/SkyBox.as @@ -0,0 +1,141 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BitmapData; + + public class SkyBox extends Object3DContainer { + + private var backPlane:Mesh; + private var frontPlane:Mesh; + private var topPlane:Mesh; + private var bottomPlane:Mesh; + private var leftPlane:Mesh; + private var rightPlane:Mesh; + + public function SkyBox(size:Number = 1000):void { + backPlane = new Mesh(); + frontPlane = new Mesh(); + topPlane = new Mesh(); + bottomPlane = new Mesh(); + leftPlane = new Mesh(); + rightPlane = new Mesh(); + + backPlane.clipping = 2; + frontPlane.clipping = 2; + topPlane.clipping = 2; + bottomPlane.clipping = 2; + leftPlane.clipping = 2; + rightPlane.clipping = 2; + + backPlane.vertices = Vector.([ + -size, -size, -size, + -size, -size, size, + size, -size, size, + size, -size, -size, + ]); + frontPlane.vertices = Vector.([ + size, size, -size, + size, size, size, + -size, size, size, + -size, size, -size, + ]); + topPlane.vertices = Vector.([ + size, size, size, + size, -size, size, + -size, -size, size, + -size, size, size, + ]); + bottomPlane.vertices = Vector.([ + size, -size, -size, + size, size, -size, + -size, size, -size, + -size, -size, -size, + ]); + leftPlane.vertices = Vector.([ + -size, size, -size, + -size, size, size, + -size, -size, size, + -size, -size, -size, + ]); + rightPlane.vertices = Vector.([ + size, -size, -size, + size, -size, size, + size, size, size, + size, size, -size, + ]); + + var uvts:Vector. = Vector.([1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]); + backPlane.uvts = uvts; + frontPlane.uvts = uvts; + topPlane.uvts = uvts; + bottomPlane.uvts = uvts; + leftPlane.uvts = uvts; + rightPlane.uvts = uvts; + + var indices:Vector. = Vector.([4, 0, 1, 2, 3]); + backPlane.indices = indices; + frontPlane.indices = indices; + topPlane.indices = indices; + bottomPlane.indices = indices; + leftPlane.indices = indices; + rightPlane.indices = indices; + + backPlane.numVertices = 4; + frontPlane.numVertices = 4; + topPlane.numVertices = 4; + bottomPlane.numVertices = 4; + leftPlane.numVertices = 4; + rightPlane.numVertices = 4; + + backPlane.numFaces = 1; + frontPlane.numFaces = 1; + topPlane.numFaces = 1; + bottomPlane.numFaces = 1; + leftPlane.numFaces = 1; + rightPlane.numFaces = 1; + + backPlane.poly = true; + frontPlane.poly = true; + topPlane.poly = true; + bottomPlane.poly = true; + leftPlane.poly = true; + rightPlane.poly = true; + + addChild(backPlane); + addChild(frontPlane); + addChild(topPlane); + addChild(bottomPlane); + addChild(leftPlane); + addChild(rightPlane); + } + + public function set backTexture(value:BitmapData):void { + backPlane.texture = value; + } + + public function set frontTexture(value:BitmapData):void { + frontPlane.texture = value; + } + + public function set topTexture(value:BitmapData):void { + topPlane.texture = value; + } + + public function set bottomTexture(value:BitmapData):void { + bottomPlane.texture = value; + } + + public function set leftTexture(value:BitmapData):void { + leftPlane.texture = value; + } + + public function set rightTexture(value:BitmapData):void { + rightPlane.texture = value; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/containers/SplitContainer.as b/Alternativa3D7/7.2/alternativa/engine3d/containers/SplitContainer.as new file mode 100644 index 0000000..8d7354a --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/containers/SplitContainer.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.containers { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SplitContainer extends Object3DContainer { + + public var splitPlane:Vector3D = new Vector3D(0, 0, 1, 0); + static private const invertCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraPosition:Vector. = Vector.([0, 0, 0]); + static private const invertCameraPosition:Vector. = new Vector.(3, true); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var child:Object3D; + invertCameraMatrix.identity(); + invertCameraMatrix.prepend(object.cameraMatrix); + invertCameraMatrix.invert(); + invertCameraMatrix.transformVectors(cameraPosition, invertCameraPosition); + if (invertCameraPosition[0]*splitPlane.x + invertCameraPosition[1]*splitPlane.y + invertCameraPosition[2]*splitPlane.z < splitPlane.w) { + for (i = 0; i < numVisibleChildren; i++) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } else { + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/controllers/SimpleObjectController.as b/Alternativa3D7/7.2/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..55f6195 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,454 @@ +package alternativa.engine3d.controllers { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + + import flash.display.InteractiveObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.ui.Keyboard; + import flash.utils.getTimer; + + /** + * + */ + public class SimpleObjectController { + + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + + public var speed:Number; + public var speedMultiplier:Number; + public var mouseSensitivity:Number; + public var maxPitch:Number = Number.MAX_VALUE; + public var minPitch:Number = -Number.MAX_VALUE; + + private var eventSource:InteractiveObject; + private var _object:Object3D; + + private var _up:Boolean; + private var _down:Boolean; + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _accelerate:Boolean; + + private var displacement:Vector3D = new Vector3D(); + private var mousePoint:Point = new Point(); + private var mouseLook:Boolean; + private var objectTransform:Vector.; + + private var time:int; + + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответсвующая команде + * клавиша. + */ + private var actionBindings:Object = {}; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Object = {}; + + /** + * + * @param eventSource источник событий для контроллера + * @param speed скорость поступательного перемещения объекта + * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши + */ + public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { + this.eventSource = eventSource; + this.object = object; + this.speed = speed; + this.speedMultiplier = speedMultiplier; + this.mouseSensitivity = mouseSensitivity; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_ACCELERATE] = accelerate; + + setDefaultBindings(); + + enable(); + } + + /** + * Активирует контроллер. + */ + public function enable():void { + eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Дективирует контроллер. + */ + public function disable():void { + eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + stopMouseLook(); + } + + /** + * + */ + private function onMouseDown(e:MouseEvent):void { + startMouseLook(); + } + + /** + * + */ + private function onMouseUp(e:MouseEvent):void { + stopMouseLook(); + } + + /** + * Включает режим взгляда мышью. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Отключает режим взгляда мышью. + */ + public function stopMouseLook():void { + mouseLook = false; + } + + /** + * + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + + /** + * Управляемый объект. + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * + */ + public function get object():Object3D { + return _object; + } + + /** + * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.matrix.decompose(); + } + + /** + * Вычисляет новое положение объекта, используя внутренний счётчик времени. + */ + public function update():void { + if (_object == null) return; + + var frameTime:Number = time; + time = getTimer(); + frameTime = 0.001*(time - frameTime); + if (frameTime > 0.1) frameTime = 0.1; + + var moved:Boolean = false; + + if (mouseLook) { + var dx:Number = eventSource.mouseX - mousePoint.x; + var dy:Number = eventSource.mouseY - mousePoint.y; + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + var v:Vector3D = objectTransform[1]; + v.x -= dy*Math.PI/180*mouseSensitivity; + if (v.x > maxPitch) v.x = maxPitch; + if (v.x < minPitch) v.x = minPitch; + v.z -= dx*Math.PI/180*mouseSensitivity; + moved = true; + } + + displacement.x = _right ? 1 : (_left ? -1 : 0); + displacement.y = _forward ? 1 : (_back ? -1 : 0); + displacement.z = _up ? 1 : (_down ? -1 : 0); + if (displacement.lengthSquared > 0) { + if (_object is Camera3D) { + var tmp:Number = displacement.z; + displacement.z = displacement.y; + displacement.y = -tmp; + } + deltaTransformVector(displacement); + if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); + else displacement.scaleBy(speed*frameTime/displacement.length); + (objectTransform[0] as Vector3D).incrementBy(displacement); + moved = true; + } + + if (moved) _object.matrix.recompose(objectTransform); + } + + /** + * + * @param pos + */ + public function setObjectPos(pos:Vector3D):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = pos.x; + v.y = pos.y; + v.z = pos.z; + } + } + + /** + * + * @param pos + */ + public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = x; + v.y = y; + v.z = z; + } + } + + /** + * + * @param point + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * + * @param x + * @param y + * @param z + */ + public function lookAtXYZ(x:Number, y:Number, z:Number):void { + if (_object == null) return; + var v:Vector3D = objectTransform[0]; + var dx:Number = x - v.x; + var dy:Number = y - v.y; + var dz:Number = z - v.z; + v = objectTransform[1]; + v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); + if (_object is Camera3D) v.x -= 0.5*Math.PI; + v.y = 0; + v.z = -Math.atan2(dx, dy); + _object.matrix.recompose(objectTransform); + } + + private var _vin:Vector. = new Vector.(3); + private var _vout:Vector. = new Vector.(3); + + private function deltaTransformVector(v:Vector3D):void { + _vin[0] = v.x; + _vin[1] = v.y; + _vin[2] = v.z; + _object.matrix.transformVectors(_vin, _vout); + var c:Vector3D = objectTransform[0]; + v.x = _vout[0] - c.x; + v.y = _vout[1] - c.y; + v.z = _vout[2] - c.z; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:* in keyBindings) delete keyBindings[key]; + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + bindKey(87, ACTION_FORWARD); + bindKey(83, ACTION_BACK); + bindKey(65, ACTION_LEFT); + bindKey(68, ACTION_RIGHT); + bindKey(69, ACTION_UP); + bindKey(67, ACTION_DOWN); + bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); + + bindKey(Keyboard.UP, ACTION_FORWARD); + bindKey(Keyboard.DOWN, ACTION_BACK); + bindKey(Keyboard.LEFT, ACTION_LEFT); + bindKey(Keyboard.RIGHT, ACTION_RIGHT); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/BackfaceCulling.as b/Alternativa3D7/7.2/alternativa/engine3d/core/BackfaceCulling.as new file mode 100644 index 0000000..f67b960 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/BackfaceCulling.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.core { + + public class BackfaceCulling { + + /** + * Отсечение по динамически расчитываемым нормалям. + */ + static public const DYNAMIC_NORMALS:int = 0; + /** + * Отсечение по предрасчитанным нормалям. + */ + static public const STATIC_NORMALS:int = 1; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/BoundBox.as b/Alternativa3D7/7.2/alternativa/engine3d/core/BoundBox.as new file mode 100644 index 0000000..d70cc00 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/BoundBox.as @@ -0,0 +1,67 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + import flash.geom.Matrix3D; + + public class BoundBox { + + public var minX:Number = Number.MAX_VALUE; + public var minY:Number = Number.MAX_VALUE; + public var minZ:Number = Number.MAX_VALUE; + public var maxX:Number = -Number.MAX_VALUE; + public var maxY:Number = -Number.MAX_VALUE; + public var maxZ:Number = -Number.MAX_VALUE; + + public function setSize(minX:Number, minY:Number, minZ:Number, maxX:Number, maxY:Number, maxZ:Number):void { + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + } + + public function addBoundBox(boundBox:BoundBox):void { + minX = (boundBox.minX < minX) ? boundBox.minX : minX; + minY = (boundBox.minY < minY) ? boundBox.minY : minY; + minZ = (boundBox.minZ < minZ) ? boundBox.minZ : minZ; + maxX = (boundBox.maxX > maxX) ? boundBox.maxX : maxX; + maxY = (boundBox.maxY > maxY) ? boundBox.maxY : maxY; + maxZ = (boundBox.maxZ > maxZ) ? boundBox.maxZ : maxZ; + } + + public function addPoint(x:Number, y:Number, z:Number):void { + if (x < minX) minX = x; + if (x > maxX) maxX = x; + if (y < minY) minY = y; + if (y > maxY) maxY = y; + if (z < minZ) minZ = z; + if (z > maxZ) maxZ = z; + } + + public function infinity():void { + minX = minY = minZ = Number.MAX_VALUE; + maxX = maxY = maxZ = -Number.MAX_VALUE; + } + + public function copyFrom(boundBox:BoundBox):void { + minX = boundBox.minX; + minY = boundBox.minY; + minZ = boundBox.minZ; + maxX = boundBox.maxX; + maxY = boundBox.maxY; + maxZ = boundBox.maxZ; + } + + public function clone():BoundBox { + var clone:BoundBox = new BoundBox(); + clone.copyFrom(this); + return clone; + } + + public function toString():String { + return "BoundBox [" + minX.toFixed(2) + ", " + minY.toFixed(2) + ", " + minZ.toFixed(2) + " - " + maxX.toFixed(2) + ", " + maxY.toFixed(2) + ", " + maxZ.toFixed(2) + "]"; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Camera3D.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..cc9535d --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,414 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.events.Event; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.System; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + import flash.utils.getTimer; + + use namespace alternativa3d; + + public class Camera3D extends Object3D { + + /** + * Вьюпорт камеры. + */ + public var canvas:Canvas; + public var fov:Number = Math.PI/2; + /** + * Ширина вьюпорта. + */ + public var width:Number = 500; + /** + * Высота вьюпорта. + */ + public var height:Number = 500; + + public var nearClipping:Number = 0; + public var farClipping:Number = 5000; + public var farFalloff:Number = 4000; + + // Матрица проецирования + alternativa3d var projectionMatrix:Matrix3D; + alternativa3d var projectionMatrixData:Vector. = new Vector.(16, true); + // Параметры перспективы + alternativa3d var viewSize:Number; + alternativa3d var viewSizeX:Number; + alternativa3d var viewSizeY:Number; + alternativa3d var perspectiveScaleX:Number; + alternativa3d var perspectiveScaleY:Number; + alternativa3d var focalLength:Number; + // Перекрытия + alternativa3d var occlusionPlanes:Vector.> = new Vector.>(); + alternativa3d var occlusionEdges:Vector.> = new Vector.>(); + alternativa3d var numOccluders:int; + alternativa3d var occludedAll:Boolean; + + public function Camera3D() { + updateProjection(); + } + + override alternativa3d function get canDraw():Boolean { + return false; + } + + /** + * Отрисовка иерархии объектов, в которой находится камера. + * Перед render(), если менялись параметры камеры, нужно вызвать updateProjection(). + */ + public function render():void { + // Расчёт матрицы перевода из рута в камеру + cameraMatrix.identity(); + var object:Object3D = this; + while (object._parent != null) { + cameraMatrix.append(object.matrix); + object = object._parent; + } + cameraMatrix.invert(); + cameraMatrix.appendScale(perspectiveScaleX, perspectiveScaleY, 1); + + numOccluders = 0; + occludedAll = false; + + numTriangles = 0; + + // Отрисовка + if (object.visible && object.canDraw) { + object.cameraMatrix.identity(); + object.cameraMatrix.prepend(cameraMatrix); + object.cameraMatrix.prepend(object.matrix); + if (object.cullingInCamera(this, 63) >= 0) { + // Отрисовка объекта + canvas.numDraws = 0; + if (debugMode) object.debug(this, object, canvas); + object.draw(this, object, canvas); + // Зачистка ненужных канвасов + canvas.removeChildren(canvas.numDraws); + } else { + // Если отсеклось, зачищаем рутовый канвас + canvas.removeChildren(0); + } + } + } + + /** + * После изменения параметров fov, width, height нужно вызвать этот метод. + */ + public function updateProjection():void { + // Расчёт параметров перспективы + viewSize = Math.sqrt(width*width + height*height)*0.5; + focalLength = viewSize/Math.tan(fov*0.5); + viewSizeX = width*0.5; + viewSizeY = height*0.5; + perspectiveScaleX = focalLength/viewSizeX; + perspectiveScaleY = focalLength/viewSizeY; + // Подготовка матрицы проецирования + projectionMatrixData[0] = viewSizeX; + projectionMatrixData[5] = viewSizeY; + projectionMatrixData[10] = 1; + projectionMatrixData[11] = 1; + projectionMatrix = new Matrix3D(projectionMatrixData); + } + + private static var _tmpv:Vector. = new Vector.(3); + + /** + * @param v + * @param result + */ + public function projectGlobal(v:Vector3D, result:Vector3D):void { + _tmpv[0] = v.x; _tmpv[1] = v.y; _tmpv[2] = v.z; + cameraMatrix.transformVectors(_tmpv, _tmpv); + projectionMatrix.transformVectors(_tmpv, _tmpv); + result.z = _tmpv[2]; + result.x = _tmpv[0]/result.z; + result.y = _tmpv[1]/result.z; + } + + // DEBUG + + // Режим отладки + public var debugMode:Boolean = false; + + // Список объектов дебага + private var debugSet:Object = new Object(); + + // Добавить в дебаг + public function addToDebug(debug:int, ... rest):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + for (var i:int = 0; i < rest.length;) debugSet[debug][rest[i++]] = true; + } + + // Убрать из дебага + public function removeFromDebug(debug:int, ... rest):void { + if (debugSet[debug]) { + for (var i:int = 0; i < rest.length;) delete debugSet[debug][rest[i++]]; + for (var key:* in debugSet[debug]); + if (!key) delete debugSet[debug]; + } + } + + // Проверка, находится ли объект или один из классов, от которых он нследован, в дебаге + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 256; debug = debug << 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + public var diagram:Sprite = createDiagram(); + + private var fpsTextField:TextField; + private var memoryTextField:TextField; + private var trianglesTextField:TextField; + private var timerTextField:TextField; + private var graph:Bitmap; + private var rect:Rectangle; + + private var _diagramAlign:String = "TR"; + private var _diagramHorizontalMargin:Number = 2; + private var _diagramVerticalMargin:Number = 2; + + public var fpsUpdatePeriod:int = 10; + private var fpsUpdateCounter:int; + private var previousFrameTime:int; + private var previousPeriodTime:int; + + private var maxMemory:int; + alternativa3d var numTriangles:int; + + public var timerUpdatePeriod:int = 10; + private var timerUpdateCounter:int; + private var timeSum:int; + private var timeCount:int; + private var timer:int; + + private function createDiagram():Sprite { + var diagram:Sprite = new Sprite(); + diagram.mouseEnabled = false; + diagram.mouseChildren = false; + // Инициализация диаграммы + diagram.addEventListener(Event.ADDED_TO_STAGE, function():void { + // FPS + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.LEFT; + fpsTextField.text = "FPS: " + Number(diagram.stage.frameRate).toFixed(2); + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + diagram.addChild(fpsTextField); + // Память + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.LEFT; + memoryTextField.text = "MEM: " + bytesToString(System.totalMemory); + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 4; + diagram.addChild(memoryTextField); + // Треугольники + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.LEFT; + trianglesTextField.text = "TRI: " + 0; + trianglesTextField.selectable = false; + trianglesTextField.x = -2; + trianglesTextField.y = 13; + diagram.addChild(trianglesTextField); + // Время выполнения метода + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.LEFT; + timerTextField.text = "TMR:"; + timerTextField.selectable = false; + timerTextField.x = -2; + timerTextField.y = 13 + 9; + diagram.addChild(timerTextField); + // График + graph = new Bitmap(new BitmapData(60, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40) + graph.x = 0; + graph.y = 27 + 9; + diagram.addChild(graph); + // Сброс параметров + previousFrameTime = previousPeriodTime = getTimer(); + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + // Подписка + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Деинициализация диаграммы + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void { + // Обнуление + diagram.removeChild(fpsTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(graph); + fpsTextField = null; + memoryTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Отписка + diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram); + diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram); + }); + return diagram; + } + + private function resizeDiagram(e:Event = null):void { + if (diagram.stage != null) { + var coord:Point = diagram.parent.globalToLocal(new Point()); + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) { + diagram.x = Math.round(coord.x + _diagramHorizontalMargin); + } + if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth/2 - graph.width/2); + } + if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width); + } + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) { + diagram.y = Math.round(coord.y + _diagramVerticalMargin); + } + if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight/2 - (graph.y + graph.height)/2); + } + if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height); + } + } + } + + private function updateDiagram(e:Event):void { + var fps:Number; + var mod:int; + var time:int = getTimer(); + var stageFrameRate:int = diagram.stage.frameRate; + + // FPS текст + if (++fpsUpdateCounter == fpsUpdatePeriod) { + fps = 1000*fpsUpdatePeriod/(time - previousPeriodTime); + if (fps > stageFrameRate) fps = stageFrameRate; + mod = fps*100 % 100; + fpsTextField.text = "FPS: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS график + fps = 1000/(time - previousFrameTime); + if (fps > stageFrameRate) fps = stageFrameRate; + graph.bitmapData.scroll(1, 0); + graph.bitmapData.fillRect(rect, 0x20FFFFFF); + graph.bitmapData.setPixel32(0, 40*(1 - fps/stageFrameRate), 0xFFCCCCCC); + previousFrameTime = time; + + // Память текст + var memory:int = System.totalMemory; + memoryTextField.text = "MEM: " + bytesToString(memory); + // Память график + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00); + + // Треугольники текст + trianglesTextField.text = "TRI: " + numTriangles; + + // Время текст + if (++timerUpdateCounter == timerUpdatePeriod) { + if (timeCount > 0) { + fps = timeSum/timeCount; + mod = fps*100 % 100; + timerTextField.text = "TMR: " + int(fps) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = "TMR:"; + } + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + } + } + + public function startTimer():void { + timer = getTimer(); + } + + public function stopTimer():void { + timeSum += getTimer() - timer; + timeCount++; + } + + public function get diagramAlign():String { + return _diagramAlign; + } + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + public function get diagramVerticalMargin():Number { + return _diagramVerticalMargin; + } + public function set diagramVerticalMargin(value:Number):void { + _diagramVerticalMargin = value; + resizeDiagram(); + } + + private function bytesToString(bytes:int):String { + if (bytes < 1024) return bytes + "b"; + else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb"; + else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb"; + else if (bytes < 1048576) return (bytes >> 10) + "kb"; + else if (bytes < 10485760) return (bytes/1048576).toFixed(2) + "mb"; + else if (bytes < 104857600) return (bytes/1048576).toFixed(1) + "mb"; + else return (bytes >> 20) + "mb"; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Canvas.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Canvas.as new file mode 100644 index 0000000..f3560c8 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Canvas.as @@ -0,0 +1,104 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Canvas extends Sprite { + + static private const defaultColorTransform:ColorTransform = new ColorTransform(); + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + alternativa3d var gfx:Graphics = graphics; + + private var modifiedGraphics:Boolean; + private var modifiedAlpha:Boolean; + private var modifiedBlendMode:Boolean; + private var modifiedColorTransform:Boolean; + private var modifiedFilters:Boolean; + + alternativa3d var _numChildren:int = 0; + alternativa3d var numDraws:int = 0; + + public function Canvas() { + mouseEnabled = false; + mouseChildren = false; + } + + alternativa3d function getChildCanvas(useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas; + var displayObject:DisplayObject; + // Зачистка не канвасов + while (_numChildren > numDraws && !((displayObject = getChildAt(_numChildren - 1 - numDraws)) is Canvas)) { + removeChild(displayObject); + _numChildren--; + } + // Получение канваса + if (_numChildren > numDraws++) { + canvas = displayObject as Canvas; + // Зачистка + canvas.gfx.clear(); + if (canvas._numChildren > 0 && !useChildren) { + canvas.removeChildren(0); + } + } else { + canvas = (collectorLength > 0) ? collector[--collectorLength] : new Canvas(); + addChildAt(canvas, 0); + _numChildren++; + } + // Пометка о том, что в graphics будет что-то нарисовано + canvas.modifiedGraphics = useGraphics; + // Установка свойств + if (alpha != 1) { + canvas.alpha = alpha; + canvas.modifiedAlpha = true; + } else if (canvas.modifiedAlpha) { + canvas.alpha = 1; + canvas.modifiedAlpha = false; + } + if (blendMode != "normal") { + canvas.blendMode = blendMode; + canvas.modifiedBlendMode = true; + } else if (canvas.modifiedBlendMode) { + canvas.blendMode = "normal"; + canvas.modifiedBlendMode = false; + } + if (colorTransform != null) { + colorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = colorTransform; + canvas.modifiedColorTransform = true; + } else if (canvas.modifiedColorTransform) { + defaultColorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = defaultColorTransform; + canvas.modifiedColorTransform = false; + } + if (filters != null) { + canvas.filters = filters; + canvas.modifiedFilters = true; + } else if (canvas.modifiedFilters) { + canvas.filters = null; + canvas.modifiedFilters = false; + } + return canvas; + } + + alternativa3d function removeChildren(keep:int):void { + for (var canvas:Canvas; _numChildren > keep; _numChildren--) { + if ((canvas = removeChildAt(0) as Canvas) != null) { + if (canvas.modifiedGraphics) canvas.gfx.clear(); + if (canvas._numChildren > 0) canvas.removeChildren(0); + collector[collectorLength++] = canvas; + } + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Clipping.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Clipping.as new file mode 100644 index 0000000..90a0db1 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Clipping.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Clipping { + + /** + * Объект отсекается целиком, если он полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const BOUND_CULLING:int = 0; + /** + * Грань отсекается целиком, если она полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const FACE_CULLING:int = 1; + /** + * Грань подрезается пирамидой видимости камеры. + */ + static public const FACE_CLIPPING:int = 2; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Debug.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..20e427c --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Debug.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class Debug { + + static public const NAMES:int = 1; + static public const AXES:int = 2; + static public const CENTERS:int = 4; + static public const BOUNDS:int = 8; + + static public const EDGES:int = 16; + static public const VERTICES:int = 32; + static public const NORMALS:int = 64; + + static public const NODES:int = 128; + static public const SPLITS:int = 256; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Fragment.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Fragment.as new file mode 100644 index 0000000..30c26c0 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Fragment.as @@ -0,0 +1,53 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + public class Fragment { + + static private var collector:Fragment; + + public var next:Fragment; + + public var negative:Fragment; + public var positive:Fragment; + + public var geometry:Geometry; + + public var indices:Vector. = new Vector.(); + public var num:int = 0; + + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + static public function create():Fragment { + if (collector != null) { + var res:Fragment = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Fragment(); + } + } + + public function create():Fragment { + if (collector != null) { + var res:Fragment = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Fragment(); + } + } + + public function destroy():void { + num = 0; + next = collector; + collector = this; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Geometry.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Geometry.as new file mode 100644 index 0000000..56a3475 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Geometry.as @@ -0,0 +1,1564 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class Geometry { + + static private var collector:Geometry; + + // Вспомогательные + static private const sortingFragments:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private const verticesMap:Vector. = new Vector.(); + static private var negativeReserve:Fragment = Fragment.create(); + static private var positiveReserve:Fragment = Fragment.create(); + + public var next:Geometry; + + public var vertices:Vector. = new Vector.(); + public var uvts:Vector. = new Vector.(); + public var verticesLength:int = 0; + public var numVertices:int = 0; + + public var projectedVertices:Vector. = new Vector.(); + private var drawIndices:Vector. = new Vector.(); + private var drawIndicesLength:int = 0; + + public var fragment:Fragment; + + public var numOccluders:int; + + // Передаваемые при сплите свойства + + public var cameraMatrix:Matrix3D = new Matrix3D(); + public var alpha:Number; + public var blendMode:String; + public var colorTransform:ColorTransform; + public var filters:Array; + + public var sorting:int; + + public var texture:BitmapData; + public var smooth:Boolean; + public var repeatTexture:Boolean; + + public var debugResult:int = 0; + + public var viewAligned:Boolean = false; + public var textureMatrix:Matrix = new Matrix(); + public var projectionX:Number; + public var projectionY:Number; + + // AABB + public var minX:Number; + public var minY:Number; + public var minZ:Number; + public var maxX:Number; + public var maxY:Number; + public var maxZ:Number; + + // OOBB + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const localVertices:Vector. = new Vector.(); + public var points:Vector. = new Vector.(24, true); + public var planes:Vector. = new Vector.(24, true); + + static public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function destroy():void { + numVertices = 0; + verticesLength = 0; + viewAligned = false; + colorTransform = null; + filters = null; + numOccluders = 0; + debugResult = 0; + next = collector; + collector = this; + } + + public function calculateAABB():void { + var verts:Vector. = vertices; + minX = Number.MAX_VALUE; + minY = Number.MAX_VALUE; + minZ = Number.MAX_VALUE; + maxX = -Number.MAX_VALUE; + maxY = -Number.MAX_VALUE; + maxZ = -Number.MAX_VALUE; + for (var i:int = 0, c:Number; i < verticesLength;) { + c = verts[i]; i++; + if (c < minX) minX = c; + if (c > maxX) maxX = c; + c = verts[i]; i++; + if (c < minY) minY = c; + if (c > maxY) maxY = c; + c = verts[i]; i++; + if (c < minZ) minZ = c; + if (c > maxZ) maxZ = c; + } + } + + public function calculateOOBB():void { + var verts:Vector. = localVertices; + vertices.length = verticesLength; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(cameraMatrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(vertices, verts); + minX = Number.MAX_VALUE; + minY = Number.MAX_VALUE; + minZ = Number.MAX_VALUE; + maxX = -Number.MAX_VALUE; + maxY = -Number.MAX_VALUE; + maxZ = -Number.MAX_VALUE; + for (var i:int = 0, c:Number; i < verticesLength;) { + c = verts[i]; i++; + if (c < minX) minX = c; + if (c > maxX) maxX = c; + c = verts[i]; i++; + if (c < minY) minY = c; + if (c > maxY) maxY = c; + c = verts[i]; i++; + if (c < minZ) minZ = c; + if (c > maxZ) maxZ = c; + } + // A + points[0] = minX; + points[1] = minY; + points[2] = minZ; + // B + points[3] = maxX; + points[4] = minY; + points[5] = minZ; + // C + points[6] = minX; + points[7] = maxY; + points[8] = minZ; + // D + points[9] = maxX; + points[10] = maxY; + points[11] = minZ; + // E + points[12] = minX; + points[13] = minY; + points[14] = maxZ; + // F + points[15] = maxX; + points[16] = minY; + points[17] = maxZ; + // G + points[18] = minX; + points[19] = maxY; + points[20] = maxZ; + // H + points[21] = maxX; + points[22] = maxY; + points[23] = maxZ; + // Перевод вершин баунда из локальных координат в камеру + cameraMatrix.transformVectors(points, points); + // Front + var ax:Number = points[0]; + var ay:Number = points[1]; + var az:Number = points[2]; + var abx:Number = points[3] - ax; + var aby:Number = points[4] - ay; + var abz:Number = points[5] - az; + var acx:Number = points[12] - ax; + var acy:Number = points[13] - ay; + var acz:Number = points[14] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[0] = nx; + planes[1] = ny; + planes[2] = nz; + planes[3] = ax*nx + ay*ny + az*nz; + // Back + ax = points[6]; + ay = points[7]; + az = points[8]; + abx = points[18] - ax; + aby = points[19] - ay; + abz = points[20] - az; + acx = points[9] - ax; + acy = points[10] - ay; + acz = points[11] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[4] = nx; + planes[5] = ny; + planes[6] = nz; + planes[7] = ax*nx + ay*ny + az*nz; + // Left + ax = points[0]; + ay = points[1]; + az = points[2]; + abx = points[12] - ax; + aby = points[13] - ay; + abz = points[14] - az; + acx = points[6] - ax; + acy = points[7] - ay; + acz = points[8] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[8] = nx; + planes[9] = ny; + planes[10] = nz; + planes[11] = ax*nx + ay*ny + az*nz; + // Right + ax = points[3]; + ay = points[4]; + az = points[5]; + abx = points[9] - ax; + aby = points[10] - ay; + abz = points[11] - az; + acx = points[15] - ax; + acy = points[16] - ay; + acz = points[17] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[12] = nx; + planes[13] = ny; + planes[14] = nz; + planes[15] = ax*nx + ay*ny + az*nz; + // Top + ax = points[12]; + ay = points[13]; + az = points[14]; + abx = points[15] - ax; + aby = points[16] - ay; + abz = points[17] - az; + acx = points[18] - ax; + acy = points[19] - ay; + acz = points[20] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[16] = nx; + planes[17] = ny; + planes[18] = nz; + planes[19] = ax*nx + ay*ny + az*nz; + // Bottom + ax = points[0]; + ay = points[1]; + az = points[2]; + abx = points[6] - ax; + aby = points[7] - ay; + abz = points[8] - az; + acx = points[3] - ax; + acy = points[4] - ay; + acz = points[5] - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + planes[20] = nx; + planes[21] = ny; + planes[22] = nz; + planes[23] = ax*nx + ay*ny + az*nz; + } + + public function split(axisX:Boolean, axisY:Boolean, coord:Number, threshold:Number, negative:Geometry, positive:Geometry):void { + var i:int; + var c:Number; + var verts:Vector.; + var vertsLen:int; + // Сброс карты + for (i = 0; i < numVertices; i++) verticesMap[i] = -1; + // Разбиение + splitFragments(fragment, negative, positive, axisX, axisY, coord, coord - threshold, coord + threshold); + // Копирование свойств + if (negative != null && fragment.negative != null) { + negative.fragment = fragment.negative; + fragment.negative = null; + negative.texture = texture; + negative.smooth = smooth; + negative.repeatTexture = repeatTexture; + negative.cameraMatrix.identity(); + negative.cameraMatrix.prepend(cameraMatrix); + negative.alpha = alpha; + negative.blendMode = blendMode; + negative.colorTransform = colorTransform; + negative.filters = filters; + negative.sorting = sorting; + negative.debugResult = debugResult; + negative.viewAligned = viewAligned; + if (viewAligned) { + negative.textureMatrix.a = textureMatrix.a; + negative.textureMatrix.b = textureMatrix.b; + negative.textureMatrix.c = textureMatrix.c; + negative.textureMatrix.d = textureMatrix.d; + negative.textureMatrix.tx = textureMatrix.tx; + negative.textureMatrix.ty = textureMatrix.ty; + negative.projectionX = projectionX; + negative.projectionY = projectionY; + } + // Обновление баунда + verts = negative.vertices; + vertsLen = negative.verticesLength; + if (axisX) { + negative.minX = minX; + negative.maxX = coord; + negative.minY = maxY; + negative.maxY = minY; + negative.minZ = maxZ; + negative.maxZ = minZ; + for (i = 0; i < vertsLen;) { + i++; + c = verts[i]; i++; + if (c < negative.minY) negative.minY = c; + if (c > negative.maxY) negative.maxY = c; + c = verts[i]; i++; + if (c < negative.minZ) negative.minZ = c; + if (c > negative.maxZ) negative.maxZ = c; + } + } else if (axisY) { + negative.minX = maxX; + negative.maxX = minX; + negative.minY = minY; + negative.maxY = coord; + negative.minZ = maxZ; + negative.maxZ = minZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < negative.minX) negative.minX = c; + if (c > negative.maxX) negative.maxX = c; + i++; + c = verts[i]; i++; + if (c < negative.minZ) negative.minZ = c; + if (c > negative.maxZ) negative.maxZ = c; + } + } else { + negative.minX = maxX; + negative.minY = maxY; + negative.minZ = minZ; + negative.maxX = minX; + negative.maxY = minY; + negative.maxZ = coord; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < negative.minX) negative.minX = c; + if (c > negative.maxX) negative.maxX = c; + c = verts[i]; i++; + if (c < negative.minY) negative.minY = c; + if (c > negative.maxY) negative.maxY = c; + i++; + } + } + } + if (positive != null && fragment.positive != null) { + positive.fragment = fragment.positive; + fragment.positive = null; + positive.texture = texture; + positive.smooth = smooth; + positive.repeatTexture = repeatTexture; + positive.cameraMatrix.identity(); + positive.cameraMatrix.prepend(cameraMatrix); + positive.alpha = alpha; + positive.blendMode = blendMode; + positive.colorTransform = colorTransform; + positive.filters = filters; + positive.sorting = sorting; + positive.debugResult = debugResult; + positive.viewAligned = viewAligned; + if (viewAligned) { + positive.textureMatrix.a = textureMatrix.a; + positive.textureMatrix.b = textureMatrix.b; + positive.textureMatrix.c = textureMatrix.c; + positive.textureMatrix.d = textureMatrix.d; + positive.textureMatrix.tx = textureMatrix.tx; + positive.textureMatrix.ty = textureMatrix.ty; + positive.projectionX = projectionX; + positive.projectionY = projectionY; + } + // Обновление баунда + verts = positive.vertices; + vertsLen = positive.verticesLength; + if (axisX) { + positive.minX = coord; + positive.maxX = maxX; + positive.minY = maxY; + positive.maxY = minY; + positive.minZ = maxZ; + positive.maxZ = minZ; + for (i = 0; i < vertsLen;) { + i++; + c = verts[i]; i++; + if (c < positive.minY) positive.minY = c; + if (c > positive.maxY) positive.maxY = c; + c = verts[i]; i++; + if (c < positive.minZ) positive.minZ = c; + if (c > positive.maxZ) positive.maxZ = c; + } + } else if (axisY) { + positive.minX = maxX; + positive.maxX = minX; + positive.minY = coord; + positive.maxY = maxY; + positive.minZ = maxZ; + positive.maxZ = minZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < positive.minX) positive.minX = c; + if (c > positive.maxX) positive.maxX = c; + i++; + c = verts[i]; i++; + if (c < positive.minZ) positive.minZ = c; + if (c > positive.maxZ) positive.maxZ = c; + } + } else { + positive.minX = maxX; + positive.maxX = minX; + positive.minY = maxY; + positive.maxY = minY; + positive.minZ = coord; + positive.maxZ = maxZ; + for (i = 0; i < vertsLen;) { + c = verts[i]; i++; + if (c < positive.minX) positive.minX = c; + if (c > positive.maxX) positive.maxX = c; + c = verts[i]; i++; + if (c < positive.minY) positive.minY = c; + if (c > positive.maxY) positive.maxY = c; + i++; + } + } + } + fragment.destroy(); + fragment = null; + } + + private function splitFragments(fragment:Fragment, negativeGeometry:Geometry, positiveGeometry:Geometry, axisX:Boolean, axisY:Boolean, coord:Number, minCoord:Number, maxCoord:Number):void { + var result:Fragment = fragment; + var innegative:Boolean = negativeGeometry != null; + var inpositive:Boolean = positiveGeometry != null; + var split:Boolean = innegative && inpositive; + var crop:Boolean = !split; + var negativeNegative:Fragment; + var negativePositive:Fragment; + var positiveNegative:Fragment; + var positivePositive:Fragment; + // Проход по дочерним нодам + if (result.negative != null) { + splitFragments(result.negative, negativeGeometry, positiveGeometry, axisX, axisY, coord, minCoord, maxCoord); + negativeNegative = result.negative.negative; + negativePositive = result.negative.positive; + result.negative.negative = null; + result.negative.positive = null; + result.negative.destroy(); + } + if (result.positive != null) { + splitFragments(result.positive, negativeGeometry, positiveGeometry, axisX, axisY, coord, minCoord, maxCoord); + positiveNegative = result.positive.negative; + positivePositive = result.positive.positive; + result.positive.negative = null; + result.positive.positive = null; + result.positive.destroy(); + } + // Разделение + var negativeFirst:Fragment; + var negativeLast:Fragment; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var negativeVertices:Vector.; + var negativeUVTs:Vector.; + var positiveVertices:Vector.; + var positiveUVTs:Vector.; + var negV:int; + var negVi:int; + var posV:int; + var posVi:int; + if (innegative) { + negativeVertices = negativeGeometry.vertices; + negativeUVTs = negativeGeometry.uvts; + negV = negativeGeometry.numVertices; + negVi = negativeGeometry.verticesLength; + } + if (inpositive) { + positiveVertices = positiveGeometry.vertices; + positiveUVTs = positiveGeometry.uvts; + posV = positiveGeometry.numVertices; + posVi = positiveGeometry.verticesLength; + } + while (fragment != null) { + var next:Fragment = fragment.next; + if (fragment.num > 0) { + var indices:Vector. = fragment.indices; + var num:int = fragment.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + var a:int = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ac:Number = axisX ? ax : (axisY ? ay : az); + for (var i:int = 0; i < num; i++) { + // Вторая точка ребра + var b:int = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bc:Number = axisX ? bx : (axisY ? by : bz); + // Рассечение ребра + if (split && (bc > maxCoord && ac < minCoord || bc < minCoord && ac > maxCoord) || crop && (innegative && (ac < coord && bc >= coord || bc < coord && ac >= coord) || inpositive && (ac <= coord && bc > coord || bc <= coord && ac > coord))) { + var t:Number = (ac - coord)/(ac - bc); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + var x:Number = ax + (bx - ax)*t; + var y:Number = ay + (by - ay)*t; + var z:Number = az + (bz - az)*t; + var u:Number = au + (bu - au)*t; + var v:Number = av + (bv - av)*t; + if (innegative) { + negativeVertices[negVi] = x; + negativeUVTs[negVi] = u; negVi++; + negativeVertices[negVi] = y; + negativeUVTs[negVi] = v; negVi++; + negativeVertices[negVi] = z; + negativeUVTs[negVi] = 0; negVi++; + negativeIndices[negativeNum] = negV; negativeNum++; negV++; + } + if (inpositive) { + positiveVertices[posVi] = x; + positiveUVTs[posVi] = u; posVi++; + positiveVertices[posVi] = y; + positiveUVTs[posVi] = v; posVi++; + positiveVertices[posVi] = z; + positiveUVTs[posVi] = 0; posVi++; + positiveIndices[positiveNum] = posV; positiveNum++; posV++; + } + } + // Добавление точки + if (split && bc < minCoord || crop && innegative && bc < coord) { + if (verticesMap[b] < 0) { + negativeVertices[negVi] = bx; + negativeUVTs[negVi] = uvts[bi]; negVi++; + negativeVertices[negVi] = by; n = bi + 1; + negativeUVTs[negVi] = uvts[n]; negVi++; + negativeVertices[negVi] = bz; + negativeUVTs[negVi] = 0; negVi++; + negativeIndices[negativeNum] = negV; negativeNum++; + verticesMap[b] = negV; negV++; + } else { + negativeIndices[negativeNum] = verticesMap[b]; negativeNum++; + } + behind = true; + } else if (split && bc > maxCoord || crop && inpositive && bc > coord) { + if (verticesMap[b] < 0) { + positiveVertices[posVi] = bx; + positiveUVTs[posVi] = uvts[bi]; posVi++; + positiveVertices[posVi] = by; n = bi + 1; + positiveUVTs[posVi] = uvts[n]; posVi++; + positiveVertices[posVi] = bz; + positiveUVTs[posVi] = 0; posVi++; + positiveIndices[positiveNum] = posV; positiveNum++; + verticesMap[b] = posV; posV++; + } else { + positiveIndices[positiveNum] = verticesMap[b]; positiveNum++; + } + infront = true; + } else if (split) { + negativeVertices[negVi] = bx; + positiveVertices[posVi] = bx; + negativeUVTs[negVi] = uvts[bi]; negVi++; + positiveUVTs[posVi] = uvts[bi]; posVi++; + negativeVertices[negVi] = by; + positiveVertices[posVi] = by; n = bi + 1; + negativeUVTs[negVi] = uvts[n]; negVi++; + positiveUVTs[posVi] = uvts[n]; posVi++; + negativeVertices[negVi] = bz; + positiveVertices[posVi] = bz; + negativeUVTs[negVi] = 0; negVi++; + positiveUVTs[posVi] = 0; posVi++; + negativeIndices[negativeNum] = negV; negativeNum++; + positiveIndices[positiveNum] = posV; positiveNum++; + verticesMap[b] = negV; negV++; + verticesMap[b] = posV; posV++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ac = bc; + } + // Анализ разбиения + if (behind) { + negativeGeometry.numVertices = negV; + negativeGeometry.verticesLength = negVi; + negative.num = negativeNum; + if (sorting == 3) { + negative.normalX = fragment.normalX; + negative.normalY = fragment.normalY; + negative.normalZ = fragment.normalZ; + negative.offset = fragment.offset; + } + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + negativeReserve = fragment.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + } else if (negativeNum > 0) { + negV = negativeGeometry.numVertices; + negVi = negativeGeometry.verticesLength; + } + if (infront || split && !behind && !infront) { + positiveGeometry.numVertices = posV; + positiveGeometry.verticesLength = posVi; + positive.num = positiveNum; + if (sorting == 3) { + positive.normalX = fragment.normalX; + positive.normalY = fragment.normalY; + positive.normalZ = fragment.normalZ; + positive.offset = fragment.offset; + } + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + positiveReserve = fragment.create(); + positive = positiveReserve; + positiveIndices = positive.indices; + } else if (positiveNum > 0) { + posV = positiveGeometry.numVertices; + posVi = positiveGeometry.verticesLength; + } + } + if (fragment != result) { + fragment.destroy(); + } + fragment = next; + } + // Если сзади от сплита есть фрагменты или обе дочерние ноды + if (negativeFirst != null || negativeNegative != null && positiveNegative != null) { + if (negativeFirst == null) { + negativeFirst = result.create(); + } + if (sorting == 2) { + negativeFirst.normalX = result.normalX; + negativeFirst.normalY = result.normalY; + negativeFirst.normalZ = result.normalZ; + negativeFirst.offset = result.offset; + } + result.negative = negativeFirst; + negativeFirst.negative = negativeNegative; + negativeFirst.positive = positiveNegative; + } else { + result.negative = (negativeNegative != null) ? negativeNegative : positiveNegative; + } + // Если сзади от сплита есть фрагменты или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + if (positiveFirst == null) { + positiveFirst = result.create(); + } + if (sorting == 2) { + positiveFirst.normalX = result.normalX; + positiveFirst.normalY = result.normalY; + positiveFirst.normalZ = result.normalZ; + positiveFirst.offset = result.offset; + } + result.positive = positiveFirst; + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + } else { + result.positive = (negativePositive != null) ? negativePositive : positivePositive; + } + } + + public function draw(camera:Camera3D, parentCanvas:Canvas, threshold:Number, matrix:Matrix3D = null):void { + var i:int; + var j:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + var next:Fragment; + // Перевод в камеру + if (matrix != null) { + vertices.length = verticesLength; + matrix.transformVectors(vertices, vertices); + } + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + if (viewAligned) { + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + var x:Number = vertices[0]*projectionX; + var y:Number = vertices[1]*projectionY; + canvas.gfx.moveTo(x, y); + for (i = 3; i < verticesLength; i++) { + x = vertices[i]*projectionX; i++; + y = vertices[i]*projectionY; i++; + canvas.gfx.lineTo(x, y); + } + fragment.destroy(); + fragment = null; + } else { + // Сброс + drawIndicesLength = 0; + // Без сортировки + if (sorting == 0) { + while (fragment != null) { + next = fragment.next; + indices = fragment.indices; + num = fragment.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + fragment.destroy(); + fragment = next; + } + // Сортировка по средним Z + } else if (sorting == 1) { + var fragments:Vector. = sortingFragments; + var fragmentsLength:int = 0; + // Заполнение вектора + while (fragment != null) { + next = fragment.next; + fragment.next = null; + indices = fragment.indices; + num = fragment.num; + var sum:Number = 0; + for (i = 0; i < num; i++) { + var vi:int = indices[i]*3 + 2; + sum += vertices[vi]; + } + fragment.offset = sum/num; + fragments[fragmentsLength] = fragment; + fragmentsLength++; + fragment = next; + } + // Сортировка + var stack:Vector. = sortingStack; + stack[0] = 0; + stack[1] = fragmentsLength - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + var k:int = r + l; + var t:int = k >> 1; + next = fragments[t]; + var median:Number = next.offset; + while (i <= j) { + var left:Fragment = fragments[i]; + while (left.offset > median) { + i++; + left = fragments[i]; + } + var right:Fragment = fragments[j]; + while (right.offset < median) { + j--; + right = fragments[j]; + } + if (i <= j) { + fragments[i] = right; + fragments[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Сбор + for (i = 0; i < fragmentsLength; i++) { + next = fragments[i]; + indices = next.indices; + num = next.num; + a = indices[0]; + b = indices[1]; + for (j = 2; j < num; j++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[j]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + next.destroy(); + fragments[i] = null; + } + // Статическое BSP + } else if (sorting == 2) { + drawStaticNode(fragment); + fragment = null; + // Динамическое BSP + } else { + next = fragment.next; + fragment.next = null; + drawDynamicNode(fragment, next, threshold); + fragment = null; + } + // Проецирование + vertices.length = verticesLength; + uvts.length = verticesLength; + projectedVertices.length = numVertices << 1; + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "none"); + } + } + + public function drawPart(camera:Camera3D, parentCanvas:Canvas, fragment:Fragment):void { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, alpha, blendMode, colorTransform, filters); + var i:int; + var a:int; + var b:int; + var c:int; + var next:Fragment; + var indices:Vector.; + var num:int; + if (viewAligned) { + // Отрисовка + canvas.gfx.beginBitmapFill(texture, textureMatrix, false, smooth); + while (fragment != null) { + next = fragment.next; + indices = fragment.indices; + num = fragment.num; + a = indices[0]*3; + var x:Number = vertices[a]*projectionX; a++; + var y:Number = vertices[a]*projectionY; + canvas.gfx.moveTo(x, y); + for (i = 1; i < num; i++) { + a = indices[i]*3; + x = vertices[a]*projectionX; a++; + y = vertices[a]*projectionY; + canvas.gfx.lineTo(x, y); + } + fragment.geometry = null; + fragment.destroy(); + fragment = next; + } + } else { + // Сброс + drawIndicesLength = 0; + // Перебор + while (fragment != null) { + next = fragment.next; + indices = fragment.indices; + num = fragment.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + fragment.geometry = null; + fragment.destroy(); + fragment = next; + } + // Отрисовка + canvas.gfx.beginBitmapFill(texture, null, repeatTexture, smooth); + drawIndices.length = drawIndicesLength; + camera.numTriangles += drawIndicesLength/3; + canvas.gfx.drawTriangles(projectedVertices, drawIndices, uvts, "none"); + } + } + + private function drawStaticNode(splitter:Fragment):void { + var negative:Fragment = splitter.negative; + var positive:Fragment = splitter.positive; + splitter.negative = null; + splitter.positive = null; + if (splitter.offset < 0) { + if (negative != null) { + drawStaticNode(negative); + } + if (splitter.num > 0) { + while (splitter != null) { + var next:Fragment = splitter.next; + var indices:Vector. = splitter.indices; + var num:int = splitter.num; + var a:int = indices[0]; + var b:int = indices[1]; + for (var i:int = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + var c:int = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + splitter.destroy(); + splitter = next; + } + } else { + splitter.destroy(); + } + if (positive != null) { + drawStaticNode(positive); + } + } else { + if (positive != null) { + drawStaticNode(positive); + } + splitter.destroy(); + if (negative != null) { + drawStaticNode(negative); + } + } + } + + private function drawDynamicNode(splitter:Fragment, source:Fragment, threshold:Number):void { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var splitterLast:Fragment = splitter; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + var i:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + // Перебор входной последовательности + while (source != null) { + next = source.next; + indices = source.indices; + num = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + a = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (i = 0; i < num; i++) { + // Вторая точка ребра + b = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[verticesLength] = ax + (bx - ax)*t; + uvts[verticesLength] = au + (bu - au)*t; verticesLength++; + vertices[verticesLength] = ay + (by - ay)*t; + uvts[verticesLength] = av + (bv - av)*t; verticesLength++; + vertices[verticesLength] = az + (bz - az)*t; + uvts[verticesLength] = 0; verticesLength++; + negativeIndices[negativeNum] = numVertices; + negativeNum++; + positiveIndices[positiveNum] = numVertices; + positiveNum++; + numVertices++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + negative.num = negativeNum; + positive.num = positiveNum; + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.destroy(); + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = splitter; + splitter = source; + } + source = next; + } + // Сбор задней части + if (negativeFirst != negativeLast) { + next = negativeFirst.next; + negativeFirst.next = null; + drawDynamicNode(negativeFirst, next, threshold); + } else if (negativeFirst != null) { + negativeFirst.next = splitter; + splitter = negativeFirst; + } + // Если в передней части только один фрагмент + if (positiveFirst != null && positiveFirst == positiveLast) { + splitterLast.next = positiveFirst; + positiveFirst = null; + } + // Отрисовка + while (splitter != null) { + next = splitter.next; + indices = splitter.indices; + num = splitter.num; + a = indices[0]; + b = indices[1]; + for (i = 2; i < num; i++) { + drawIndices[drawIndicesLength] = a; + drawIndicesLength++; + drawIndices[drawIndicesLength] = b; + drawIndicesLength++; + c = indices[i]; + drawIndices[drawIndicesLength] = c; + drawIndicesLength++; + b = c; + } + splitter.destroy(); + splitter = next; + } + // Сбор передней части + if (positiveFirst != null) { + next = positiveFirst.next; + positiveFirst.next = null; + drawDynamicNode(positiveFirst, next, threshold); + } + } + + public function debug(camera:Camera3D, container:Object3D, parentCanvas:Canvas, threshold:Number, bb:int, matrix:Matrix3D = null):void { + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + if (debugResult & Debug.EDGES) { + if (matrix != null) { + matrix = matrix.clone(); + } else { + matrix = new Matrix3D(); + } + var i:int; + var k:int; + var vi:int; + var num:int; + var x:Number; + var y:Number; + var current:Fragment; + // Перевод в камеру + vertices.length = verticesLength; + matrix.transformVectors(vertices, vertices); + if (viewAligned && (debugResult & Debug.BOUNDS) && bb == 2) { + canvas.gfx.lineStyle(0, 0xFF9900); + } else { + canvas.gfx.lineStyle(0, 0xFFFFFF); + } + // Сброс + drawIndicesLength = 0; + // Динамическое BSP + if (sorting == 3 && !viewAligned) { + // Клонирование списка + var first:Fragment; + var last:Fragment; + current = fragment; + while (current != null) { + if (first != null) { + last.next = last.create(); + last = last.next; + } else { + first = Fragment.create(); + last = first; + } + for (i = 0; i < current.num; i++) { + last.indices[i] = current.indices[i]; + } + last.num = current.num; + last.normalX = current.normalX; + last.normalY = current.normalY; + last.normalZ = current.normalZ; + last.offset = current.offset; + current = current.next; + } + var numVerts:int = numVertices; + // Сбор + current = first.next; + first.next = null; + debugDynamicNode(first, current, threshold, canvas); + // Проецирование + vertices.length = verticesLength; + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Отрисовка + for (i = 0, k = 0; i < drawIndicesLength; i++) { + if (i == k) { + num = drawIndices[i]; + i++; + k += num; + vi = drawIndices[k] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + k++; + } + vi = drawIndices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + numVertices = numVerts; + verticesLength = numVerts*3; + } else { + // Проецирование + Utils3D.projectVectors(camera.projectionMatrix, vertices, projectedVertices, uvts); + // Без сортировки + if (sorting == 0 || sorting == 1 || viewAligned) { + current = fragment; + while (current != null) { + k = current.num - 1; + vi = current.indices[k] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (i = 0; i < current.num; i++) { + vi = current.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + current = current.next; + } + // Статическое BSP + } else { + debugStaticNode(fragment, canvas); + } + } + matrix.invert(); + matrix.transformVectors(vertices, vertices); + } + if (debugResult & Debug.BOUNDS) { + if (bb > 0) { + var containerBoundBox:BoundBox = container._boundBox; + container._boundBox = new BoundBox(); + if (bb == 1) { + container._boundBox.minX = minX; + container._boundBox.minY = minY; + container._boundBox.minZ = minZ; + container._boundBox.maxX = maxX; + container._boundBox.maxY = maxY; + container._boundBox.maxZ = maxZ; + container.drawBoundBox(camera, canvas, 0x99FF00); + } else if (bb == 2 && !viewAligned) { + var containerCameraMatrix:Matrix3D = container.cameraMatrix; + container.cameraMatrix = cameraMatrix; + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(cameraMatrix); + inverseCameraMatrix.invert(); + inverseCameraMatrix.transformVectors(points, points); + container._boundBox.infinity(); + for (i = 0; i < 24;) { + var c:Number = points[i]; i++; + if (c < container._boundBox.minX) container._boundBox.minX = c; + if (c > container._boundBox.maxX) container._boundBox.maxX = c; + c = points[i]; i++; + if (c < container._boundBox.minY) container._boundBox.minY = c; + if (c > container._boundBox.maxY) container._boundBox.maxY = c; + c = points[i]; i++; + if (c < container._boundBox.minZ) container._boundBox.minZ = c; + if (c > container._boundBox.maxZ) container._boundBox.maxZ = c; + } + container.drawBoundBox(camera, canvas, 0xFF9900); + container.cameraMatrix = containerCameraMatrix; + } + container._boundBox = containerBoundBox; + } + } + } + + public function debugPart(camera:Camera3D, canvas:Canvas, fragment:Fragment):void { + if (debugResult & Debug.EDGES) { + while (fragment != null) { + var k:int = fragment.num - 1; + var vi:int = fragment.indices[k] << 1; + var x:Number = projectedVertices[vi]; vi++; + var y:Number = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (var i:int = 0; i < fragment.num; i++) { + vi = fragment.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + fragment = fragment.next; + } + } + } + + private function debugStaticNode(splitter:Fragment, canvas:Canvas):void { + if (splitter.negative != null) { + debugStaticNode(splitter.negative, canvas); + } + if (splitter.positive != null) { + debugStaticNode(splitter.positive, canvas); + } + if (splitter.num > 0) { + var current:Fragment = splitter; + while (current != null) { + var k:int = current.num - 1; + var vi:int = current.indices[k] << 1; + var x:Number = projectedVertices[vi]; vi++; + var y:Number = projectedVertices[vi]; + canvas.gfx.moveTo(x, y); + for (var i:int = 0; i < current.num; i++) { + vi = current.indices[i] << 1; + x = projectedVertices[vi]; vi++; + y = projectedVertices[vi]; + canvas.gfx.lineTo(x, y); + } + current = current.next; + } + } + } + + private function debugDynamicNode(splitter:Fragment, source:Fragment, threshold:Number, canvas:Canvas):void { + var negative:Fragment = negativeReserve; + var negativeIndices:Vector. = negative.indices; + var positive:Fragment = positiveReserve; + var positiveIndices:Vector. = positive.indices; + var next:Fragment; + var negativeFirst:Fragment; + var negativeLast:Fragment; + var splitterLast:Fragment = splitter; + var positiveFirst:Fragment; + var positiveLast:Fragment; + var normalX:Number = splitter.normalX; + var normalY:Number = splitter.normalY; + var normalZ:Number = splitter.normalZ; + var offset:Number = splitter.offset; + var i:int; + var a:int; + var b:int; + var c:int; + var indices:Vector.; + var num:int; + // Перебор входной последовательности + while (source != null) { + next = source.next; + indices = source.indices; + num = source.num; + var infront:Boolean = false; + var behind:Boolean = false; + var negativeNum:int = 0; + var positiveNum:int = 0; + // Первая точка ребра + var n:int = num - 1; + a = indices[n]; + var ai:int = a*3; + var ax:Number = vertices[ai]; n = ai + 1; + var ay:Number = vertices[n]; n++; + var az:Number = vertices[n]; + var ao:Number = ax*normalX + ay*normalY + az*normalZ - offset; + for (i = 0; i < num; i++) { + // Вторая точка ребра + b = indices[i]; + var bi:int = b*3; + var bx:Number = vertices[bi]; n = bi + 1; + var by:Number = vertices[n]; n++; + var bz:Number = vertices[n]; + var bo:Number = bx*normalX + by*normalY + bz*normalZ - offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) { + var t:Number = ao/(ao - bo); + var au:Number = uvts[ai]; ai++; + var av:Number = uvts[ai]; + var bu:Number = uvts[bi]; n = bi + 1; + var bv:Number = uvts[n]; + vertices[verticesLength] = ax + (bx - ax)*t; + uvts[verticesLength] = au + (bu - au)*t; verticesLength++; + vertices[verticesLength] = ay + (by - ay)*t; + uvts[verticesLength] = av + (bv - av)*t; verticesLength++; + vertices[verticesLength] = az + (bz - az)*t; + uvts[verticesLength] = 0; verticesLength++; + negativeIndices[negativeNum] = numVertices; + negativeNum++; + positiveIndices[positiveNum] = numVertices; + positiveNum++; + numVertices++; + } + // Добавление точки + if (bo < -threshold) { + negativeIndices[negativeNum] = b; + negativeNum++; + behind = true; + } else if (bo > threshold) { + positiveIndices[positiveNum] = b; + positiveNum++; + infront = true; + } else { + negativeIndices[negativeNum] = b; + negativeNum++; + positiveIndices[positiveNum] = b; + positiveNum++; + } + a = b; + ai = bi; + ax = bx; + ay = by; + az = bz; + ao = bo; + } + // Анализ разбиения + if (behind && infront) { + negative.num = negativeNum; + positive.num = positiveNum; + negative.normalX = source.normalX; + negative.normalY = source.normalY; + negative.normalZ = source.normalZ; + negative.offset = source.offset; + positive.normalX = source.normalX; + positive.normalY = source.normalY; + positive.normalZ = source.normalZ; + positive.offset = source.offset; + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + negativeReserve = source.create(); + positiveReserve = source.create(); + negative = negativeReserve; + negativeIndices = negative.indices; + positive = positiveReserve; + positiveIndices = positive.indices; + source.destroy(); + } else if (behind) { + source.next = null; + if (negativeFirst != null) { + negativeLast.next = source; + } else { + negativeFirst = source; + } + negativeLast = source; + } else if (infront) { + source.next = null; + if (positiveFirst != null) { + positiveLast.next = source; + } else { + positiveFirst = source; + } + positiveLast = source; + } else { + source.next = splitter; + splitter = source; + } + source = next; + } + // Сбор задней части + if (negativeFirst != negativeLast) { + next = negativeFirst.next; + negativeFirst.next = null; + debugDynamicNode(negativeFirst, next, threshold, canvas); + } else if (negativeFirst != null) { + negativeFirst.next = splitter; + splitter = negativeFirst; + } + // Если в передней части только один фрагмент + if (positiveFirst != null && positiveFirst == positiveLast) { + splitterLast.next = positiveFirst; + positiveFirst = null; + } + // Отрисовка + while (splitter != null) { + next = splitter.next; + indices = splitter.indices; + num = splitter.num; + + drawIndices[drawIndicesLength] = num; + drawIndicesLength++; + for (i = 0; i < num; i++) { + drawIndices[drawIndicesLength] = indices[i]; + drawIndicesLength++; + } + splitter.destroy(); + splitter = next; + } + // Сбор передней части + if (positiveFirst != null) { + next = positiveFirst.next; + positiveFirst.next = null; + debugDynamicNode(positiveFirst, next, threshold, canvas); + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/MipMap.as b/Alternativa3D7/7.2/alternativa/engine3d/core/MipMap.as new file mode 100644 index 0000000..9563f34 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/MipMap.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.display.BitmapData; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Объект, представляющий текстуру в виде последовательности её уменьшенных копий. + * Каждая следующая в два раза меньше предыдущей. Последняя имеет размер 1х1 пиксел. + * Чем дальше от камеры отрисовываемый объект, тем меньшая текстура выбирается. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class MipMap { + + /** + * Мип-текстуры + */ + public var textures:Vector. = new Vector.(); + /** + * Количество мип-текстур + */ + public var num:int; + /** + * Отношение размера пиксела текстуры к единице измерения трёхмерного пространства + */ + public var resolution:Number; + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + static private const point:Point = new Point(); + static private const matrix:Matrix = new Matrix(); + static private const rect:Rectangle = new Rectangle(); + public function MipMap(texture:BitmapData, resolution:Number = 1) { + var bmp:BitmapData = new BitmapData(texture.width, texture.height, texture.transparent); + var current:BitmapData = textures[num++] = texture; + filter.preserveAlpha = !texture.transparent; + var w:Number = rect.width = texture.width, h:Number = rect.height = texture.height; + while (w > 1 && h > 1 && rect.width > 1 && rect.height > 1) { + bmp.applyFilter(current, rect, point, filter); + rect.width = w >> 1; + rect.height = h >> 1; + matrix.a = rect.width/w; + matrix.d = rect.height/h; + w *= 0.5; + h *= 0.5; + current = new BitmapData(rect.width, rect.height, texture.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + textures[num++] = current; + } + bmp.dispose(); + this.resolution = resolution; + } + + /** + * Получение мип-уровня в зависимости от удалённости объекта от камеры + * @param distance Z-координата объекта в пространстве камеры + * @param camera Камера + * @return Индекс в списке мип-текстур textures + */ + public function getLevel(distance:Number, camera:Camera3D):int { + var level:int = Math.log(distance/(camera.focalLength*resolution))*1.442695040888963387; + if (level < 0) return 0; else if (level >= num) return num - 1; + return level; + } + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/MipMapping.as b/Alternativa3D7/7.2/alternativa/engine3d/core/MipMapping.as new file mode 100644 index 0000000..f322221 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/MipMapping.as @@ -0,0 +1,18 @@ +package alternativa.engine3d.core { + + public class MipMapping { + + /** + * Нет мипмаппинга. + */ + static public const NONE:int = 0; + /** + * Мипмаппинг по удалённости объекта от камеры. + */ + static public const PER_OBJECT:int = 1; + + //static public const PER_POLYGON:int = 2; + //static public const PER_PIXEL:int = 3; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Node.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Node.as new file mode 100644 index 0000000..9eb364a --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Node.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + public final class Node { + + public var positive:Node; + public var negative:Node; + + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + + public var offset:Number; + public var offsetMin:Number; + public var offsetMax:Number; + + public var boundBox:BoundBox; + + public var objects:Vector.; + public var bounds:Vector.; + + public var numObjects:int = 0; + public var numNonOccluders:int = 0; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Object3D.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..d988989 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,270 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Базовый трёхмерный объект + */ + public class Object3D { + + public var name:String; + /** + * Матрица трансформации. Управлять трансформацией объекта можно только через это свойство + * путём назначения новой матрицы или с помощью методов матрицы. + */ + public var matrix:Matrix3D = new Matrix3D(); + public var visible:Boolean = true; + + public var alpha:Number = 1; + public var blendMode:String = "normal"; + public var colorTransform:ColorTransform = null; + public var filters:Array = null; + + alternativa3d var _parent:Object3DContainer; + + alternativa3d var _boundBox:BoundBox; + + alternativa3d var culling:int = 0; + + alternativa3d var cameraMatrix:Matrix3D = new Matrix3D(); + + static private const boundBoxVertices:Vector. = new Vector.(24, true); + + alternativa3d function get canDraw():Boolean { + return true; + } + + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void {} + + alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return null; + } + + public function get boundBox():BoundBox { + return _boundBox; + } + + public function set boundBox(value:BoundBox):void { + _boundBox = value; + } + + /** + * Расчёт баунда + * @param matrix Трансформация пространства, в системе которого расчитывается баунд. + * Если этот параметр не указан, баунд расчитается в локальных координатах объекта. + * @param boundBox Баунд, в который записывается результат. + * Если этот параметр не указан, создаётся новый экземпляр. + * @return Расчитанный баунд. + */ + public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return null; + } + + public function get parent():Object3DContainer { + return _parent; + } + + /** + * @private + */ + alternativa3d function cullingInCamera(camera:Camera3D, parentCulling:int):int { + if (camera.occludedAll) return -1; + culling = parentCulling; + var i:int, infront:Boolean, behind:Boolean, boundBox:BoundBox = this.boundBox, numOccluders:int = camera.numOccluders, cull:Boolean = culling > 0 && boundBox != null, occlude:Boolean = numOccluders > 0 && boundBox != null; + // Расчёт точек баунда в координатах камеры + if (cull || occlude) boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX, boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY, boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ, boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX, boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY, boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ, cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + // Куллинг + if (cull) { + if (culling & 1) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] > camera.nearClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + // TODO: проверка не нужна + if (infront) culling &= 62; + } + } + if (culling & 2) { + for (i = 2, infront = false, behind = false; i <= 23; i += 3) { + if (boundBoxVertices[i] < camera.farClipping) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 61; + } + } + if (culling & 4) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 59; + } + } + if (culling & 8) { + for (i = 0, infront = false, behind = false; i <= 21; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 2)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 55; + } + } + if (culling & 16) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (-boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 47; + } + } + if (culling & 32) { + for (i = 1, infront = false, behind = false; i <= 22; i += 3) { + if (boundBoxVertices[i] < boundBoxVertices[int(i + 1)]) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + if (infront) culling &= 31; + } + } + } + // Окклюдинг + if (occlude) { + for (var o:int = 0; o < numOccluders; o++) { + var planeOccluder:Vector. = camera.occlusionPlanes[o], planeOccluderLength:int = planeOccluder.length; + for (var ni:int = 0; ni < planeOccluderLength; ni += 3) { + var nx:Number = planeOccluder[ni], ny:Number = planeOccluder[int(ni + 1)], nz:Number = planeOccluder[int(ni + 2)]; + for (i = 0; i < 24; i += 3) if (nx*boundBoxVertices[i] + ny*boundBoxVertices[int(i + 1)] + nz*boundBoxVertices[int(i + 2)] >= 0) break; + if (i < 24) break; + } + if (ni == planeOccluderLength) return -1; + } + } + return culling; + } + + alternativa3d function drawAxes(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawCenter(camera:Camera3D, canvas:Canvas):void { + + } + + alternativa3d function drawName(camera:Camera3D, canvas:Canvas):void { + + } + + static private const boundBoxProjectedVertices:Vector. = new Vector.(16, true); + static private const boundBoxUVTs:Vector. = new Vector.(24, true); + alternativa3d function drawBoundBox(camera:Camera3D, canvas:Canvas, color:int = -1):void { + + var boundBox:BoundBox = this.boundBox; + if (boundBox == null) return; + + boundBoxVertices[0] = boundBoxVertices[3] = boundBoxVertices[6] = boundBoxVertices[9] = boundBox.minX; + boundBoxVertices[1] = boundBoxVertices[4] = boundBoxVertices[13] = boundBoxVertices[16] = boundBox.minY; + boundBoxVertices[2] = boundBoxVertices[8] = boundBoxVertices[14] = boundBoxVertices[20] = boundBox.minZ; + + boundBoxVertices[12] = boundBoxVertices[15] = boundBoxVertices[18] = boundBoxVertices[21] = boundBox.maxX; + boundBoxVertices[7] = boundBoxVertices[10] = boundBoxVertices[19] = boundBoxVertices[22] = boundBox.maxY; + boundBoxVertices[5] = boundBoxVertices[11] = boundBoxVertices[17] = boundBoxVertices[23] = boundBox.maxZ; + + cameraMatrix.transformVectors(boundBoxVertices, boundBoxVertices); + for (var i:int = 0; i < 8; i++) { + if (boundBoxVertices[int(i*3 +2)] <= 0) return; + } + Utils3D.projectVectors(camera.projectionMatrix, boundBoxVertices, boundBoxProjectedVertices, boundBoxUVTs); + canvas.gfx.endFill(); + canvas.gfx.lineStyle(0, (color < 0) ? ((culling > 0) ? 0xFFFF00 : 0x00FF00) : color); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.moveTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[0], boundBoxProjectedVertices[1]); + canvas.gfx.lineTo(boundBoxProjectedVertices[8], boundBoxProjectedVertices[9]); + canvas.gfx.moveTo(boundBoxProjectedVertices[2], boundBoxProjectedVertices[3]); + canvas.gfx.lineTo(boundBoxProjectedVertices[10], boundBoxProjectedVertices[11]); + canvas.gfx.moveTo(boundBoxProjectedVertices[4], boundBoxProjectedVertices[5]); + canvas.gfx.lineTo(boundBoxProjectedVertices[12], boundBoxProjectedVertices[13]); + canvas.gfx.moveTo(boundBoxProjectedVertices[6], boundBoxProjectedVertices[7]); + canvas.gfx.lineTo(boundBoxProjectedVertices[14], boundBoxProjectedVertices[15]); + } + + public function toString():String { + var className:String = getQualifiedClassName(this); + return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]"; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Object3DContainer.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Object3DContainer.as new file mode 100644 index 0000000..40c96d9 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Object3DContainer.as @@ -0,0 +1,203 @@ +package alternativa.engine3d.core { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Базовый контейнер трёхмерных объектов. + * Логика контейнеров и child-parent-отношений идентична логике + * displayObject'ов во Flash. + */ + public class Object3DContainer extends Object3D { + + protected var children:Vector. = new Vector.(); + protected var _numChildren:int = 0; + + protected var visibleChildren:Vector. = new Vector.(); + protected var numVisibleChildren:int = 0; + + override alternativa3d function get canDraw():Boolean { + return _numChildren > 0; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Сбор видимых объектов + numVisibleChildren = 0; + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + if (child.cullingInCamera(camera, object.culling) >= 0) { + visibleChildren[numVisibleChildren] = child; + numVisibleChildren++; + } + } + } + // Если есть видимые объекты + if (numVisibleChildren > 0) { + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, canvas); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + for (var i:int = numVisibleChildren - 1; i >= 0; i--) { + var child:Object3D = visibleChildren[i]; + if (camera.debugMode) child.debug(camera, child, canvas); + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var i:int; + var first:Geometry; + var last:Geometry; + var geometry:Geometry; + for (i = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible && child.canDraw) { + child.cameraMatrix.identity(); + child.cameraMatrix.prepend(object.cameraMatrix); + child.cameraMatrix.prepend(child.matrix); + if (child.cullingInCamera(camera, object.culling) >= 0) { + geometry = child.getGeometry(camera, child); + if (geometry != null) { + if (first != null) { + last.next = geometry; + } else { + first = geometry; + last = geometry; + } + while (last.next != null) { + last = last.next; + } + } + } + } + } + if (object.alpha != 1) { + geometry = first; + while (geometry != null) { + geometry.alpha *= object.alpha; + geometry = geometry.next; + } + } + if (object.blendMode != "normal") { + geometry = first; + while (geometry != null) { + if (geometry.blendMode == "normal") { + geometry.blendMode = object.blendMode; + } + geometry = geometry.next; + } + } + if (object.colorTransform != null) { + geometry = first; + while (geometry != null) { + if (geometry.colorTransform != null) { + var ct:ColorTransform = new ColorTransform(object.colorTransform.redMultiplier, object.colorTransform.greenMultiplier, object.colorTransform.blueMultiplier, object.colorTransform.alphaMultiplier, object.colorTransform.redOffset, object.colorTransform.greenOffset, object.colorTransform.blueOffset, object.colorTransform.alphaOffset); + ct.concat(geometry.colorTransform); + geometry.colorTransform = ct; + } else { + geometry.colorTransform = object.colorTransform; + } + geometry = geometry.next; + } + } + if (object.filters != null) { + geometry = first; + while (geometry != null) { + if (geometry.filters != null) { + var fs:Array = new Array(); + var fsLength:int = 0; + var num:int = geometry.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = geometry.filters[i]; fsLength++; + } + num = object.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = object.filters[i]; fsLength++; + } + geometry.filters = fs; + } else { + geometry.filters = object.filters; + } + geometry = geometry.next; + } + } + return first; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var m:Matrix3D = matrix != null ? matrix.clone() : new Matrix3D(); + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчитываем баунды объектов + var childBoundBox:BoundBox = new BoundBox(); + var childMatrix:Matrix3D = new Matrix3D(); + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + childMatrix.identity(); + childMatrix.prepend(m); + childMatrix.prepend(child.matrix); + child.calculateBoundBox(childMatrix, childBoundBox); + boundBox.minX = (childBoundBox.minX < boundBox.minX) ? childBoundBox.minX : boundBox.minX; + boundBox.minY = (childBoundBox.minY < boundBox.minY) ? childBoundBox.minY : boundBox.minY; + boundBox.minZ = (childBoundBox.minZ < boundBox.minZ) ? childBoundBox.minZ : boundBox.minZ; + boundBox.maxX = (childBoundBox.maxX > boundBox.maxX) ? childBoundBox.maxX : boundBox.maxX; + boundBox.maxY = (childBoundBox.maxY > boundBox.maxY) ? childBoundBox.maxY : boundBox.maxY; + boundBox.maxZ = (childBoundBox.maxZ > boundBox.maxZ) ? childBoundBox.maxZ : boundBox.maxZ; + } + return boundBox; + } + + public function addChild(child:Object3D):void { + children[_numChildren++] = child; + child._parent = this; + } + + public function removeChild(child:Object3D):void { + var i:int = children.indexOf(child); + if (i < 0) throw new ArgumentError("Child not found"); + _numChildren--; + for (; i < _numChildren; i++) children[i] = children[int(i + 1)]; + children.length = _numChildren; + child._parent = null; + } + + public function hasChild(child:Object3D):Boolean { + return children.indexOf(child) > -1; + } + + public function getChildAt(index:uint):Object3D { + return children[index]; + } + + public function get numChildren():uint { + return _numChildren; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/core/Sorting.as b/Alternativa3D7/7.2/alternativa/engine3d/core/Sorting.as new file mode 100644 index 0000000..d53792d --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/core/Sorting.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + public class Sorting { + + /** + * Грани не сортируются. + */ + static public const NONE:int = 0; + /** + * Грани сортируются по средним Z. + */ + static public const AVERAGE_Z:int = 1; + /** + * Грани находятся в BSP-дереве. + */ + static public const STATIC_BSP:int = 2; + /** + * Грани при отрисовке образуют BSP-дерево. + */ + static public const DYNAMIC_BSP:int = 3; + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/BatchTextureLoader.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/BatchTextureLoader.as new file mode 100644 index 0000000..eb1a63c --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/BatchTextureLoader.as @@ -0,0 +1,275 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.BitmapData; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка пакета. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка пакета успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent.LOADER_ERROR + */ + [Event (name="loaderError", type="alternativa.engine3d.loaders.events.BatchTextureLoaderErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной текстуры. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной текстуры успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * @private + * Пакетный загрузчик текстур. + * + * При возникновении ошибки во время загрузки очередной текстуры, пакетный загрузчик заменяет соответствующую текстуру изображением-заглушкой и + * генерирует событие ошибки пакетного загрузчика. Пользователь пакетного загрузчика в обработчике ошибки может решить, прерывать ли процесс + * загрузки вызовом метода close() или нет. + */ + public class BatchTextureLoader extends EventDispatcher { + /** + * Текстура-заглушка для замены незагруженных текстур. + */ + private static var stubBitmapData:BitmapData; + + private static const IDLE:int = 0; + private static const LOADING:int = 1; + + // Состояние загрузчика + private var state:int = IDLE; + + // Загрузчик текстур + private var textureLoader:TextureLoader; + // Контекст безопасности загрузчика + private var loaderContext:LoaderContext; + // Базовый URL файлов текстур + private var baseURL:String; + // Пакет с описанием текстур материалов (textureName => TextureInfo) + private var batch:Object; + // Список имён текстур в пакете + private var textureNames:Vector.; + // Индекс текущего материала. + private var textureIndex:int; + // Общее количество загружаемых текстур + private var numTextures:int; + // Результирующая таблица (textureName => BitmapData) + private var _textures:Object; + + /** + * Создаёт новый экземпляр загрузчика. + */ + public function BatchTextureLoader() { + } + + /** + * Результирующая таблица битмапов. Ключами являются имена текстур, значениями -- объекты класса BitmapData. + */ + public function get textures():Object { + return _textures; + } + + /** + * Прекращает текущую загрузку. + */ + public function close():void { + if (state == LOADING) { + textureLoader.close(); + cleanup(); + _textures = null; + state = IDLE; + } + } + + /** + * Очищает ссылку на загруженный список текстур материалов. + */ + public function unload():void { + _textures = null; + } + + /** + * Запускает загрузку. + * + * @param baseURL базовый URL файлов текстур + * @param batch описание пакета текстур -- таблица textureName => TextureInfo + * @param loaderContext LoaderContext для загрузки + */ + public function load(baseURL:String, batch:Object, loaderContext:LoaderContext = null):void { + if (baseURL == null) { + throw ArgumentError("Parameter baseURL cannot be null"); + } + if (batch == null) { + throw ArgumentError("Parameter batch cannot be null"); + } + + this.baseURL = baseURL; + this.batch = batch; + this.loaderContext = loaderContext; + + if (textureLoader == null) { + textureLoader = new TextureLoader(); + } else { + close(); + } + textureLoader.addEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.addEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.addEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + + // Получение массива имён текстур + textureNames = new Vector.(); + for (var textureName:String in batch) { + textureNames.push(textureName); + } + numTextures = textureNames.length; + // Старт загрузки + textureIndex = 0; + _textures = {}; + + if (hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + + state = LOADING; + loadNextTexture(); + } + + /** + * Запускает загрузку очередной текстуры. + */ + private function loadNextTexture():void { + var info:TextureInfo = batch[textureNames[textureIndex]]; + var opacityMapFileUrl:String = info.opacityMapFileName == null || info.opacityMapFileName == "" ? null : baseURL + info.opacityMapFileName; + textureLoader.load(baseURL + info.diffuseMapFileName, opacityMapFileUrl, loaderContext); + } + + /** + * + */ + private function onTextureLoadingStart(e:Event):void { + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, numTextures, textureIndex)); + } + } + + /** + * + */ + private function onProgress(e:LoaderProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var totalProgress:Number = (textureIndex + e.totalProgress)/numTextures; + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, numTextures, textureIndex, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * Обрабатывает завершение загрузки текстуры. + */ + private function onTextureLoadingComplete(e:Event):void { + _textures[textureNames[textureIndex]] = textureLoader.bitmapData; + tryNextTexure(); + } + + /** + * Обрабатывает ошибку при загрузке текстуры. Незагруженная текстура заменяется изображением-заглушкой и + * генерируется событие ошибки пакетного загрузчика. + */ + private function onLoadingError(e:ErrorEvent):void { + var textureName:String = textureNames[textureIndex]; + _textures[textureName] = getStubBitmapData(); + dispatchEvent(new BatchTextureLoaderErrorEvent(BatchTextureLoaderErrorEvent.LOADER_ERROR, textureName, e.text)); + tryNextTexure(); + } + + /** + * + */ + private function tryNextTexure():void { + // Проверка состояния необходима, т.к. оно могло измениться в результате вызова метода close() в обработчике события ошибки загрузки + if (state == IDLE) return; + + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, numTextures, textureIndex)); + } + if (++textureIndex == numTextures) { + // Загружены все текстуры, отправляется сообщение о завершении + cleanup(); + removeEventListeners(); + state = IDLE; + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } else { + loadNextTexture(); + } + } + + /** + * + */ + private function removeEventListeners():void { + textureLoader.removeEventListener(Event.OPEN, onTextureLoadingStart); + textureLoader.removeEventListener(LoaderProgressEvent.LOADER_PROGRESS, onProgress); + textureLoader.removeEventListener(Event.COMPLETE, onTextureLoadingComplete); + textureLoader.removeEventListener(IOErrorEvent.IO_ERROR, onLoadingError); + } + + /** + * Очищает внутренние ссылки на объекты. + */ + private function cleanup():void { + loaderContext = null; + textureNames = null; + } + + /** + * Метод для получения текстуры-заглушки. + * + * @return текстура-заглушка для замещения незагруженных текстур + */ + private function getStubBitmapData():BitmapData { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stubBitmapData.setPixel((i%2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stubBitmapData; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DS.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DS.as new file mode 100644 index 0000000..fc1ca58 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DS.as @@ -0,0 +1,927 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.objects.Mesh; + + import flash.display.BitmapData; + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + [Event (name="complete", type="flash.events.Event")] + /** + * + */ + public class Loader3DS extends EventDispatcher { + + private static const STATE_IDLE:int = -1; + private static const STATE_LOADING_MODEL:int = 0; + private static const STATE_LOADING_TEXTURES:int = 1; + + private static var stubBitmapData:BitmapData; + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + private var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + private var loaderState:int = STATE_IDLE; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + /** + * Прекращение текущей загрузки. + */ + public function close():void { + if (loaderState == STATE_LOADING_MODEL) { + modelLoader.close(); + } + if (loaderState == STATE_LOADING_TEXTURES) { + textureLoader.close(); + } + loaderState = STATE_IDLE; + } + + /** + * Метод очищает внутренние ссылки на загруженные данные чтобы сборщик мусора мог освободить занимаемую ими память. Метод не работает + * во время загрузки. + */ + public function unload():void { + if (loaderState == STATE_IDLE) { + clean(); + } + } + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function load(url:String, context:LoaderContext = null):void { + path = url.substring(0, url.lastIndexOf("/") + 1); + this.context = context; + + // Очистка + version = 0; + clean(); + + if (modelLoader == null) { + modelLoader = new URLLoader(); + modelLoader.dataFormat = URLLoaderDataFormat.BINARY; + modelLoader.addEventListener(Event.COMPLETE, on3DSLoad); + modelLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); + modelLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, on3DSError); + } else { + close(); + } + + loaderState = STATE_LOADING_MODEL; + modelLoader.load(new URLRequest(url)); + } + + private function on3DSLoad(e:Event):void { + loaderState = STATE_IDLE; + data = modelLoader.data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + } + + private function on3DSError(e:Event):void { + loaderState = STATE_IDLE; + dispatchEvent(e); + } + + private function loadBitmaps():void { + if (textureLoader == null) { + textureLoader = new TextureLoader(); + textureLoader.addEventListener(Event.COMPLETE, loadNextBitmap); + textureLoader.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); + } + + // Имена материалов с диффузными текстурами собираются в массив textureMaterialNames + bitmaps = new Array(); + textureMaterialNames = new Array(); + for each (var materialData:MaterialData in materialDatas) { + if (materialData.diffuseMap != null) { + textureMaterialNames.push(materialData.name); + } + } + + loaderState = STATE_LOADING_TEXTURES; + loadNextBitmap(); + } + + private function loadNextBitmap(e:Event = null):void { + if (e != null) { + if (!(e is IOErrorEvent)) { + bitmaps[textureMaterialNames[counter]] = textureLoader.bitmapData; + } else { + if (stubBitmapData == null) { + var size:uint = 20; + stubBitmapData = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j+=2) { + stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); + } + } + } + bitmaps[textureMaterialNames[counter]] = stubBitmapData; + } + } else { + counter = -1; + } + counter++; + if (counter < textureMaterialNames.length) { + var materialData:MaterialData = materialDatas[textureMaterialNames[counter]]; + textureLoader.load(path + materialData.diffuseMap.filename, materialData.opacityMap == null ? null : path + materialData.opacityMap.filename, context); + } else { + loaderState = STATE_IDLE; + buildContent(); + } + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + object.name = animationData.objectName; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + } + } + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + loadBitmaps(); + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DSByteArray.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DSByteArray.as new file mode 100644 index 0000000..ce03a99 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Loader3DSByteArray.as @@ -0,0 +1,815 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + public class Loader3DSByteArray extends EventDispatcher { + + private var _content:Vector.; + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Array; + private var bitmaps:Array; + + private var modelLoader:URLLoader; + private var textureLoader:TextureLoader; + alternativa3d var data:ByteArray; + + private var counter:int; + private var textureMaterialNames:Array; + private var context:LoaderContext; + private var path:String; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + /** + * Устанавливаемый уровень мобильности загруженных объектов. + */ + public var mobility:int = 0; + + private function clean():void { + _content = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + bitmaps = null; + textureMaterialNames = null; + } + + public function parseByteArray(data:ByteArray):void { + this.data = data; + this.data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, this.data.bytesAvailable); + } + + private function buildContent():void { + var i:uint; + var length:uint; + + // Формируем связи объектов + _content = new Vector.(); + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData); + _content.push(mesh); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null); + _content.push(mesh); + } + } + } + + // Рассылаем событие о завершении + dispatchEvent(new Event(Event.COMPLETE)); + } + + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x * units, animationData.position.y * units, animationData.position.z * units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { + + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + // Добавляем поверхности + /*if (objectData.surfaces != null) { + for (var surfaceId:String in objectData.surfaces) { + var materialData:MaterialData = materialDatas[surfaceId]; + if (materialData.diffuseMap != null) { + mesh.texture = bitmaps[materialData.name]; + } + } + }*/ + } + + alternativa3d function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } else { + // Загрузка битмап + //loadBitmaps(); + buildContent(); // Без подгрузки битмап + } + } + + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Array(); + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Array(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(data.readFloat(), -data.readFloat(), data.readFloat(), angle); + } + + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Объект-контейнер, содержащий все загруженные объекты. + */ + public function get content():Vector. { + return _content; + } + + // Считываем строку заканчивающуюся на нулевой байт + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k > 0.998) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k < -0.998) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/MaterialParams.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/MaterialParams.as new file mode 100644 index 0000000..df75e1a --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/MaterialParams.as @@ -0,0 +1,15 @@ +package alternativa.engine3d.loaders { + + public class MaterialParams { + + public var color:uint; + public var opacity:Number; + public var diffuseMap:String; + public var opacityMap:String; + + public function toString():String { + return "[MaterialParams color=" + color + ", opacity=" + opacity + ", diffuseMap=" + diffuseMap + ", opacityMap=" + opacityMap + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parsed3DSData.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parsed3DSData.as new file mode 100644 index 0000000..a5dc69d --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parsed3DSData.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3D; + + public class Parsed3DSData { + + /** + * Список объектов в порядке их появления в 3DS-данных. + **/ + public var objects:Vector.; + /** + * Список материалов каждого объекта. Если для объекта нет назначенных материалов, соответствующий элемент списка равен null. + **/ + public var objectMaterials:Vector.>; + /** + * Список материалов 3DS-файла (materialName => MaterialParams). + */ + public var materials:Object; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..6623207 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,973 @@ +package alternativa.engine3d.loaders { + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Mesh; + + import flash.display.BlendMode; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + import flash.geom.Orientation3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.net.URLLoader; + import flash.utils.ByteArray; + import flash.utils.Endian; + + /** + * + */ + public class Parser3DS extends EventDispatcher { + + private var version:uint; + private var objectDatas:Object; + private var animationDatas:Vector.; + private var materialDatas:Object; + + private var modelLoader:URLLoader; + private var data:ByteArray; + + /** + * Повтор текстуры при заливке для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var repeat:Boolean = true; + /** + * Сглаживание текстур при увеличении масштаба. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var smooth:Boolean = false; + /** + * Режим наложения цвета для создаваемых текстурных материалов. + * + * @see alternativa.engine3d.materials.TextureMaterial + */ + public var blendMode:String = BlendMode.NORMAL; + + /** + * Коэффициент пересчёта единиц измерения модели. + */ + public var units:Number = 1; + + /** + * + * @param data + */ + public function Parser3DS() { + } + + /** + * + * @param data + */ + public function parse(data:ByteArray):Parsed3DSData { + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(0, data.bytesAvailable); + return buildContent(); + } + + /** + * + */ + private function clean():void { + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + /** + * + */ + private function buildContent():Parsed3DSData { + var result:Parsed3DSData = new Parsed3DSData(); + var i:uint; + var length:uint; + + // Формируем связи объектов + result.objects = new Vector.(); + result.objectMaterials = new Vector.>(); + result.materials = {}; + + // Создаём материалы + var materialData:MaterialData; + for (var materialName:String in materialDatas) { + materialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + var materialMatrix:Matrix = new Matrix(); + if (mapData != null) { + var rot:Number = mapData.rotation*Math.PI/180; + var rotSin:Number = Math.sin(rot); + var rotCos:Number = Math.cos(rot); + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + } + materialData.matrix = materialMatrix; + + var mat:MaterialParams = new MaterialParams(); + mat.color = materialData.color; + mat.opacity = 1 - 0.01*materialData.transparency; + if (materialData.diffuseMap != null) { + mat.diffuseMap = materialData.diffuseMap.filename; + } + if (materialData.opacityMap != null) { + mat.opacityMap = materialData.opacityMap.filename; + } + result.materials[materialName] = mat; + } + + // Если есть данные об анимации и иерархии объектов + var objectName:String; + var objectData:ObjectData; + var mesh:Mesh; + if (animationDatas != null) { + if (objectDatas != null) { + + length = animationDatas.length; + for (i = 0; i < length; i++) { + var animationData:AnimationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + + // Если на один объект приходится несколько данных об анимации + if (objectData != null) { + var nameCounter:uint = 2; + for (var j:uint = i + 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (objectName == animationData2.objectName) { + var newName:String = objectName + nameCounter; + var newObjectData:ObjectData = new ObjectData(); + animationData2.objectName = newName; + newObjectData.name = newName; + if (objectData.vertices != null) { + newObjectData.vertices = new Vector.().concat(objectData.vertices); + } + if (objectData.uvs != null) { + newObjectData.uvs = new Vector.().concat(objectData.uvs); + } + if (objectData.matrix != null) { + newObjectData.matrix = objectData.matrix.clone(); + } + if (objectData.faces != null) { + newObjectData.faces = new Array().concat(objectData.faces); + } + if (objectData.surfaces != null) { + newObjectData.surfaces = objectData.surfaces.clone(); + } + objectDatas[newName] = newObjectData; + nameCounter++; + } + } + } + + if (objectData != null && objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + animationData.object = mesh; + buildObject(animationData); + buildMesh(mesh, objectData, animationData, result); + } + } + } + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + // Меш + mesh = new Mesh(); + mesh.createEmptyGeometry(objectData.vertices.length, objectData.faces.length); + buildMesh(mesh, objectData, null, result); + } + } + } + + clean(); + return result; + } + + /** + * + * @param animationData + */ + private function buildObject(animationData:AnimationData):void { + var object:Mesh = animationData.object; + var transform:Vector. = new Vector.(3, true); + transform[0] = (animationData.position == null) ? new Vector3D() : new Vector3D(animationData.position.x*units, animationData.position.y*units, animationData.position.z*units); + transform[1] = (animationData.rotation == null) ? new Vector3D() : animationData.rotation.clone(); + transform[2] = (animationData.scale == null) ? new Vector3D(1, 1, 1) : animationData.scale.clone(); +// trace("transform[2]", transform[2]); + object.matrix.recompose(transform, Orientation3D.AXIS_ANGLE); + } + + /** + * + * @param mesh + * @param objectData + * @param animationData + */ + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, parsedData:Parsed3DSData):void { + mesh.name = objectData.name; + // Добавляем вершины + var i:uint; + var j:uint; + var key:*; + var length:uint = objectData.vertices.length; + for (i = 0; i < length; i++) { + var vertexData:Vector3D = objectData.vertices[i]; + var uv:Point = objectData.uvs[i]; + j = i*3; + mesh.vertices[j] = vertexData.x; + mesh.vertices[j + 1] = vertexData.y; + mesh.vertices[j + 2] = vertexData.z; + mesh.uvts[j] = uv.x; + mesh.uvts[j + 1] = 1 - uv.y; + } + + // Коррекция вершин + if (animationData != null) { + // Инвертируем матрицу + objectData.matrix.invert(); + + // Вычитаем точку привязки из смещения матрицы + if (animationData.pivot != null) { + objectData.matrix.appendTranslation(-animationData.pivot.x, -animationData.pivot.y, -animationData.pivot.z); + } + + // Трансформируем вершины + objectData.matrix.transformVectors(mesh.vertices, mesh.vertices); + } + for (i = 0; i < mesh.numVertices*3; i++) { + mesh.vertices[i] *= units; + } + + // Добавляем грани + length = objectData.faces.length; + for (i = 0; i < length; i++) { + var faceData:FaceData = objectData.faces[i]; + j = i*3; + mesh.indices[j] = faceData.a; + mesh.indices[j + 1] = faceData.b; + mesh.indices[j + 2] = faceData.c; + } + + parsedData.objects.push(mesh); + + // Добавляем поверхности + if (objectData.surfaces != null) { + var meshMaterials:Vector. = new Vector.(); + for (var surfaceId:String in objectData.surfaces) { + meshMaterials.push(surfaceId); +// var materialData:MaterialData = materialDatas[surfaceId]; +// if (materialData.diffuseMap != null) { +// mesh.texture = bitmaps[materialData.name]; +// } + } + parsedData.objectMaterials.push(meshMaterials); + } else { + parsedData.objectMaterials.push(null); + } +// trace("mesh.matrix.decompose()", mesh.matrix.decompose()); + } + + /** + * + * @param index + * @param length + */ + private function parse3DSChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Главный + case 0x4D4D: + parseMainChunk(dataIndex, dataLength); + break; + } + + parse3DSChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + * @param length + */ + private function parseMainChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Версия + case 0x0002: + parseVersion(dataIndex); + break; + // 3D-сцена + case 0x3D3D: + parse3DChunk(dataIndex, dataLength); + break; + // Анимация + case 0xB000: + parseAnimationChunk(dataIndex, dataLength); + break; + } + + parseMainChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param index + */ + private function parseVersion(index:uint):void { + data.position = index; + version = data.readUnsignedInt(); + } + + /** + * + * @param index + * @param length + */ + private function parse3DChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Материал + case 0xAFFF: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, dataIndex, dataLength); + break; + // Объект + case 0x4000: + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + var objectLength:uint = parseObject(object, dataIndex); + // Парсим объект + parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); + break; + } + + parse3DChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + * @param length + */ + private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Имя материала + case 0xA000: + parseMaterialName(material, dataIndex); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = dataIndex + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = dataIndex + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = dataIndex + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = dataIndex + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, dataIndex, dataLength); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, dataIndex, dataLength); + break; + // Bump map + case 0xA230: + //material.normalMap = new MapData(); + //parseMapChunk(material.normalMap, dataIndex, dataLength); + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + + parseMaterialChunk(material, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param material + * @param index + */ + private function parseMaterialName(material:MaterialData, index:uint):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = {}; + } + // Получаем название материала + material.name = getString(index); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + /** + * + * @param materialName + * @param map + * @param index + * @param length + */ + private function parseMapChunk(materialName:String, map:MapData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Имя файла + case 0xA300: + map.filename = getString(dataIndex).toLowerCase(); + break; + // Масштаб по U + case 0xA354: + data.position = dataIndex; + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + data.position = dataIndex; + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + data.position = dataIndex; + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + data.position = dataIndex; + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + data.position = dataIndex; + map.rotation = data.readFloat(); + break; + } + + parseMapChunk(materialName, map, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseObject(object:ObjectData, index:uint):uint { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Получаем название объекта + object.name = getString(index); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + return object.name.length + 1; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Меш + case 0x4100: + parseMeshChunk(object, dataIndex, dataLength); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + + parseObjectChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Вершины + case 0x4110: + parseVertices(object, dataIndex); + break; + // UV + case 0x4140: + parseUVs(object, dataIndex); + break; + // Трансформация + case 0x4160: + parseMatrix(object, dataIndex); + break; + // Грани + case 0x4120: + var facesLength:uint = parseFaces(object, dataIndex); + parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); + break; + } + + parseMeshChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseVertices(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.vertices.push(new Vector3D(data.readFloat(), data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseUVs(object:ObjectData, index:uint):void { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:uint = 0; i < num; i++) { + object.uvs.push(new Point(data.readFloat(), data.readFloat())); + } + } + + /** + * + * @param object + * @param index + */ + private function parseMatrix(object:ObjectData, index:uint):void { + data.position = index; + object.matrix = new Matrix3D(Vector.([ + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 0, + data.readFloat(), data.readFloat(), data.readFloat(), 1 + ])); + } + + /** + * + * @param object + * @param index + * @return + */ + private function parseFaces(object:ObjectData, index:uint):uint { + data.position = index; + var num:uint = data.readUnsignedShort(); + object.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + var face:FaceData = new FaceData(); + face.a = data.readUnsignedShort(); + face.b = data.readUnsignedShort(); + face.c = data.readUnsignedShort(); + object.faces.push(face); + data.position += 2; // Пропускаем флаг + } + return 2 + num*8; + } + + /** + * + * @param object + * @param index + * @param length + */ + private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + + switch (chunkId) { + // Поверхности + case 0x4130: + parseSurface(object, dataIndex); + break; + // Группы сглаживания + case 0x4150: + break; + } + + parseFacesChunk(object, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param object + * @param index + */ + private function parseSurface(object:ObjectData, index:uint):void { + // Создаём данные поверхности + var surface:SurfaceData = new SurfaceData(); + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Получаем название материала + surface.materialName = getString(index); + // Помещаем данные поверхности в список + object.surfaces[surface.materialName] = surface; + + // Получаем грани поверхности + data.position = index + surface.materialName.length + 1; + var num:uint = data.readUnsignedShort(); + surface.faces = new Array(); + for (var i:uint = 0; i < num; i++) { + surface.faces.push(data.readUnsignedShort()); + } + } + + /** + * + * @param index + * @param length + */ + private function parseAnimationChunk(index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + var animation:AnimationData = new AnimationData(); + if (animationDatas == null) { + animationDatas = new Vector.(); + } + animationDatas.push(animation); + parseObjectAnimationChunk(animation, dataIndex, dataLength); + break; + + // Таймлайн + case 0xB008: + break; + } + + parseAnimationChunk(index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + * @param length + */ + private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { + if (length > 6) { + data.position = index; + var chunkId:uint = data.readUnsignedShort(); + var chunkLength:uint = data.readUnsignedInt(); + var dataIndex:uint = index + 6; + var dataLength:uint = chunkLength - 6; + switch (chunkId) { + // Идентификация объекта и его связь + case 0xB010: + parseObjectAnimationInfo(animation, dataIndex); + break; + // Точка привязки объекта (pivot) + case 0xB013: + parseObjectAnimationPivot(animation, dataIndex); + break; + // Смещение объекта относительно родителя + case 0xB020: + parseObjectAnimationPosition(animation, dataIndex); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + parseObjectAnimationRotation(animation, dataIndex); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + parseObjectAnimationScale(animation, dataIndex); + break; + } + + parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); + } + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { + var name:String = getString(index); + data.position = index + name.length + 1 + 4; + animation.objectName = name; + animation.parentIndex = data.readUnsignedShort(); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { + data.position = index; + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { + data.position = index + 20; + var angle:Number = data.readFloat(); + animation.rotation = new Vector3D(-data.readFloat(), -data.readFloat(), -data.readFloat(), angle); + } + + /** + * + * @param animation + * @param index + */ + private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { + data.position = index + 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + } + + /** + * Считывает строку, заканчивающуюся на нулевой байт. + * + * @param index + * @return + */ + private function getString(index:uint):String { + data.position = index; + var charCode:uint = data.readByte(); + var res:String = ""; + while (charCode != 0) { + res += String.fromCharCode(charCode); + charCode = data.readByte(); + } + return res; + } + + /** + * + * @param angle + * @param x + * @param z + * @param y + * @return + */ + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = 0.5*angle; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -0.5*Math.PI; + res.x = 0; + return res; + } + if (k <= -1) { + half = 0.5*angle; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = 0.5*Math.PI; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(k); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + + +import flash.geom.Matrix; +import flash.geom.Matrix3D; +import flash.geom.Vector3D; +import __AS3__.vec.Vector; +import flash.geom.Point; +import alternativa.engine3d.objects.Mesh; + +class MaterialData { + public var name:String; + public var color:uint; + public var specular:uint; + public var glossiness:uint; + public var transparency:uint; + public var diffuseMap:MapData; + public var opacityMap:MapData; + //public var normalMap:MapData; + public var matrix:Matrix; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var matrix:Matrix3D; + public var faces:Array; + public var surfaces:Object; +} + +class FaceData { + public var a:uint; + public var b:uint; + public var c:uint; +} + +class SurfaceData { + public var materialName:String; + public var faces:Array; +} + +class AnimationData { + public var objectName:String; + public var object:Mesh; + public var parentIndex:uint; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureFilesData.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureFilesData.as new file mode 100644 index 0000000..265859b --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureFilesData.as @@ -0,0 +1,13 @@ +package alternativa.engine3d.loaders { + public class TextureFilesData { + + public var diffuseMap:String; + public var opacityMap:String; + + public function TextureFilesData(diffuseMap:String, opacityMap:String) { + this.diffuseMap = diffuseMap; + this.opacityMap = opacityMap; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureInfo.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureInfo.as new file mode 100644 index 0000000..babb394 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureInfo.as @@ -0,0 +1,36 @@ +package alternativa.engine3d.loaders { + + /** + * Структура для хранения имён файла диффузной текстуры и файла карты прозрачности. + */ + public class TextureInfo { + /** + * Имя файла диффузной текстуры. + */ + public var diffuseMapFileName:String; + /** + * Имя файла карты прозрачности. + */ + public var opacityMapFileName:String; + + /** + * Создаёт новый экземпляр. + * + * @param diffuseMapFileName имя файла диффузной текстуры + * @param opacityMapFileName имя файла карты прозрачности + */ + public function TextureInfo(diffuseMapFileName:String = null, opacityMapFileName:String = null) { + this.diffuseMapFileName = diffuseMapFileName; + this.opacityMapFileName = opacityMapFileName; + } + + /** + * Создаёт строковое представление объекта. + * + * @return строковое представление объекта + */ + public function toString():String { + return "[TextureInfo diffuseMapFileName=" + diffuseMapFileName + ", opacityMapFileName=" + opacityMapFileName + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureLoader.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureLoader.as new file mode 100644 index 0000000..3cc7171 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/TextureLoader.as @@ -0,0 +1,268 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.display.LoaderInfo; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Событие посылается, когда начинается загрузка ресурса. + * + * @eventType flash.events.Event.OPEN + */ + [Event (name="open", type="flash.events.Event")] + /** + * Событие посылается, когда загрузка ресурса успешно завершена. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Событие посылается при возникновении ошибки загрузки. + * + * @eventType flash.events.IOErrorEvent.IO_ERROR + */ + [Event (name="ioError", type="flash.events.IOErrorEvent")] + /** + * Событие посылается, когда начинается загрузка очередной части ресурса. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается, когда загрузка очередной части ресурса успешно завершена. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Событие посылается для отображения прогресса загрузки. + * + * @eventType alternativa.engine3d.loaders.events.LoaderProgressEvent.LOADER_PROGRESS + */ + [Event (name="loaderProgress", type="alternativa.engine3d.loaders.events.LoaderProgressEvent")] + + /** + * Загрузчик текстуры, состоящей из одного или двух файлов. В случае, если указан второй файл, он используется для заполнения альфа-канала + * получаемой текстуры. + */ + public class TextureLoader extends EventDispatcher { + + private static const IDLE:int = -1; + private static const LOADING_DIFFUSE_MAP:int = 0; + private static const LOADING_ALPHA_MAP:int = 1; + + private var state:int = IDLE; + private var bitmapLoader:Loader; + private var loaderContext:LoaderContext; + private var alphaTextureUrl:String; + private var _bitmapData:BitmapData; + + /** + * Создаёт новый экземпляр. Если указан URL диффузной части текстуры, то сразу начинается загрузка. + * + * @param diffuseTextureUrl URL диффузной части текстуры + * @param alphaTextureUrl URL карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function TextureLoader() { + } + + /** + * Загруженная текстура. + */ + public function get bitmapData():BitmapData { + return _bitmapData; + } + + /** + * Загрузка текстурных карт. При успешной загрузке посылается сообщение Event.COMPLETE. + * + * @param diffuseTextureUrl URL файла диффузной карты + * @param alphaTextureUrl URL файла карты прозрачности + * @param loaderContext LoaderContext, используемый при загрузке + */ + public function load(diffuseTextureUrl:String, alphaTextureUrl:String = null, loaderContext:LoaderContext = null):void { + unload(); + this.alphaTextureUrl = alphaTextureUrl == "" ? null : alphaTextureUrl; + this.loaderContext = loaderContext; + + loadPart(LOADING_DIFFUSE_MAP, diffuseTextureUrl); + } + + /** + * Прекращвет текущую загрузку. Если нет активных загрузок, не происходит ничего. + */ + public function close():void { + if (state == IDLE) return; + state = IDLE; + bitmapLoader.unload(); + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Очищает внутренние ссылки на загруженные объекты, чтобы сборщик мусора смог их удалить. + */ + public function unload():void { + close(); + _bitmapData = null; + } + + /** + * Очищает временные внутренние ссылки. + */ + private function cleanup():void { + destroyLoader(); + alphaTextureUrl = null; + loaderContext = null; + } + + /** + * Запускает загрузку части текстуры. + * + * @param state фаза загрузки + * @param url URL загружаемого файла + */ + private function loadPart(state:int, url:String):void { + this.state = state; + createLoader(); + bitmapLoader.load(new URLRequest(url), loaderContext); + } + + /** + * Обрабатывает начало загрузки очередной части текстуры. + */ + private function onPartLoadingOpen(e:Event):void { + if (_bitmapData == null && hasEventListener(Event.OPEN)) { + dispatchEvent(new Event(Event.OPEN)); + } + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, 2, state == LOADING_DIFFUSE_MAP ? 0 : 1)); + } + } + + /** + * + */ + private function onPartLoadingProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + var partNumber:int = state == LOADING_DIFFUSE_MAP ? 0 : 1; + var totalProgress:Number = 0.5*(partNumber + e.bytesLoaded/e.bytesTotal); + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, 2, partNumber, totalProgress, e.bytesLoaded, e.bytesTotal)); + } + } + + /** + * + */ + private function onPartLoadingComplete(e:Event):void { + switch (state) { + case LOADING_DIFFUSE_MAP: { + // Загрузилась диффузная текстура. При необходимости загружается карта прозрачности. + _bitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + dispatchPartComplete(0); + if (alphaTextureUrl != null) { + loadPart(LOADING_ALPHA_MAP, alphaTextureUrl); + } else { + complete(); + } + break; + } + case LOADING_ALPHA_MAP: { + // Загрузилась карта прозрачности. Выполняется копирование прозрачности в альфа-канал диффузной текстуры. + var pt:Point = new Point(); + var tmpBmd:BitmapData = _bitmapData; + _bitmapData = new BitmapData(_bitmapData.width, _bitmapData.height); + _bitmapData.copyPixels(tmpBmd, tmpBmd.rect, pt); + + var alpha:BitmapData = Bitmap(bitmapLoader.content).bitmapData; + destroyLoader(); + if (_bitmapData.width != alpha.width || _bitmapData.height != alpha.height) { + tmpBmd.draw(alpha, new Matrix(_bitmapData.width/alpha.width, 0, 0, _bitmapData.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = tmpBmd; + } else { + tmpBmd.dispose(); + } + _bitmapData.copyChannel(alpha, alpha.rect, pt, BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + dispatchPartComplete(1); + complete(); + break; + } + } + } + + /** + * Создаёт событие завершения загрузки части текстуры. + * + * @param partnNumber номер загруженной части текстуры + */ + private function dispatchPartComplete(partNumber:int):void { + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, 2, partNumber)); + } + } + + /** + * + */ + private function onLoadError(e:Event):void { + state = IDLE; + cleanup(); + dispatchEvent(e); + } + + /** + * + */ + private function complete():void { + state = IDLE; + cleanup(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } + + /** + * + */ + private function createLoader():void { + bitmapLoader = new Loader(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.addEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.addEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.addEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + } + + /** + * + */ + private function destroyLoader():void { + if (bitmapLoader == null) return; + bitmapLoader.unload(); + var loaderInfo:LoaderInfo = bitmapLoader.contentLoaderInfo; + loaderInfo.removeEventListener(Event.OPEN, onPartLoadingOpen); + loaderInfo.removeEventListener(ProgressEvent.PROGRESS, onPartLoadingProgress); + loaderInfo.removeEventListener(Event.COMPLETE, onPartLoadingComplete); + loaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, onLoadError); + bitmapLoader = null; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as new file mode 100644 index 0000000..0d8da26 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/BatchTextureLoaderErrorEvent.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.loaders.events { + import flash.events.ErrorEvent; + import flash.events.Event; + + /** + * Класс представляет событие ошибки, генерируемое пакетным загрузчиком текстур. + */ + public class BatchTextureLoaderErrorEvent extends ErrorEvent { + + /** + * + */ + public static const LOADER_ERROR:String = "loaderError"; + + // Имя текстуры, с которой произошла проблема + private var _textureName:String; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param textureName имя текстуры, с которой произошла проблема + * @param text описание ошибки + */ + public function BatchTextureLoaderErrorEvent(type:String, textureName:String, text:String) { + super(type); + this.text = text; + _textureName = textureName; + } + + /** + * Имя текстуры, с которой произошла проблема. + */ + public function get textureName():String { + return _textureName; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new BatchTextureLoaderErrorEvent(type, _textureName, text); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[BatchTextureLoaderErrorEvent textureName=" + _textureName + ", text=" + text + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..57a739d --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,68 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Событие загрузчиков ресурсов, состоящих из нескольких частей. + */ + public class LoaderEvent extends Event { + /** + * Событие начала загрузки очередной части ресурса. + */ + public static const PART_OPEN:String = "partOpen"; + /** + * Событие окончания загрузки очередной части ресурса. + */ + public static const PART_COMPLETE:String = "partComplete"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер части, к которой относится событие. Нумерация начинается с нуля + */ + public function LoaderEvent(type:String, partsTotal:int, currentPart:int) { + super(type); + _partsTotal = partsTotal; + _currentPart= currentPart; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderEvent(type, _partsTotal, _currentPart); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..273740e --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Событие прогресса загрузки ресурсов, состоящих из нескольких частей. + */ + public class LoaderProgressEvent extends ProgressEvent { + + /** + * Событие прогресса загрузки очередной части ресурса. + */ + public static const LOADER_PROGRESS:String = "loaderProgress"; + + // Общее количество загружаемых частей + private var _partsTotal:int; + // Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + private var _currentPart:int; + // Общий прогресс загрузки, выраженный числом в интервале [0, 1] + private var _totalProgress:Number = 0; + + /** + * Создаёт новый экземпляр. + * + * @param type тип события + * @param totalParts общее количество загружаемых частей + * @param currentPart номер загружаемой в настоящий момент части. Нумерация начинается с нуля + * @param totalProgress общий прогресс загрузки, выраженный числом в интервале [0, 1] + * @param bytesLoaded количество загруженных байт текущей части + * @param bytesTotal объём текущей части + */ + public function LoaderProgressEvent(type:String, partsTotal:int, currentPart:int, totalProgress:Number = 0, bytesLoaded:uint=0, bytesTotal:uint=0) { + super(type, false, false, bytesLoaded, bytesTotal); + _partsTotal = partsTotal; + _currentPart= currentPart; + _totalProgress = totalProgress; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер загружаемой в настоящий момент части. Нумерация начинается с нуля. + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Общий прогресс загрузки, выраженный числом в интервале [0, 1]. + */ + public function get totalProgress():Number { + return _totalProgress; + } + + /** + * Клонирует объект. + * + * @return клон объекта + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _partsTotal, _currentPart, _totalProgress, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строкове представление объекта. + * + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", totalProgress=" + _totalProgress.toFixed(2) + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/AnimSprite.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..b7e5ff5 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,72 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Анимированный спрайт. + * Анимация осуществляется путём переключения изображений, + * хранящихся в списке textures + */ + public class AnimSprite extends Sprite3D { + + /** + * Список кадров изображений + */ + public var textures:Vector.; + public var mipMaps:Vector.; + /** + * Устанавливаемый кадр + */ + public var frame:uint = 0; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + super.draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + super.debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + return super.getGeometry(camera, object); + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + if (mipMapping == 0) { + texture = textures[frame]; + } else { + mipMap = mipMaps[frame]; + } + return super.calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Axes.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Axes.as new file mode 100644 index 0000000..ae1e36f --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Axes.as @@ -0,0 +1,120 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.display.Graphics; + import flash.geom.Utils3D; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, иллюстрирующий систему коордщинат + */ + public class Axes extends Object3D { + + public var axisLength:Number; + public var lineThickness:Number; + public var dotRadius:Number; + public var textSize:Number; + + public function Axes(axisLength:Number = 30, lineThickness:Number = 0, dotRadius:Number = 2, textSize:Number = 10):void { + this.axisLength = axisLength; + this.lineThickness = lineThickness; + this.dotRadius = dotRadius; + this.textSize = textSize; + _boundBox = new BoundBox(); + _boundBox.setSize(-dotRadius, -dotRadius, -dotRadius, axisLength, axisLength, axisLength); + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + var p:Vector. = Vector.([0, 0, 0, axisLength, 0, 0, 0, axisLength, 0, 0, 0, axisLength]); + var d:Vector. = new Vector.(8); + object.cameraMatrix.transformVectors(p, p); + + // Центр за камерой + if (p[2] < camera.nearClipping) return; + + Utils3D.projectVectors(camera.projectionMatrix, p, d, new Vector.()); + var size:Number = camera.viewSize/p[2]; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + var gfx:Graphics = canvas.gfx; + var text:TextField; + + // Ось X + if (p[5] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0xFF0000); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[2], d[3]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[2]; + text.y = d[3]; + text.text = "X"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0xFF0000)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Y + if (p[8] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x00FF00); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[4], d[5]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[4]; + text.y = d[5]; + text.text = "Y"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x00FF00)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Z + if (p[11] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x0000FF); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[6], d[7]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[6]; + text.y = d[7]; + text.text = "Z"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x0000FF)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Начало координат + gfx.lineStyle(); + gfx.beginFill(0xFFFFFF); + gfx.drawCircle(d[0], d[1], dotRadius*size); + + //debugDrawBoundRaduis(camera, object, canvas); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Bone.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Bone.as new file mode 100644 index 0000000..e75a798 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Bone.as @@ -0,0 +1,60 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class Bone extends Mesh { + + public var localTransform:Vector.; + /** + * @private + */ + alternativa3d var localMatrix:Matrix3D; + + /** + * @private + */ + alternativa3d var length:Number; + /** + * @private + */ + alternativa3d var distance:Number; + /** + * @private + */ + alternativa3d var _numChildren:uint = 0; + /** + * @private + */ + alternativa3d var children:Vector. = new Vector.(); + + public function Bone(length:Number, distance:Number) { + this.length = length; + this.distance = distance; + } + + public function addChild(child:Bone):void { + children[_numChildren++] = child; + child.localTransform = child.matrix.decompose(); + child.localMatrix = new Matrix3D(); + } + + public function calculateMatrix():void { + for (var i:int = 0; i < _numChildren; i++) { + var child:Bone = children[i]; + child.matrix.identity(); + child.matrix.prepend(matrix); + child.localMatrix.recompose(child.localTransform); + child.matrix.prepend(child.localMatrix); + child.calculateMatrix(); + } + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/LOD.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..ec06539 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,74 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект, имеющий набор объектов с разной детализацией. + * При отрисовке, он выбирает в зависимости от расстояния от камеры + * объект с нужной детализацией и отрисовывает его вместо себя. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class LOD extends Object3D { + + /** + * Объекты с разной детализацией + */ + public var lodObjects:Vector.; + /** + * Расстояния до камеры соответствующие объектам с разной детализацией + */ + public var lodDistances:Vector.; + + /** + * @private + */ + private function getLODObject(object:Object3D):Object3D { + var cameraDistance:Number = object.cameraMatrix.position.length; + // Поиск ближайшего лода + var min:Number = Infinity; + var length:uint = lodObjects.length; + var lod:Object3D; + for (var i:int = 0; i < length; i++) { + var d:Number = Math.abs(cameraDistance - lodDistances[i]); + if (d < min) { + min = d; + lod = lodObjects[i]; + } + } + return lod; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return getLODObject(object).getGeometry(camera, object); + } + + override public function get boundBox():BoundBox { + return (lodObjects[0] as Object3D).boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return (lodObjects[0] as Object3D).calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Mesh.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..5891419 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,2897 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Полигональный объект + */ + public class Mesh extends Object3D { + + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; + + /** + * Вершины в виде x, y, z + */ + public var vertices:Vector.; + /** + * UV-координаты в виде u, v, t + */ + public var uvts:Vector.; + /** + * Количество вершин + */ + public var numVertices:int = 0; + /** + * Индексы вершин + */ + public var indices:Vector.; + /** + * Количество граней + */ + public var numFaces:int = 0; + + public var texture:BitmapData; + public var smooth:Boolean = false; + public var repeatTexture:Boolean = true; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 1 - по граням + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; + /** + * Режим отсечения граней по направлению к камере + * 0 - отсечение по предрасчитанным нормалям. Для расчёта нормалей нужен calculateNormals() + * 1 - отсечение по динамически расчитываемым временным нормалям + */ + public var backfaceCulling:int = 0; + /** + * Режим сортировки полигонов + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + * 3 - построение динамического BSP при отрисовке + */ + public var sorting:int = 0; + public var bsp:BSPNode; + /** + * Применение мипмаппинга + * 0 - без мипмаппинга + * 1 - мипмаппинг по удалённости от камеры. Требуется установка свойства mipMap + */ + public var mipMapping:int = 0; + public var mipMap:MipMap; + /** + * Нормали в виде: x, y, z, offset + */ + public var normals:Vector.; + /** + * Геометрическая погрешность при расчёте BSP-дерева + */ + public var threshold:Number = 0.1; + + private var cameraVertices:Vector. = new Vector.(); + private var projectedVertices:Vector. = new Vector.(); + + // Вспомогательные вектора + static private const polygon:Vector. = new Vector.(); + static private const sortingStack:Vector. = new Vector.(); + static private const sortingMap:Vector. = new Vector.(); + static private const sortingAverageZ:Vector. = new Vector.(); + static private const indices1:Vector. = new Vector.(); + static private const indices2:Vector. = new Vector.(); + static private const fragments:Vector. = new Vector.(); + static private const verticesMap:Vector. = new Vector.(); + static private var fragmentsRealLength:int = 0; + static private var fragmentsLength:int; + protected var sourceIndices:Vector.; + protected var sourceIndicesLength:int; + protected var resultIndices:Vector.; + protected var resultIndicesLength:int; + + public function createEmptyGeometry(numVertices:uint, numFaces:uint):void { + this.numVertices = numVertices; + this.numFaces = numFaces; + vertices = new Vector.(numVertices*3); + indices = new Vector.(numFaces*3); + uvts = new Vector.(numVertices*3); + } + + /** + * @private + */ + override alternativa3d function get canDraw():Boolean { + return (texture != null || mipMap != null) && numFaces > 0; + } + + static alternativa3d const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const cameraCenter:Vector. = new Vector.(3, true); + alternativa3d var cameraX:Number; + alternativa3d var cameraY:Number; + alternativa3d var cameraZ:Number; + alternativa3d function calculateInverseCameraMatrix(matrix:Matrix3D):void { + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(matrix); + inverseCameraMatrix.invert(); + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + inverseCameraMatrix.transformVectors(cameraCenter, cameraCenter); + cameraX = cameraCenter[0], cameraY = cameraCenter[1], cameraZ = cameraCenter[2]; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Выход по объектному клиппингу + if (clipping == 0 && (object.culling & 1)) return; + // Подготовка к отсечению по предрасчитанным нормалям + if (backfaceCulling == 1 || sorting == 2) calculateInverseCameraMatrix(object.cameraMatrix); + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Текущий тип клиппинга: 0 - целиком, 1 - по граням, 2 - подрезка + var clippingType:int = (object.culling == 0 || clipping == 0) ? 0 : clipping; + // Полная отрисовка + if (clippingType == 0) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) triangulate(); + } else { + backfaceCullTriangles(); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + backfaceCullPolygons(); + if (resultIndicesLength > 0) sortPolygons(); + } else { + backfaceCullTriangles(); + if (resultIndicesLength > 0) sortTriangles(); + } + // Статическое BSP + } else if (sorting == 2) { + resultIndices = indices2, resultIndicesLength = 0; + collectNodeTriangles(bsp); + // Динамическое BSP + } else { + throw new Error("Dynamic BSP not realised yet"); + } + // Куллинг + } else if (clippingType == 1) { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // Статическое BSP + } else if (sorting == 2) { + resultIndices = indices2, resultIndicesLength = 0; + cullNode(bsp, object.culling, camera.nearClipping, camera.farClipping); + // Динамическое BSP + } else { + throw new Error("Dynamic BSP not realised yet"); + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) triangulate(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + } + // Сортировка по средним Z + } else if (sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) { + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortPolygons(); + } else { + clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + if (resultIndicesLength > 0) sortTriangles(); + } + // Статическое BSP + } else if (sorting == 2) { + resultIndices = indices2, resultIndicesLength = 0; + clipNode(bsp, object.culling, camera.nearClipping, camera.farClipping, numVertices); + // Динамическое BSP + } else { + throw new Error("Dynamic BSP not realised yet"); + } + } + // Отрисовка + if (resultIndicesLength > 0) { + // Подрезка + resultIndices.length = resultIndicesLength; + // Количество отрисовываемых треугольников + camera.numTriangles += resultIndicesLength/3; + // Коррекция области перерисовки + //correctRedrawRegion(object.culling, camera); + // Проецирование + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Коррекция области перерисовки + if (clippingType > 0) cropVertices(object.culling, camera); + // Отрисовка графики + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.gfx.beginBitmapFill((mipMapping == 0) ? texture : getMipTexture(camera, object), null, repeatTexture, smooth); + canvas.gfx.drawTriangles(projectedVertices, resultIndices, uvts, "none"); + } + // Подрезка вершин и UV + if (clippingType == 2) { + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = numVertices << 1; + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + var i:int, j:int, k:int, n:int, vi:int, a:int, b:int, c:int, x:Number, y:Number, z:Number, indicesLength:int, backface:int = backfaceCulling; + if (debugResult & Debug.EDGES || debugResult & Debug.VERTICES || debugResult & Debug.NORMALS) { + // Трансформация + calculateInverseCameraMatrix(object.cameraMatrix); + object.cameraMatrix.transformVectors(vertices, cameraVertices); + if (clipping != 0 || !(object.culling & 1)) { + // Полная отрисовка + if (object.culling == 0 || clipping == 0) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) backfaceCullPolygons() else backfaceCullTriangles(); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + } + // Куллинг + } else if (clipping == 1) { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) cullPolygons(object.culling, camera.nearClipping, camera.farClipping) else cullTriangles(object.culling, camera.nearClipping, camera.farClipping); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 0; + cullPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + // Клиппинг + } else { + // Без сортировки + if (sorting == 0 || sorting == 1) { + resultIndices = indices, resultIndicesLength = poly ? indices.length : numFaces*3; + if (poly) clipPolygons(object.culling, camera.nearClipping, camera.farClipping) else clipTriangles(object.culling, camera.nearClipping, camera.farClipping); + // BSP + } else { + resultIndices = indices2, resultIndicesLength = 0; + collectNodePolygons(bsp); + backfaceCulling = 0; + clipPolygons(object.culling, camera.nearClipping, camera.farClipping); + backfaceCulling = backface; + } + } + // Отрисовка + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + // Рёбра + if (debugResult & Debug.EDGES) { + // Полигоны + if (poly || sorting == 2) { + for (i = 0; i < resultIndicesLength; i = k) { + k = resultIndices[i++] + i; + canvas.gfx.lineStyle(0, 0xFFFFFF, 0.3); + for (j = i + 2; j < k - 1; j++) { + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[i] << 1], projectedVertices[++vi]); + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + canvas.gfx.lineStyle(0, 0xFFFFFF); + canvas.gfx.moveTo(projectedVertices[vi = resultIndices[int(k - 1)] << 1], projectedVertices[++vi]); + for (j = i; j < k; j++) { + canvas.gfx.lineTo(projectedVertices[vi = resultIndices[j] << 1], projectedVertices[++vi]); + } + } + // Треугольники + } else { + canvas.gfx.lineStyle(0, 0xFFFFFF); + for (i = 0; i < resultIndicesLength;) { + a = resultIndices[i++], b = resultIndices[i++], c = resultIndices[i++]; + canvas.gfx.moveTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[b << 1], projectedVertices[(b << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[c << 1], projectedVertices[(c << 1) + 1]); + canvas.gfx.lineTo(projectedVertices[a << 1], projectedVertices[(a << 1) + 1]); + } + } + } + // Вершины + if (debugResult & Debug.VERTICES) { + canvas.gfx.lineStyle(); + for (i = 0; i < numVertices; i++) sortingMap[i] = 0; + for (i = 0, k = 0; i < resultIndicesLength; i++) { + if (i == k) k = (poly || sorting == 2) ? (resultIndices[i++] + i) : (i + 3); + if (resultIndices[i] < numVertices) sortingMap[resultIndices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] != 0) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + // Изолированные вершины + for (i = 0, k = 0, indicesLength = indices.length; i < indicesLength; i++) { + if (i == k) k = (poly) ? (indices[i++] + i) : (i + 3); + if (indices[i] < numVertices) sortingMap[indices[i]] = 1; + } + for (i = 0; i < numVertices; i++) { + if (sortingMap[i] == 0 && (cameraVertices[i*3 + 2]) > camera.nearClipping) { + canvas.gfx.beginFill(0xFF0000); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 3); + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(projectedVertices[vi = i << 1], projectedVertices[++vi], 2); + } + } + } + // Нормали + if (debugResult & Debug.NORMALS) { + if (object.culling != 0 && clipping == 2) resultIndices = sourceIndices, resultIndicesLength = sourceIndicesLength; + for (i = 0, k = 0, n = 0; i < resultIndicesLength; i = k) { + k = (poly || sorting == 2) ? (resultIndices[i++] + i) : (i + 3); + x = y = z = 0; + for (j = i; j < k; j++) { + x += cameraVertices[vi = int(resultIndices[j]*3)]; + y += cameraVertices[++vi]; + z += cameraVertices[++vi]; + } + x /= (k - i); + y /= (k - i); + z /= (k - i); + if (clipping == 2 && (z < camera.nearClipping || z > camera.farClipping || z < -x || z < x || z < -y || z < y)) continue; + projectedVertices[n++] = x; + projectedVertices[n++] = y; + projectedVertices[n++] = z; + var ax:Number = cameraVertices[vi = int(resultIndices[i]*3)]; + var ay:Number = cameraVertices[++vi]; + var az:Number = cameraVertices[++vi]; + var abx:Number = cameraVertices[vi = int(resultIndices[int(i + 1)]*3)] - ax; + var aby:Number = cameraVertices[++vi] - ay; + var abz:Number = cameraVertices[++vi] - az; + var acx:Number = cameraVertices[vi = int(resultIndices[int(i + 2)]*3)] - ax; + var acy:Number = cameraVertices[++vi] - ay; + var acz:Number = cameraVertices[++vi] - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + if (nl != 0) { + nx /= nl; + ny /= nl; + nz /= nl; + } + var projectionZ:Number = camera.focalLength/z; + projectedVertices[n++] = x + 15*nx*camera.perspectiveScaleX/projectionZ; + projectedVertices[n++] = y + 15*ny*camera.perspectiveScaleY/projectionZ; + projectedVertices[n++] = z + 15*nz/projectionZ; + } + projectedVertices.length = n; + Utils3D.projectVectors(camera.projectionMatrix, projectedVertices, projectedVertices, uvts); + n = n/3 << 1; + for (i = 0; i < n;) { + canvas.gfx.lineStyle(); + canvas.gfx.beginFill(0x00FFFF); + canvas.gfx.drawCircle(projectedVertices[i], projectedVertices[int(i + 1)], 1.5); + canvas.gfx.lineStyle(0, 0x00FFFF); + canvas.gfx.moveTo(projectedVertices[i++], projectedVertices[i++]); + canvas.gfx.lineTo(projectedVertices[i++], projectedVertices[i++]); + } + } + } + // Подрезка + cameraVertices.length = uvts.length = numVertices*3; + projectedVertices.length = numVertices << 1; + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + // Сбор треугольников BSP-дерева + private function collectNodeTriangles(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodeTriangles(node.negative); + for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.positive != null) collectNodeTriangles(node.positive); + } else { + if (node.positive != null) collectNodeTriangles(node.positive); + if (node.negative != null) collectNodeTriangles(node.negative); + } + } + + /*private function collectBSPTriangles():void { + var direction:Boolean = true, node:BSPNode = bsp, negative:BSPNode, positive:BSPNode, prev:BSPNode; + while (node != null) { + if (direction) { + if (node.cameraInfront = cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + negative = node.negative; + if (negative != null) negative.prev = node, node = negative else direction = false; + } else { + positive = node.positive; + if (positive != null) positive.prev = node, node = positive else direction = false; + } + } else { + //if (node.cameraInfront || backfaceCulling == 0) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + if (node.cameraInfront) for (var i:int = 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) resultIndices[resultIndicesLength++] = triangles[i++]; + prev = node.prev, node.prev = null; + if (node.cameraInfront) { + positive = node.positive; + if (positive != null) positive.prev = prev, node = positive, direction = true else node = prev; + } else { + negative = node.negative; + if (negative != null) negative.prev = prev, node = negative, direction = true else node = prev; + } + } + } + }*/ + + // Сбор полигонов BSP-дерева + private function collectNodePolygons(node:BSPNode):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) collectNodePolygons(node.negative); + for (var i:int = 0, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength;) resultIndices[resultIndicesLength++] = polygons[i++]; + if (node.positive != null) collectNodePolygons(node.positive); + } else { + if (node.positive != null) collectNodePolygons(node.positive); + if (node.negative != null) collectNodePolygons(node.negative); + } + } + + // Куллинг треугольников BSP-дерева + private function cullNode(node:BSPNode, culling:int, near:Number, far:Number):void { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) cullNode(node.negative, culling, near, far); + // Проход по ноде + for (var i:int = 0, x:Boolean = (culling & 12) > 0, y:Boolean = (culling & 48) > 0, triangles:Vector. = node.triangles, trianglesLength:int = node.trianglesLength; i < trianglesLength;) { + var a:int = triangles[i++], b:int = triangles[i++], c:int = triangles[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || az <= -ax && bz <= -bx && cz <= -cx || az <= ax && bz <= bx && cz <= cx || az <= -ay && bz <= -by && cz <= -cy || az <= ay && bz <= by && cz <= cy) continue; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + if (node.positive != null) cullNode(node.positive, culling, near, far); + } else { + if (node.positive != null) cullNode(node.positive, culling, near, far); + if (node.negative != null) cullNode(node.negative, culling, near, far); + } + } + + // Клиппинг полигонов BSP-дерева + private function clipNode(node:BSPNode, culling:int, near:Number, far:Number, v:int):int { + if (cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset) { + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + // Проход по ноде + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, num1:int, num2:int = 0, vi:int, t:Number, au:Number, av:Number, polygons:Vector. = node.polygons, polygonsLength:int = node.polygonsLength; i < polygonsLength; i = k) { + k = (num1 = polygons[i++]) + i; + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(polygons[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(polygons[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + // Триангуляция + for (j = i, a = polygons[j++], b = polygons[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygons[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = polygons[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0, v++; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0, v++; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0, v++; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + } else { + if (node.positive != null) v = clipNode(node.positive, culling, near, far, v); + if (node.negative != null) v = clipNode(node.negative, culling, near, far, v); + } + return v; + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + // Выход по объектному клиппингу + if (clipping == 0 && (object.culling & 1)) return null; + // Подготовка к отсечению по предрасчитанным нормалям + if (backfaceCulling == 1 || sorting == 2) calculateInverseCameraMatrix(object.cameraMatrix); + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Сброс карты + for (var i:int = 0; i < numVertices; i++) { + verticesMap[i] = -1; + } + var geometry:Geometry = Geometry.create(); + var culling:int = (object.culling == 0 || clipping == 0) ? 0 : object.culling + // Если статическое BSP + if (sorting == 2) { + geometry.fragment = getNodeFragments(bsp, geometry, culling, camera.nearClipping, camera.farClipping); + } else { + geometry.fragment = getFragments(geometry, indices, indices.length, poly, culling, camera.nearClipping, camera.farClipping); + } + // Подрезка + if (culling > 0 && clipping == 2) { + cameraVertices.length = uvts.length = numVertices*3; + } + // Если есть фрагменты + if (geometry.fragment != null) { + geometry.cameraMatrix.identity(); + geometry.cameraMatrix.prepend(object.cameraMatrix); + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.texture = (mipMapping == 0) ? texture : getMipTexture(camera, object); + geometry.repeatTexture = repeatTexture; + geometry.smooth = smooth; + if (camera.debugMode) { + geometry.debugResult = camera.checkInDebug(this); + } + return geometry; + } else { + geometry.destroy(); + return null; + } + } + + private function getNodeFragments(node:BSPNode, geometry:Geometry, culling:int, near:Number, far:Number):Fragment { + // Проход по дочерним нодам + var infront:Boolean = cameraX*node.normalX + cameraY*node.normalY + cameraZ*node.normalZ > node.offset; + var fragment:Fragment = infront ? getFragments(geometry, node.polygons, node.polygonsLength, true, culling, near, far) : null; + var negative:Fragment = (node.negative != null) ? getNodeFragments(node.negative, geometry, culling, near, far) : null; + var positive:Fragment = (node.positive != null) ? getNodeFragments(node.positive, geometry, culling, near, far) : null; + // Если нода видна или есть видимые дочерние ноды + if (fragment != null || negative != null && positive != null) { + if (fragment == null) { + fragment = Fragment.create(); + } + fragment.negative = negative; + fragment.positive = positive; + // Расчёт нормали + var vi:int = node.polygons[1]*3; + var ax:Number = cameraVertices[vi]; vi++; + var ay:Number = cameraVertices[vi]; vi++; + var az:Number = cameraVertices[vi]; + vi = node.polygons[2]*3; + var abx:Number = cameraVertices[vi] - ax; vi++; + var aby:Number = cameraVertices[vi] - ay; vi++; + var abz:Number = cameraVertices[vi] - az; + vi = node.polygons[3]*3; + var acx:Number = cameraVertices[vi] - ax; vi++; + var acy:Number = cameraVertices[vi] - ay; vi++; + var acz:Number = cameraVertices[vi] - az; + fragment.normalX = acz*aby - acy*abz; + fragment.normalY = acx*abz - acz*abx; + fragment.normalZ = acy*abx - acx*aby; + var len:Number = 1/Math.sqrt(fragment.normalX*fragment.normalX + fragment.normalY*fragment.normalY + fragment.normalZ*fragment.normalZ); + fragment.normalX *= len; + fragment.normalY *= len; + fragment.normalZ *= len; + fragment.offset = ax*fragment.normalX + ay*fragment.normalY + az*fragment.normalZ; + return fragment; + } else { + return (negative != null) ? negative : positive; + } + } + + private function getFragments(geometry:Geometry, source:Vector., sourceLength:int, poly:Boolean, culling:int, near:Number, far:Number):Fragment { + var first:Fragment; + var last:Fragment; + var j:int; + var nx:Number; + var ny:Number; + var nz:Number; + var no:Number; + var nl:Number; + var a:int; + var b:int; + var c:int; + var ai:int; + var bi:int; + var ci:int; + var vi:int; + var vj:int; + var anx:Number; + var any:Number; + var anz:Number; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var t:Number; + var au:Number; + var av:Number; + var bu:Number; + var bv:Number; + var inside:Boolean; + var outside:Boolean; + var x:Boolean = (culling & 12) > 0; + var y:Boolean = (culling & 48) > 0; + var ni:int = 0; + var num:int = 3; + var k:int = 0; + var v:int = numVertices; + for (var i:int = 0; i < sourceLength; i = k) { + if (poly) { + num = source[i]; i++; + k++; + } + k += num; + var result:Vector. = source; + var resultBegin:int = i; + var resultEnd:int = k; + // Отсечение по backface + if (sorting != 2) { + // Выход по предрасчитанной нормали + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + continue; + } + // Расчёт динамической нормали + if (sorting == 3 || backfaceCulling == 0) { + a = i; + vi = source[a]*3; a++; + anx = cameraVertices[vi]; vi++; + any = cameraVertices[vi]; vi++; + anz = cameraVertices[vi]; + vi = source[a]*3; a++; + var abx:Number = cameraVertices[vi] - anx; vi++; + var aby:Number = cameraVertices[vi] - any; vi++; + var abz:Number = cameraVertices[vi] - anz; + vi = source[a]*3; + var acx:Number = cameraVertices[vi] - anx; vi++; + var acy:Number = cameraVertices[vi] - any; vi++; + var acz:Number = cameraVertices[vi] - anz; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + // Выход по динамической нормали + if (backfaceCulling == 0 && nx*anx + ny*any + nz*anz >= 0) { + continue; + } + } + } + // Отсечение по фрустуму + if (culling > 0) { + // Куллинг + var polygonCulling:int = 0; + // Полигоны + if (poly) { + if (clipping == 1) { + if (culling & 1) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] <= near) break; + } + if (j < k) continue; + } + if (culling & 2) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] < far) break; + } + if (j == k) continue; + } + if (culling & 4) { + for (j = i; j < k; j++) { + vi = source[j]*3; + bi = vi + 2; + if (-cameraVertices[vi] < cameraVertices[bi]) break; + } + if (j == k) continue; + } + if (culling & 8) { + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + if (culling & 16) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (-cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + if (culling & 32) { + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (cameraVertices[vi] < cameraVertices[vj]) break; + } + if (j == k) continue; + } + } else { + if (culling & 1) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] > near) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 1; + } + } + } + if (culling & 2) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 2; + if (cameraVertices[vi] < far) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 2; + } + } + } + if (culling & 4) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (-cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 4; + } + } + } + if (culling & 8) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3; + vj = vi + 2; + if (cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 8; + } + } + } + if (culling & 16) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (-cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 16; + } + } + } + if (culling & 32) { + inside = false; + outside = false; + for (j = i; j < k; j++) { + vi = source[j]*3 + 1; + vj = vi + 1; + if (cameraVertices[vi] < cameraVertices[vj]) { + inside = true; + if (outside) break; + } else { + outside = true; + if (inside) break; + } + } + if (outside) { + if (!inside) { + continue; + } else { + polygonCulling |= 32; + } + } + } + } + // Треугольники + } else { + a = source[i++]; + b = source[i++]; + c = source[i++]; + ai = a*3; + bi = b*3; + ci = c*3; + if (x) { + ax = cameraVertices[ai]; + bx = cameraVertices[bi]; + cx = cameraVertices[ci]; + } + if (y) { + ay = cameraVertices[int(ai + 1)]; + by = cameraVertices[int(bi + 1)]; + cy = cameraVertices[int(ci + 1)]; + } + az = cameraVertices[int(ai + 2)]; + bz = cameraVertices[int(bi + 2)]; + cz = cameraVertices[int(ci + 2)]; + if (clipping == 1) { + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) { + continue; + } + } else { + if (az <= near && bz <= near && cz <= near || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) { + continue; + } + if (az <= near || bz <= near || cz <= near) { + polygonCulling |= 1; + } + if (az >= far || bz >= far || cz >= far) { + polygonCulling |= 2; + } + if (x && (az <= -ax || bz <= -bx || cz <= -cx)) { + polygonCulling |= 4; + } + if (x && (az <= ax || bz <= bx || cz <= cx)) { + polygonCulling |= 8; + } + if (y && (az <= -ay || bz <= -by || cz <= -cy)) { + polygonCulling |= 16; + } + if (y && (az <= ay || bz <= by || cz <= cy)) { + polygonCulling |= 32; + } + } + } + // Клиппинг + if (clipping == 2 && polygonCulling > 0) { + result = polygon; + resultBegin = 0; + // Заполнение полигона + var num1:int = 0; + var num2:int = 0; + if (poly) { + for (j = i; j < k; j++) { + result[num1] = source[j]; + num1++; + } + } else { + result[0] = a; + result[1] = b; + result[2] = c; + num1 = 3; + } + + // Клипинг по ниар + if (polygonCulling & 1) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = near; + uvts[vi] = 0; + } + if (bz > near) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по фар + if (polygonCulling & 2) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz <= far && az > far || bz > far && az <= far) { + t = (far - az)/(bz - az); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = far; + uvts[vi] = 0; + } + if (bz <= far) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по левой стороне + if (polygonCulling & 4) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + ax = cameraVertices[ai]; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bx = cameraVertices[bi]; + bz = cameraVertices[vi]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + result[num2] = v; num2++; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > -bx) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ax = bx; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по правой стороне + if (polygonCulling & 8) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 2; + ax = cameraVertices[ai]; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 2; + bx = cameraVertices[bi]; + bz = cameraVertices[vi]; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + result[num2] = v; num2++; + au = uvts[ai]; + ai++; + ay = cameraVertices[ai]; + av = uvts[ai]; + bu = uvts[bi]; + vi = bi + 1; + by = cameraVertices[vi]; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > bx) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ax = bx; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по верхней стороне + if (polygonCulling & 16) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 1; + ay = cameraVertices[vi]; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 1; + by = cameraVertices[vi]; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > -by) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ay = by; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + // Клипинг по нижней стороне + if (polygonCulling & 32) { + a = result[0]; + c = a; + ai = a*3; + vi = ai + 1; + ay = cameraVertices[vi]; + vi = ai + 2; + az = cameraVertices[vi]; + for (j = 1; j <= num1; j++) { + b = (j < num1) ? result[j] : c; + bi = b*3; + vi = bi + 1; + by = cameraVertices[vi]; + vi = bi + 2; + bz = cameraVertices[vi]; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + result[num2] = v; num2++; + ax = cameraVertices[ai]; + au = uvts[ai]; + ai++; + av = uvts[ai]; + bx = cameraVertices[bi]; + bu = uvts[bi]; + vi = bi + 1; + bv = uvts[vi]; + vi = v*3; v++; + cameraVertices[vi] = ax + (bx - ax)*t; + uvts[vi] = au + (bu - au)*t; vi++; + cameraVertices[vi] = ay + (by - ay)*t; + uvts[vi] = av + (bv - av)*t; vi++; + cameraVertices[vi] = az + (bz - az)*t; + uvts[vi] = 0; + } + if (bz > by) { + result[num2] = b; + num2++; + } + a = b; + ai = bi; + ay = by; + az = bz; + } + if (num2 == 0) continue; + num1 = num2; + num2 = 0; + } + resultEnd = num1; + } + } + // Создание фрагмента + if (first != null) { + last.next = last.create(); + last = last.next; + } else { + first = Fragment.create(); + last = first; + } + // Нормализация и присвоение нормали + if (sorting == 3) { + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + last.normalX = nx*nl; + last.normalY = ny*nl; + last.normalZ = nz*nl; + last.offset = anx*last.normalX + any*last.normalY + anz*last.normalZ; + } + // Заполнение и ремап + for (j = resultBegin; j < resultEnd; j++) { + a = result[j]; + if (a >= numVertices || verticesMap[a] < 0) { + if (a < numVertices) { + verticesMap[a] = geometry.numVertices; + } + vi = a*3; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi], + geometry.uvts[geometry.verticesLength] = uvts[vi]; vi++; + geometry.verticesLength++; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi]; + geometry.uvts[geometry.verticesLength] = uvts[vi]; vi++; + geometry.verticesLength++; + geometry.vertices[geometry.verticesLength] = cameraVertices[vi]; + geometry.uvts[geometry.verticesLength] = uvts[vi]; + geometry.verticesLength++; + last.indices[last.num] = geometry.numVertices; + geometry.numVertices++; + } else { + last.indices[last.num] = verticesMap[a]; + } + last.num++; + } + } + return first; + } + + // Отсечение треугольников по нормалям + private function backfaceCullTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0; + if (backfaceCulling == 1) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++] else i += 3; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++], resultIndices[resultIndicesLength++] = sourceIndices[i++] else i += 3; + } + } + } + + // Отсечение полигонов по нормалям + private function backfaceCullPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var i:int = 0, k:int = 0; + if (backfaceCulling == 1) { + // Отсечение по предрасчитанным нормалям + for (var ni:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + if (normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ > normals[ni++]) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } else { + // Отсечение по динамическим нормалям + for (var vi:int = 0; i < sourceIndicesLength;) { + if (i == k) { + k = sourceIndices[i++] + i; + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az < 0) { + resultIndices[resultIndicesLength++] = sourceIndices[int(i - 1)]; + } else { + i = k; + continue; + } + } + resultIndices[resultIndicesLength++] = sourceIndices[i++]; + } + } + } + + // Отсечение треугольников по пирамиде видимости + private function cullTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 0, y:Boolean = (culling & 48) > 0 || backfaceCulling == 0; + for (var i:int = 0, ni:int = 0; i < sourceIndicesLength;) { + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + if ((az <= near || bz <= near || cz <= near) || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 0) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } + } + + // Отсечение полигонов по пирамиде видимости + private function cullPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, j:int, ni:int, num:int, vi:int; i < sourceIndicesLength; i = k) { + k = (num = sourceIndices[i++]) + i; + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 0) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + if (culling & 1) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] <= near) break; + if (j < k) continue; + } + if (culling & 2) { + for (j = i; j < k; j++) if (cameraVertices[int(sourceIndices[j]*3 + 2)] < far) break; + if (j == k) continue; + } + if (culling & 4) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 8) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) break; + if (j == k) continue; + } + if (culling & 16) { + for (j = i; j < k; j++) if (-cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + if (culling & 32) { + for (j = i; j < k; j++) if (cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) break; + if (j == k) continue; + } + resultIndices[resultIndicesLength++] = num; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } + } + + private function clipTriangles(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var x:Boolean = (culling & 12) > 0 || backfaceCulling == 0, y:Boolean = (culling & 48) > 0 || backfaceCulling == 0; + for (var i:int = 0, j:int, ni:int = 0, v:int = numVertices, vi:int, t:Number, au:Number, av:Number, num1:int, num2:int; i < sourceIndicesLength;) { + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) { + i += 3; + continue; + } + var a:int = sourceIndices[i++], b:int = sourceIndices[i++], c:int = sourceIndices[i++], ai:int = a*3, bi:int = b*3, ci:int = c*3; + if (x) var ax:Number = cameraVertices[ai], bx:Number = cameraVertices[bi], cx:Number = cameraVertices[ci]; + if (y) var ay:Number = cameraVertices[int(ai + 1)], by:Number = cameraVertices[int(bi + 1)], cy:Number = cameraVertices[int(ci + 1)]; + var az:Number = cameraVertices[int(ai + 2)], bz:Number = cameraVertices[int(bi + 2)], cz:Number = cameraVertices[int(ci + 2)]; + // За пределами пирамиды видимости + if (az <= near && bz <= near && cz <= near || az >= far && bz >= far && cz >= far || x && az <= -ax && bz <= -bx && cz <= -cx || x && az <= ax && bz <= bx && cz <= cx || y && az <= -ay && bz <= -by && cz <= -cy || y && az <= ay && bz <= by && cz <= cy) continue; + if (backfaceCulling == 0) { + var abx:Number = bx - ax, aby:Number = by - ay, abz:Number = bz - az, acx:Number = cx - ax, acy:Number = cy - ay, acz:Number = cz - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Полностью в пирамиде видимости + var insideNear:Boolean = !(culling & 1) || az > near && bz > near && cz > near, insideFar:Boolean = !(culling & 2) || az < far && bz < far && cz < far, insideLeft:Boolean = !(culling & 4) || az > -ax && bz > -bx && cz > -cx, insideRight:Boolean = !(culling & 8) || az > ax && bz > bx && cz > cx, insideTop:Boolean = !(culling & 16) || az > -ay && bz > -by && cz > -cy, insideBottom:Boolean = !(culling & 32) || az > ay && bz > by && cz > cy; + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = c; + } else { + // Заполняем полигон + polygon[0] = a, polygon[1] = b, polygon[2] = c, num1 = 3, num2 = 0; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Триангуляция полигона + for (j = 2, a = polygon[0], b = polygon[1]; j < num1;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = polygon[j++]; + } + } + } + + private function clipPolygons(culling:int, near:Number, far:Number):void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + var infront:Boolean, behind:Boolean, inside:Boolean, a:int, b:int, c:int, ai:int, bi:int, bx:Number, by:Number, bz:Number; + for (var i:int = 0, k:int = 0, j:int, ni:int = 0, num1:int, num2:int = 0, v:int = numVertices, vi:int, t:Number, au:Number, av:Number; i < sourceIndicesLength; i = k) { + k = (num1 = sourceIndices[i++]) + i; + if (backfaceCulling == 1 && normals[ni++]*cameraX + normals[ni++]*cameraY + normals[ni++]*cameraZ <= normals[ni++]) continue; + if (backfaceCulling == 0) { + var ax:Number = cameraVertices[vi = int(sourceIndices[i]*3)], ay:Number = cameraVertices[++vi], az:Number = cameraVertices[++vi], abx:Number = cameraVertices[vi = int(sourceIndices[int(i + 1)]*3)] - ax, aby:Number = cameraVertices[++vi] - ay, abz:Number = cameraVertices[++vi] - az, acx:Number = cameraVertices[vi = int(sourceIndices[int(i + 2)]*3)] - ax, acy:Number = cameraVertices[++vi] - ay, acz:Number = cameraVertices[++vi] - az; + if ((acz*aby - acy*abz)*ax + (acx*abz - acz*abx)*ay + (acy*abx - acx*aby)*az >= 0) continue; + } + // Отсечение + var insideNear:Boolean = true; + if (culling & 1) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] > near) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideNear = false; + infront = false, behind = false; + } + var insideFar:Boolean = true; + if (culling & 2) { + for (j = i; j < k; j++) if ((inside = cameraVertices[int(sourceIndices[j]*3 + 2)] < far) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideFar = false; + infront = false, behind = false; + } + var insideLeft:Boolean = true; + if (culling & 4) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideLeft = false; + infront = false, behind = false; + } + var insideRight:Boolean = true; + if (culling & 8) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3)] < cameraVertices[int(vi + 2)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideRight = false; + infront = false, behind = false; + } + var insideTop:Boolean = true; + if (culling & 16) { + for (j = i; j < k; j++) if ((inside = -cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideTop = false; + infront = false, behind = false; + } + var insideBottom:Boolean = true; + if (culling & 32) { + for (j = i; j < k; j++) if ((inside = cameraVertices[vi = int(sourceIndices[j]*3 + 1)] < cameraVertices[int(vi + 1)]) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) continue; else insideBottom = false; + } + // Полное вхождение + if (insideNear && insideFar && insideLeft && insideRight && insideTop && insideBottom) { + resultIndices[resultIndicesLength++] = num1; + for (j = i; j < k;) resultIndices[resultIndicesLength++] = sourceIndices[j++]; + } else { + // Заполняем полигон + for (j = i; j < k;) polygon[int(j - i)] = sourceIndices[j++]; + // Клипинг по ниар + if (!insideNear) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz > near && az <= near || bz <= near && az > near) t = (near - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = near, uvts[vi] = 0; + if (bz > near) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по фар + if (!insideFar) { + for (j = 1, a = c = polygon[0], ai = a*3, az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bz = cameraVertices[int(bi + 2)]; + if (bz <= far && az > far || bz > far && az <= far) t = (far - az)/(bz - az), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = far, uvts[vi] = 0; + if (bz <= far) polygon[num2++] = b; + a = b, ai = bi, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по левой стороне + if (!insideLeft) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) t = (ax + az)/(ax + az - bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по правой стороне + if (!insideRight) { + for (j = 1, a = c = polygon[0], ai = a*3, ax = cameraVertices[ai], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, bx = cameraVertices[bi], bz = cameraVertices[int(bi + 2)]; + if (bz > bx && az <= ax || bz <= bx && az > ax) t = (az - ax)/(az - ax + bx - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = ax + (bx - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = (ay = cameraVertices[int(ai + 1)]) + (cameraVertices[int(bi + 1)] - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > bx) polygon[num2++] = b; + a = b, ai = bi, ax = bx, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по верхней стороне + if (!insideTop) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) t = (ay + az)/(ay + az - by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > -by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Клипинг по нижней стороне + if (!insideBottom) { + for (j = 1, a = c = polygon[0], ai = a*3, ay = cameraVertices[int(ai + 1)], az = cameraVertices[int(ai + 2)]; j <= num1; j++) { + b = (j < num1) ? polygon[j] : c, bi = b*3, by = cameraVertices[int(bi + 1)], bz = cameraVertices[int(bi + 2)]; + if (bz > by && az <= ay || bz <= by && az > ay) t = (az - ay)/(az - ay + by - bz), polygon[num2++] = v, cameraVertices[vi = int(v++*3)] = (ax = cameraVertices[ai]) + (cameraVertices[bi] - ax)*t, uvts[vi++] = (au = uvts[ai]) + (uvts[bi] - au)*t, cameraVertices[vi] = ay + (by - ay)*t, uvts[vi++] = (av = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - av)*t, cameraVertices[vi] = az + (bz - az)*t, uvts[vi] = 0; + if (bz > by) polygon[num2++] = b; + a = b, ai = bi, ay = by, az = bz; + } + if (num2 == 0) continue; + num1 = num2, num2 = 0; + } + // Копирование полигона + resultIndices[resultIndicesLength++] = num1; + for (j = 0; j < num1;) resultIndices[resultIndicesLength++] = polygon[j++]; + } + } + } + + // Триангуляция полигонов для отрисовки + private function triangulate():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + for (var i:int = 0, k:int = 0, a:int, b:int; i < sourceIndicesLength;) { + if (i == k) k = sourceIndices[i++] + i, a = sourceIndices[i++], b = sourceIndices[i++]; + resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[i++]; + } + } + + private function sortTriangles():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, n:int, k:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) map[n = int(i/3)] = i, averageZ[n] = cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)] + cameraVertices[int(sourceIndices[i++]*3 + 2)]; + // Сортировка + sortAverageZ(n++); + // Перестановка граней по карте сортировки + for (i = 0; i < n;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i++]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + //flashSort(++n); + //for (i = n - 1; i >= 0;) resultIndices[resultIndicesLength++] = sourceIndices[k = map[i--]], resultIndices[resultIndicesLength++] = sourceIndices[++k], resultIndices[resultIndicesLength++] = sourceIndices[++k]; + } + + private function sortPolygons():void { + sourceIndices = resultIndices, sourceIndicesLength = resultIndicesLength, resultIndices = (sourceIndices == indices1) ? indices2 : indices1, resultIndicesLength = 0; + // Ищем средние Z и готовим карту сортировки + for (var i:int = 0, j:int, n:int = 0, num:int, k:int = 0, z:Number, a:int, b:int, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ; i < sourceIndicesLength;) { + if (i == k) map[n] = i, k = (num = sourceIndices[i++]) + i, z = 0; + z += cameraVertices[int(sourceIndices[i]*3 + 2)]; + if (++i == k) averageZ[n++] = z/num; + } + // Сортировка + sortAverageZ(n - 1); + // Перестановка граней по карте сортировки и триангуляция + for (i = 0; i < n; i++) for (j = map[i], k = sourceIndices[j] + ++j, a = sourceIndices[j++], b = sourceIndices[j++]; j < k;) resultIndices[resultIndicesLength++] = a, resultIndices[resultIndicesLength++] = b, resultIndices[resultIndicesLength++] = b = sourceIndices[j++]; + } + + public function flashSort(length:int):void { + var i:int = 0, j:int = 0, k:int = 0, t:int; + // Одна восьмая часть длины вектора + var len:int = length >> 3; + // Массив значений Z + var averageZ:Vector. = sortingAverageZ; + // Карта перестановок + var map:Vector. = sortingMap; + var mapIndex:int; + // Вспомогательный вектор + var ind:Vector. = new Vector.(len); + //if (ind.length < len) ind.length = len; + // Минимальное значение + var minValue:Number = averageZ[0]; + // Максимальное значение + var maxValue:Number = minValue; + // Индекс максимального элемента + var maxIndex:int = 0; + // Значение текущего элемента + var value:Number; + + // Нахождение минимального значения и индекса максимального элемента + for (i = 1; i < length; ++i) { + value = averageZ[i]; + if (value < minValue) minValue = value; + else if (value > maxValue) maxValue = value, maxIndex = i; + } + + // Если все элементы одинаковы + if (minValue == maxValue) return; + + // Классификация + var c1:Number = (len - 1)/(maxValue - minValue); + + for (i = 0; i < length; i++) { + k = c1*(averageZ[i] - minValue); + ind[k]++; + } + + for (k = 1; k < len; k++) { + t = k - 1; + ind[k] += ind[t]; + } + + // Обмен максимального и нулевого элемента + averageZ[maxIndex] = averageZ[0]; + averageZ[0] = maxValue; + + j = 0; + k = len - 1; + i = length - 1; + + var swap:Number; + var nmove:int = 0; + while (nmove < i) { + while (j > (ind[k] - 1)) { + k = c1*(averageZ[++j] - minValue); + } + + value = averageZ[j]; + + while (j != ind[k]) { + k = c1*(value - minValue); + t = ind[k] - 1; + swap = averageZ[t]; + averageZ[t] = value; + mapIndex = map[t]; + map[t] = map[j]; + map[j] = mapIndex; + value = swap; + ind[k]--; + nmove++; + } + } + + for(i = 1; i < length; i++) { + swap = averageZ[i]; + mapIndex = map[i]; + j = i - 1; + while(j >= 0 && averageZ[j] > swap) { + t = j + 1; + averageZ[t] = averageZ[j]; + map[t] = map[j]; + j--; + } + t = j + 1; + averageZ[t] = swap; + map[t] = mapIndex; + } + } + + private function sortAverageZ(r:int):void { + var l:int = 0; + var i:int, j:int, stack:Vector. = sortingStack, map:Vector. = sortingMap, averageZ:Vector. = sortingAverageZ, stackIndex:int, left:Number, median:Number, right:Number, mapIndex:int; + for (stack[0] = l, stack[1] = r, stackIndex = 2; stackIndex > 0;) { + j = r = stack[--stackIndex], i = l = stack[--stackIndex], median = averageZ[(r + l) >> 1]; + for (;i <= j;) { + for (;(left = averageZ[i]) > median; i++); + for (;(right = averageZ[j]) < median; j--); + if (i <= j) mapIndex = map[i], map[i] = map[j], map[j] = mapIndex, averageZ[i++] = right, averageZ[j--] = left; + } + if (l < j) stack[stackIndex++] = l, stack[stackIndex++] = j; + if (i < r) stack[stackIndex++] = i, stack[stackIndex++] = r; + } + } + + /*private function correctRedrawRegion(culling:int, camera:Camera3D):void { + var ix:int, iy:int, iz:int, verticesLength:int = cameraVertices.length, vi:int = resultIndices[1]*3; + var tx:Number = cameraVertices[vi++], ty:Number = cameraVertices[vi++], tz:Number = cameraVertices[vi]; + var x:Number, y:Number, z:Number, t:Number = 0.01, near:Number = camera.nearClipping, far:Number = camera.farClipping + t + t; + for (ix = 0, iy = 1, iz = 2; iz < verticesLength; ix += 3, iy += 3, iz += 3) { + x = cameraVertices[ix], y = cameraVertices[iy], z = cameraVertices[iz] + t; + if (z < near || z > far || z < -x || z < x || z < -y || z < y) cameraVertices[ix] = tx, cameraVertices[iy] = ty, cameraVertices[iz] = tz; + } + if (culling & 12) { + if (culling & 48) { + if (culling & 3) { + // xyz + } else { + // xy + } + } else { + if (culling & 3) { + // xz + + } else { + // x + + } + } + } else { + if (culling & 48) { + if (culling & 3) { + // yz + + } else { + // y + + } + } else { + if (culling & 3) { + // z + + } + } + } + }*/ + + // Коррекция области перерисовки + private function cropVertices(culling:int, camera:Camera3D):void { + // Коррекция ширины и высоты с учётом ошибки вычислений + var w:Number = camera.width/2 + 0.1, h:Number = camera.height/2 + 0.1, nearDist:Number = camera.nearClipping; + var i:int, j:int, projectedVerticesLength:int = projectedVertices.length, c:Number; + if (clipping == 1) { + if (culling & 1) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= nearDist) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } + } + } + } else { + if (culling & 1) { + if (culling & 12 && culling & 48) { + for (i = 0, j = 2; i < projectedVerticesLength; i++, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[++i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + if ((c = projectedVertices[++i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } else if (culling & 12) { + for (i = 0, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[i] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[int(i + 1)] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } + } else if (culling & 48) { + for (i = 1, j = 2; i < projectedVerticesLength; i += 2, j += 3) { + if (cameraVertices[j] <= 0) { + projectedVertices[int(i - 1)] = (cameraVertices[int(j - 2)] < 0) ? -w : w; + projectedVertices[i] = (cameraVertices[int(j - 1)] < 0) ? -h : h; + } else { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } + } + } else { + if (culling & 4 && culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -w) projectedVertices[i] = -w; + else if (c > w) projectedVertices[i] = w; + } + } else if (culling & 4) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -w) projectedVertices[i] = -w; + } else if (culling & 8) { + for (i = 0; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > w) projectedVertices[i] = w; + } + if (culling & 16 && culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) { + if ((c = projectedVertices[i]) < -h) projectedVertices[i] = -h; + else if (c > h) projectedVertices[i] = h; + } + } else if (culling & 16) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] < -h) projectedVertices[i] = -h; + } else if (culling & 32) { + for (i = 1; i < projectedVerticesLength; i += 2) if (projectedVertices[i] > h) projectedVertices[i] = h; + } + } + } + } + + alternativa3d function getMipTexture(camera:Camera3D, object:Object3D):BitmapData { + // Находим расстояние до объекта + cameraCenter[0] = cameraCenter[1] = cameraCenter[2] = 0; + object.cameraMatrix.transformVectors(cameraCenter, cameraCenter); + return mipMap.textures[mipMap.getLevel(cameraCenter[2], camera)]; + } + + /** + * Расчёт нормалей + * @param normalize Флаг нормализации + */ + public function calculateNormals(normalize:Boolean = false):void { + // Подготавливаем массив нормалей + if (normals == null) normals = new Vector.() else normals.length = numFaces << 2; + // Расчитываем нормали + for (var i:int = 0, j:int = 0, num:int = 3, vi:int, nl:Number, indicesLength:int = indices.length; i < indicesLength; i += num) { + if (poly) num = indices[i++]; + // Получаем координаты A + var ax:Number = vertices[vi = int(indices[i]*3)]; + var ay:Number = vertices[++vi]; + var az:Number = vertices[++vi]; + // Получаем вектор AB + var abx:Number = vertices[vi = int(indices[int(i + 1)]*3)] - ax; + var aby:Number = vertices[++vi] - ay; + var abz:Number = vertices[++vi] - az; + // Получаем вектор AC + var acx:Number = vertices[vi = int(indices[int(i + 2)]*3)] - ax; + var acy:Number = vertices[++vi] - ay; + var acz:Number = vertices[++vi] - az; + // Считаем нормаль + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + // Нормализуем + if (normalize && (nl = Math.sqrt(nx*nx + ny*ny + nz*nz)) > 0) { + nx /= nl; + ny /= nl; + nz /= nl; + } + // Сохраняем нормаль и смещение + normals[j++] = nx; + normals[j++] = ny; + normals[j++] = nz; + normals[j++] = ax*nx + ay*ny + az*nz; + } + } + + public function optimizeForDynamicBSP():void { + var i:int; + var j:int; + var k:int; + var n:int; + var infront:Boolean; + var behind:Boolean; + var num:int = 3; + var ni:int = 0; + var nj:int = 0; + var indicesLength:int = indices.length; + var splits:Vector. = sortingAverageZ; + var stack:Vector. = sortingStack; + var map:Vector. = sortingMap; + // Сбор сплитов + for (i = 0; i < numFaces; i++) { + var normalX:Number = normals[ni]; ni++; + var normalY:Number = normals[ni]; ni++; + var normalZ:Number = normals[ni]; ni++; + var offset:Number = normals[ni]; ni++; + splits[i] = 0; + for (j = 0, k = 0, n = 0; j < indicesLength;) { + if (j == k) { + if (n == i) { + map[i] = (i << 16) + j; + } + n++; + if (poly) { + num = indices[j]; j++; + k++; + } + k += num; + if (n - 1 == i) { + j = k; + continue; + } + behind = false; + infront = false; + } + var vi:int = indices[j]*3; + var x:Number = vertices[vi]; vi++; + var y:Number = vertices[vi]; vi++; + var z:Number = vertices[vi]; + var o:Number = x*normalX + y*normalY + z*normalZ - offset; + if (o < -threshold) { + behind = true; + if (infront) { + j = k - 1; + } + } else if (o > threshold) { + infront = true; + if (behind) { + j = k - 1; + } + } + j++; + if (j == k && behind && infront) { + splits[i]++; + } + } + } + // Сортировка по сплитам + stack[0] = 0; + stack[1] = numFaces - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + k = (r + l) >> 1; + var median:Number = splits[k]; + while (i <= j) { + var left:Number = splits[i]; + while (left > median) { + i++; + left = splits[i]; + } + var right:Number = splits[j]; + while (right < median) { + j--; + right = splits[j]; + } + if (i <= j) { + var mapIndex:int = map[i]; + map[i] = map[j]; + map[j] = mapIndex; + splits[i] = right; + i++; + splits[j] = left; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Перестановка + var newIndices:Vector. = new Vector.(indicesLength); + var newNormals:Vector. = new Vector.(numFaces << 2); + j = 0; + for (n = numFaces - 1; n >= 0; n--) { + k = map[n]; + ni = (k >> 16) << 2; + i = k & 0xFFFF; + if (poly) { + num = indices[i]; i++; + newIndices[j] = num; j++; + } + k = i + num; + for (; i < k; i++) { + newIndices[j] = indices[i]; j++; + } + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; ni++; + newNormals[nj] = normals[ni]; nj++; + } + indices = newIndices; + normals = newNormals; + } + + /** + * Расчёт локального BSP-дерева + * @param splitAnalysis Флаг сплит-анализа. + * Если он включен, дерево построится с наименьшим количеством распилов, но построение будет медленнее + */ + public function calculateBSP(splitAnalysis:Boolean = false):void { + bsp = null; + if (numFaces == 0) return; + // Подготовка к построению нового дерева + fragmentsLength = 0; + for (var i:int = 0, k:int = 0, ni:int = 0, num:int, indicesLength:int = indices.length; i < indicesLength; i++) { + if (i == k) k = (num = poly ? indices[i++] : 3) + i, fragments[fragmentsLength++] = (ni << 16) + num, ni += 4; + fragments[fragmentsLength++] = indices[i]; + } + // Построение дерева + bsp = split(0, fragmentsLength, splitAnalysis ? findSplitter(0, fragmentsLength) : 0, splitAnalysis); + } + + private function split(begin:int, end:int, splitter:int, splitAnalysis:Boolean):BSPNode { + // Построение ноды + var mi:int = fragments[splitter], ni:int = mi >> 16, num:int = mi & 0xFFFF; + var node:BSPNode = new BSPNode(); + node.normalX = normals[ni++], node.normalY = normals[ni++], node.normalZ = normals[ni++], node.offset = normals[ni]; + node.addFragment(fragments, splitter + 1, splitter + num + 1); + // Если в куче только сплиттер + if (end - begin == num + 1) return node; + // Подготовка к разделению + var reserve:int = end - begin + ((end - begin) >> 2); + var negativeBegin:int = fragmentsLength, negativeEnd:int = negativeBegin, positiveBegin:int = fragmentsLength + reserve, positiveEnd:int = positiveBegin; + if ((fragmentsLength = positiveBegin + reserve) > fragmentsRealLength) fragments.length = fragmentsRealLength = fragmentsLength; + // Перебираем грани + for (var i:int = begin, j1:int = negativeEnd, j2:int = positiveEnd, k:int = begin, vi:int = numVertices*3, infront:Boolean, behind:Boolean, t:Number, uv:Number; i < end;) { + if (i == k) { + // Пропуск сплиттера + if (i == splitter) { + i += (fragments[i] & 0xFFFF) + 1; + if (i == end) break; + } + // Подготовка к разбиению + mi = fragments[i], ni = mi >> 16, num = mi & 0xFFFF, k = num + ++i, j1++, j2++, infront = false, behind = false; + // Первая точка ребра + var a:int = fragments[int(k - 1)], ai:int = a*3; + var ax:Number = vertices[ai], ay:Number = vertices[int(ai + 1)], az:Number = vertices[int(ai + 2)]; + var ao:Number = ax*node.normalX + ay*node.normalY + az*node.normalZ - node.offset; + } + // Вторая точка ребра + var b:int = fragments[i], bi:int = b*3; + var bx:Number = vertices[bi], by:Number = vertices[int(bi + 1)], bz:Number = vertices[int(bi + 2)]; + var bo:Number = bx*node.normalX + by*node.normalY + bz*node.normalZ - node.offset; + // Рассечение ребра + if (ao < -threshold && bo > threshold || bo < -threshold && ao > threshold) t = ao/(ao - bo), vertices[vi] = ax + (bx - ax)*t, uvts[vi++] = (uv = uvts[ai]) + (uvts[bi] - uv)*t, vertices[vi] = ay + (by - ay)*t, uvts[vi++] = (uv = uvts[int(ai + 1)]) + (uvts[int(bi + 1)] - uv)*t, vertices[vi] = az + (bz - az)*t, uvts[vi++] = 0, fragments[j1++] = fragments[j2++] = numVertices++; + // Добавление точки + if (bo < -threshold) { + fragments[j1++] = b, behind = true; + } else if (bo > threshold) { + fragments[j2++] = b, infront = true; + } else { + fragments[j1++] = fragments[j2++] = b; + } + // Анализ разбиения + if (++i == k) { + if (infront && behind) { + // Фрагмент распилился + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1; + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2; + } else if (infront) { + // Фрагмент спереди + fragments[positiveEnd] = (ni << 16) + j2 - positiveEnd - 1, positiveEnd = j2, j1 = negativeEnd; + } else if (behind || node.normalX*normals[ni] + node.normalY*normals[int(ni + 1)] + node.normalZ*normals[int(ni + 2)] < 0) { + // Фрагмент сзади или противонаправлен ноде + fragments[negativeEnd] = (ni << 16) + j1 - negativeEnd - 1, negativeEnd = j1, j2 = positiveEnd; + } else { + // Фрагмент в плоскости ноды и сонаправлен с ней + node.addFragment(fragments, k - num, k); + j1 = negativeEnd, j2 = positiveEnd; + } + } else { + a = b, ai = bi, ax = bx, ay = by, az = bz, ao = bo; + } + } + // Разделение заднй части + if (negativeEnd > negativeBegin) node.negative = split(negativeBegin, negativeEnd, splitAnalysis ? findSplitter(negativeBegin, negativeEnd) : negativeBegin, splitAnalysis); + // Разделение передней части + if (positiveEnd > positiveBegin) node.positive = split(positiveBegin, positiveEnd, splitAnalysis ? findSplitter(positiveBegin, positiveEnd) : positiveBegin, splitAnalysis); + return node; + } + + private function findSplitter(begin:int, end:int):int { + var splitter:int, bestSplits:int = int.MAX_VALUE; + // Перебираем нормали + for (var i:int = begin, vi:int; i < end; i += (mi & 0xFFFF) + 1) { + var currentSplits:int = 0, mi:int = fragments[i], ni:int = mi >> 16, normalX:Number = normals[ni++], normalY:Number = normals[ni++], normalZ:Number = normals[ni++], offset:Number = normals[ni]; + // Перебираем точки граней + for (var j:int = begin, k:int = begin, num:int, infront:Boolean, behind:Boolean; j < end;) { + if (j == k) k = (fragments[j] & 0xFFFF) + ++j, infront = false, behind = false; + var o:Number = vertices[vi = int(fragments[j]*3)]*normalX + vertices[++vi]*normalY + vertices[++vi]*normalZ - offset; + if (o < -threshold) { + behind = true; + if (infront) j = k - 1; + } else if (o > threshold) { + infront = true; + if (behind) j = k - 1; + } + if (++j == k) { + if (behind && infront) { + currentSplits++; + if (currentSplits >= bestSplits) break; + } + } + } + // Если найдена плоскость лучше текущей + if (currentSplits < bestSplits) { + splitter = i, bestSplits = currentSplits; + // Если плоскость ничего не распиливает + if (bestSplits == 0) break; + } + } + return splitter; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = numVertices*3; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } +/* + public function calculateRadius(axis:Vector3D):Number { + var i:int, length:int = vertices.length, c:Number, radius:Number, maxRadius:Number; + if (axis == Vector3D.X_AXIS) { + + } else if (axis == Vector3D.Y_AXIS) { + + } else if (axis == Vector3D.Z_AXIS) { + for (i = 0; i < length; i++) { + if ((radius = (c = vertices[i++])*c + (c = vertices[i++])*c) > maxRadius) maxRadius = radius; + } + } + return Math.sqrt(maxRadius); + } +*/ + /** + * Копирование свойств другого меша. Осторожно, свойства будут иметь прямые ссылки на свойства копируемого меша. + * @param mesh Объект копирования + */ + public function copyFrom(mesh:Mesh):void { + + alpha = mesh.alpha; + blendMode = mesh.blendMode; + + poly = mesh.poly; + + numVertices = mesh.numVertices; + numFaces = mesh.numFaces; + vertices = mesh.vertices; + indices = mesh.indices; + uvts = mesh.uvts; + + texture = mesh.texture; + mipMap = mesh.mipMap; + + smooth = mesh.smooth; + repeatTexture = mesh.repeatTexture; + + backfaceCulling = mesh.backfaceCulling; + clipping = mesh.clipping; + sorting = mesh.sorting; + mipMapping = mesh.mipMapping; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + + normals = mesh.normals; + bsp = mesh.bsp; + } + + public function generateClass(className:String = "GeneratedMesh", packageName:String = "", textureName:String = null):String { + + var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["__AS3__.vec.Vector"] = true; + importSet["alternativa.engine3d.core.Mesh"] = true; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + + constructor += "\t\t\tnumVertices = " + numVertices +";\r"; + constructor += "\t\t\tnumFaces = " + numFaces +";\r"; + constructor += "\t\t\tvertices = Vector.(["; + var length:uint = numVertices*3; + var n:int = 0; + + var i:int; + + for (i = 0; i < length; i++) { + constructor += vertices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tindices = Vector.(["; + length = numFaces*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += indices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + + constructor += "\t\t\tuvts = Vector.(["; + length = numVertices*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvts[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + var embeds:String = ""; + if (textureName != null) { + importSet["flash.display.BitmapData"] = true; + var bmpName:String = textureName.charAt(0).toUpperCase() + textureName.substr(1); + embeds += "\t\t[Embed(source=\"" + textureName + "\")] private static const bmp" + bmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + textureName + ":BitmapData = new bmp" + bmpName + "().bitmapData;\r\r"; + constructor += "\t\t\ttexture = " + textureName + ";\r\r"; + } + + constructor += "\t\t\tclipping = " + clipping +";\r"; + constructor += "\t\t\trepeatTexture = " + (repeatTexture ? "true" : "false") +";\r\r"; + + constructor += "\t\t\tmatrix.rawData = Vector.([" + matrix.rawData + "]);\r"; + if (_boundBox != null) { + importSet["alternativa.engine3d.bounds.BoundBox"] = true; + constructor += "\t\t\t_boundBox = new BoundBox(" + _boundBox.minX + ", " + _boundBox.minY + ", " + _boundBox.minZ + ", " + _boundBox.maxX + ", " + _boundBox.maxY + ", " + _boundBox.maxZ + ");\r"; + } + + var imports:String = ""; + + var importArray:Array = new Array(); + for (var key:* in importSet) { + importArray.push(key); + } + importArray.sort(); + + var newLine:Boolean = false; + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer; + } + + // Объединение вершин + /** + * Объединение вершин с одинаковыми координатами + * @param distanceThreshold Погрешность, в пределах которой координаты считаются одинаковыми + * @param uvThreshold Погрешность, в пределах которой UV-координаты считаются одинаковыми + */ + public function weldVertices(distanceThreshold:Number = 0, uvThreshold:Number = 0):void { + var i:int, j:int, k:int, t:int; + + // Карта соответствий + var weld:Vector. = new Vector.(numVertices = vertices.length/3); + for (i = 0; i < numVertices; i++) weld[i] = i; + + // Ненужные индексы + var uselessIndices:Vector. = new Vector.(); + var numUselessIndices:uint = 0; + + // Сравнение вершин по координатам и UV + for (i = 0; i < numVertices - 1; i++) { + if (weld[i] == i) { + var ax:Number = vertices[k = i*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + for (j = i + 1; j < numVertices; j++) { + if (weld[j] == j) { + var bx:Number = vertices[k = j*3]; + var by:Number = vertices[k + 1]; + var bz:Number = vertices[k + 2]; + var bu:Number = uvts[k]; + var bv:Number = uvts[k + 1]; + if ((ax - bx <= distanceThreshold) && (ax - bx >= -distanceThreshold) && (ay - by <= distanceThreshold) && (ay - by >= -distanceThreshold) && (az - bz <= distanceThreshold) && (az - bz >= -distanceThreshold) && (au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + weld[j] = i; + uselessIndices[numUselessIndices++] = j; + } + } + } + } + } + + // Удаление ненужных вершин и UV + for (i = 0, j = 0; j < numVertices; j++) { + if (weld[j] == j) { + if (i != j) { + vertices[k = i*3] = vertices[t = j*3]; + vertices[k + 1] = vertices[t + 1]; + vertices[k + 2] = vertices[t + 2]; + uvts[k] = uvts[t]; + uvts[k + 1] = uvts[t + 1]; + } + i++; + } + } + vertices.length = i*3; + uvts.length = i*3; + numVertices = i; + + // Корректировка индексов + uselessIndices.sort(function compare(x:int, y:int):Number {return x - y;}); + var numIndices:int = indices.length; + for (i = 0, j = 0; i < numIndices; i++) { + j = (poly && i == j) ? (indices[i++] + i) : j; + k = t = weld[indices[i]]; + for (var u:int = 0; u < numUselessIndices; u++) { + if (k > uselessIndices[u]) { + t--; + } else { + break; + } + } + indices[i] = t; + } + } + + /** + * Объединение треугольников в многоугольники + * После вызова этого метода флаг poly становится true + * @param angleThreshold Допустимый угол в радианах между нормалями, чтобы считать, что объединяемые грани в одной плоскости + * @param uvThreshold Допустимая разница uv-координат, чтобы считать, что объединяемые грани состыковываются по UV + * @param convexThreshold Величина, уменьшающая допустимый угол между смежными рёбрами объединяемых граней + */ + public function convertToPoly(angleThreshold:Number = 0, uvThreshold:Number = 0, convexThreshold:Number = 0):void { + + if (poly) return; + + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + uvThreshold += digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + + var i:int, j:int, n:int, k:int, t:int; + + // Вспомогательные флаги + var contains:Vector. = new Vector.(numFaces = indices.length/3); + + // Расчёт нормалей и матриц uv-трансформации + var normals:Vector. = new Vector.(numFaces*3); + var matrices:Vector. = new Vector.(numFaces*8); + for (i = 0; i < numFaces; i++) { + // Нахождение нормали + var ax:Number = vertices[k = indices[t = i*3]*3]; + var ay:Number = vertices[k + 1]; + var az:Number = vertices[k + 2]; + var au:Number = uvts[k]; + var av:Number = uvts[k + 1]; + var abx:Number = vertices[k = indices[t + 1]*3] - ax; + var aby:Number = vertices[k + 1] - ay; + var abz:Number = vertices[k + 2] - az; + var abu:Number = uvts[k] - au; + var abv:Number = uvts[k + 1] - av; + var acx:Number = vertices[k = indices[t + 2]*3] - ax; + var acy:Number = vertices[k + 1] - ay; + var acz:Number = vertices[k + 2] - az; + var acu:Number = uvts[k] - au; + var acv:Number = uvts[k + 1] - av; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = Math.sqrt(nx*nx + ny*ny + nz*nz); + // Если грань не вырождена + if (nl > digitThreshold) { + contains[i] = 1; + // Нормализация и сохранение нормали + normals[t] = nx /= nl; + normals[t + 1] = ny /= nl; + normals[t + 2] = nz /= nl; + // Нахождение обратной матрицы грани + var det:Number = -nx*acy*abz + acx*ny*abz + nx*aby*acz - abx*ny*acz - acx*aby*nz + abx*acy*nz; + var ma:Number = (-ny*acz + acy*nz)/det; + var mb:Number = (nx*acz - acx*nz)/det; + var mc:Number = (-nx*acy + acx*ny)/det; + var md:Number = (ax*ny*acz - nx*ay*acz - ax*acy*nz + acx*ay*nz + nx*acy*az - acx*ny*az)/det; + var me:Number = (ny*abz - aby*nz)/det; + var mf:Number = (-nx*abz + abx*nz)/det; + var mg:Number = (nx*aby - abx*ny)/det; + var mh:Number = (nx*ay*abz - ax*ny*abz + ax*aby*nz - abx*ay*nz - nx*aby*az + abx*ny*az)/det; + // Умножение прямой uv-матрицы на обратную матрицу грани и сохранение матрицы uv-трансформации + matrices[t = i*8] = abu*ma + acu*me; + matrices[t + 1] = abu*mb + acu*mf; + matrices[t + 2] = abu*mc + acu*mg; + matrices[t + 3] = abu*md + acu*mh + au; + matrices[t + 4] = abv*ma + acv*me; + matrices[t + 5] = abv*mb + acv*mf; + matrices[t + 6] = abv*mc + acv*mg; + matrices[t + 7] = abv*md + acv*mh + av; + } + } + + // Разбиение граней на группы по углу, UV и соседству + var islands:Vector.>> = new Vector.>>(); + var numIslands:int = 0; + var island:Vector.>; + var islandLength:int; + var f:Vector., fLen:int, fi:int, fj:int; + var s:Vector., sLen:int, si:int, sj:int; + for (i = 0; i < numFaces; i++) { + if (contains[i] > 0) { + contains[i] = 0; + // Создание группы + island = new Vector.>(); + islands[numIslands] = island; + // Создание грани и добавление в группу + f = new Vector.(3); + f[0] = indices[k = i*3]; + f[1] = indices[k + 1]; + f[2] = indices[k + 2]; + island[0] = f; + islandLength = 1; + normals[t = numIslands++*3] = nx = normals[k = i*3]; + normals[t + 1] = ny = normals[k + 1]; + normals[t + 2] = nz = normals[k + 2]; + ma = matrices[k = i*8]; + mb = matrices[k + 1]; + mc = matrices[k + 2]; + md = matrices[k + 3]; + me = matrices[k + 4]; + mf = matrices[k + 5]; + mg = matrices[k + 6]; + mh = matrices[k + 7]; + // Перебор и дополнение группы + for (n = 0; n < islandLength; n++) { + f = island[n]; + var a1:int = f[0]; + var b1:int = f[1]; + var c1:int = f[2]; + for (j = i + 1; j < numFaces; j++) { + if (contains[j] > 0) { + // Если грани сонаправлены + if (nx*normals[k = j*3] + ny*normals[k + 1] + nz*normals[k + 2] >= angleThreshold) { + var a2:int = indices[k]; + var b2:int = indices[k + 1]; + var c2:int = indices[k + 2]; + // Если грани соседние + if ((k = (a1 == c2 && b1 == b2 || b1 == c2 && c1 == b2 || c1 == c2 && a1 == b2) ? a2 : ((a1 == a2 && b1 == c2 || b1 == a2 && c1 == c2 || c1 == a2 && a1 == c2) ? b2 : ((a1 == b2 && b1 == a2 || b1 == b2 && c1 == a2 || c1 == b2 && a1 == a2) ? c2 : -1))) >= 0) { + ax = vertices[k *= 3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + au = uvts[k]; + av = uvts[k + 1]; + var bu:Number = ma*ax + mb*ay + mc*az + md; + var bv:Number = me*ax + mf*ay + mg*az + mh; + // Если совпадают по UV + if ((au - bu <= uvThreshold) && (au - bu >= -uvThreshold) && (av - bv <= uvThreshold) && (av - bv >= -uvThreshold)) { + contains[j] = 0; + s = new Vector.(3); + s[0] = a2; + s[1] = b2; + s[2] = c2; + island[islandLength++] = s; + } + } + } + } + } + } + } + } + + poly = true; + numFaces = 0; + + // Объединение + var numIndices:int = 0; + var faces1:Vector.> = new Vector.>(); + var faces2:Vector.> = new Vector.>(); + for (n = 0; n < numIslands; n++) { + island = islands[n]; + islandLength = island.length; + nx = normals[k = n*3]; + ny = normals[k + 1]; + nz = normals[k + 2]; + // Дополнение вспомогательных списков, если нужно + for (i = faces1.length; i < islandLength; i++) { + faces1[i] = new Vector.(); + faces2[i] = new Vector.(); + } + var numFaces1:int = islandLength; + var numFaces2:int = 0; + // Копирование граней из группы в первый список + for (i = 0; i < islandLength; i++) { + f = island[i]; + fLen = f.length; + s = faces1[i]; + for (j = 0; j < fLen; j++) s[j] = f[j]; + s.length = fLen; + } + // Объединение + do { + // Подготовка к итерации + var weld:Boolean = false; + for (i = 0; i < numFaces1; i++) contains[i] = 1; + // Попытки объединить текущую грань со всеми следующими + for (i = 0; i < numFaces1; i++) { + if (contains[i] > 0) { + f = faces1[i]; + fLen = f.length; + for (j = i + 1; j < numFaces1; j++) { + if (contains[j] > 0) { + s = faces1[j]; + sLen = s.length; + // Проверка на соседство + for (fi = 0; fi < fLen; fi++) { + a1 = f[fi]; + b1 = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]; + for (si = 0; si < sLen; si++) { + a2 = s[si]; + b2 = s[sj = (si < sLen - 1) ? (si + 1) : 0]; + if (a1 == b2 && b1 == a2) break; + } + if (si < sLen) break; + } + // Если грань соседняя + if (fi < fLen) { + // Расширение граней объединеия + while (true) { + fj = (fj < fLen - 1) ? (fj + 1) : 0; + si = (si > 0) ? (si - 1) : (sLen - 1); + b2 = f[fj]; + c2 = s[si]; + if (b2 == c2) a2 = c2 else break; + } + while (true) { + sj = (sj < sLen - 1) ? (sj + 1) : 0; + fi = (fi > 0) ? (fi - 1) : (fLen - 1); + b1 = s[sj]; + c1 = f[fi]; + if (b1 == c1) a1 = c1 else break; + } + // Первый перегиб + ax = vertices[k = a1*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b1*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c1*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + var crx:Number = aby*acz - abz*acy; + var cry:Number = abz*acx - abx*acz; + var crz:Number = abx*acy - aby*acx; + var zeroCross:Boolean = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Второй перегиб + ax = vertices[k = a2*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = b2*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = c2*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + zeroCross = crx < digitThreshold && crx > -digitThreshold && cry < digitThreshold && cry > -digitThreshold && crz < digitThreshold && crz > -digitThreshold; + if (zeroCross && abx*acx + aby*acy + abz*acz > 0 || !zeroCross && nx*crx + ny*cry + nz*crz < 0) continue; + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; aby *= nl; abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; acy *= nl; acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Объединение + var fs:Vector. = faces2[numFaces2++]; + var fsLen:int = 0; + fs[fsLen++] = a2; + while (true) { + fs[fsLen++] = f[fj]; + if (fj != fi) fj = (fj < fLen - 1) ? (fj + 1) : 0 else break; + } + fs[fsLen++] = a1; + while (true) { + fs[fsLen++] = s[sj]; + if (sj != si) sj = (sj < sLen - 1) ? (sj + 1) : 0 else break; + } + fs.length = fsLen; + contains[j] = 0; + weld = true; + break; + } + } + } + // Если не было объединения + if (j == numFaces1) { + s = faces2[numFaces2++]; + for (fi = 0; fi < fLen; fi++) s[fi] = f[fi]; + s.length = fLen; + } + } + } + // Переброс списков + island = faces1; + faces1 = faces2; + numFaces1 = numFaces2; + faces2 = island; + numFaces2 = 0; + } while (weld); + // Запись индексов в полигональной форме + for (i = 0; i < numFaces1; i++) { + f = faces1[i]; + fLen = f.length; + indices[numIndices++] = fLen; + if (fLen > 3) { + // Определение наилучшей последовательности + var max:Number = -Number.MAX_VALUE; + for (fi = 0; fi < fLen; fi++) { + ax = vertices[k = f[fi]*3]; + ay = vertices[k + 1]; + az = vertices[k + 2]; + abx = vertices[k = f[fj = (fi < fLen - 1) ? (fi + 1) : 0]*3] - ax; + aby = vertices[k + 1] - ay; + abz = vertices[k + 2] - az; + acx = vertices[k = f[(fj < fLen - 1) ? (fj + 1) : 0]*3] - ax; + acy = vertices[k + 1] - ay; + acz = vertices[k + 2] - az; + crx = aby*acz - abz*acy; + cry = abz*acx - abx*acz; + crz = abx*acy - aby*acx; + nl = Math.sqrt(crx*crx + cry*cry + crz*crz); + if (nl > max) { + max = nl; + j = fi; + } + } + for (fi = j; fi < fLen; fi++) indices[numIndices++] = f[fi]; + for (fi = 0; fi < j; fi++) indices[numIndices++] = f[fi]; + } else { + for (fi = 0; fi < fLen; fi++) indices[numIndices++] = f[fi]; + } + numFaces++; + } + } + indices.length = numIndices; + } + + } +} + +import __AS3__.vec.Vector; + +class BSPNode { + + // Дочерние ноды + public var negative:BSPNode; + public var positive:BSPNode; + + // Нормаль и смещение + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + // Треугольники + public var triangles:Vector. = new Vector.(); + public var trianglesLength:uint = 0; + + // Полигоны + public var polygons:Vector. = new Vector.(); + public var polygonsLength:uint = 0; + + // Добавление фрагмента в ноду + public function addFragment(fragment:Vector., begin:int, end:int):void { + polygons[polygonsLength++] = end - begin; + var i:int = begin, a:int = polygons[polygonsLength++] = fragment[i++], b:int = polygons[polygonsLength++] = fragment[i++], c:int; + while (i < end) { + triangles[trianglesLength++] = a; + triangles[trianglesLength++] = b; + triangles[trianglesLength++] = c = polygons[polygonsLength++] = fragment[i++]; + b = c; + } + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Occluder.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Occluder.as new file mode 100644 index 0000000..09df737 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Occluder.as @@ -0,0 +1,412 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + import flash.geom.Utils3D; + + use namespace alternativa3d; + + /** + * Полигональный объект-перекрытие. + * Объекты, которые он перекрывает от видимости камеры, исключаются из отрисовки. + * Сам окклюдер не отрисовывается. + * Должен быть конвексным + */ + public class Occluder extends Object3D { + /** + * Режим представления полигонов. + * Если false, в indices записаны треугольники (тройки индексов). + * Если true, в indices записаны многоугольники в виде: количество вершин грани, индексы вершин + */ + public var poly:Boolean = false; + public var vertices:Vector.; + public var edges:Vector.; // Два индекса - вершины, два - грани + public var indices:Vector.; + public var normals:Vector.; + + // Отношение площади перекрытия к площади вьюпорта (0 - 1) + /** + * Минимальное отношение площади перекрытия окклюдером вьюпорта к площади вьюпорта (от 0 до 1) + * Если окклюдер перекрывает больше, он помещается в очередь и учитывается + * при дальнейшей отрисовке в пределах кадра, иначе игнорируется + */ + public var minSize:Number = 0; + + private const cameraVertices:Vector. = new Vector.(); + private const visibilityMap:Vector. = new Vector.; + + /** + * Коприрование геометрии меша + * @param mesh Объект копирования + * Меш, геометрия которого копируется, обязан быть конвексным, иначе окклюдер будет некорректно работать + */ + public function copyFrom(mesh:Mesh):void { + poly = mesh.poly; + vertices = mesh.vertices; + indices = mesh.indices; + normals = mesh.normals; + + matrix.identity(); + matrix.prepend(mesh.matrix); + if (_boundBox != null) { + _boundBox.copyFrom(mesh._boundBox); + } else { + _boundBox = mesh._boundBox; + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + var v:Vector. = vertices; + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(vertices, cameraVertices); + v = cameraVertices; + } + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Ищем баунд-бокс + for (var i:int = 0, length:int = vertices.length; i < length;) { + boundBox.addPoint(v[i++], v[i++], v[i++]); + } + return boundBox; + } + + /** + * Расчёт рёбер по имеющимся вершинам и граням + */ + public function calculateEdges():void { + // Подготавливаем массив рёбер + if (edges == null) edges = new Vector.(); + + // Собираем рёбра + for (var i:int = 0, j:int = 0, n:int = 0, k:int = 0, a:int, b:int, length:int = indices.length; i < length;) { + if (i == k) { + k = poly ? (indices[i++] + i) : (i + 3); + a = indices[int(k - 1)]; + } + b = indices[i]; + edges[j++] = a; + edges[j++] = b; + edges[j++] = n; + edges[j++] = -1; + if (++i == k) n++; else a = b; + } + edges.length = j; + + // Убираем дубли + length = j, i = 0; k = 0; + var ac:int, bc:int; + while (i < length) { + if ((a = edges[i++]) >= 0) { + b = edges[i++]; + edges[k++] = a; + edges[k++] = b; + edges[k++] = edges[i++]; + j = ++i; + while (j < length) { + ac = edges[j++]; + bc = edges[j++]; + if (ac == a && bc == b || ac == b && bc == a) { + edges[int(j - 2)] = -1; + edges[k] = edges[j]; + break; + } + j += 2; + } + k++; + } else i += 3; + } + edges.length = k; + } + + static private const inverseCameraMatrix:Matrix3D = new Matrix3D(); + static private const center:Vector. = new Vector.(3, true); + private var cameraX:Number; + private var cameraY:Number; + private var cameraZ:Number; + static private const projectedEdges:Vector. = new Vector.(); + static private const uvts:Vector. = new Vector.(); + static private const viewEdges:Vector. = new Vector.(); + static private const debugEdges:Vector. = new Vector.(); + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + // Перевод в координаты камеры + object.cameraMatrix.transformVectors(vertices, cameraVertices); + // Определение центра камеры в объекте + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (cameraInside) return; + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var planeOccluder:Vector., edgeOccluder:Vector., planeOccluderLength:int = 0, edgeOccluderLength:int = 0, viewEdgesLength:int = 0; + if (camera.occlusionPlanes.length > camera.numOccluders) { + planeOccluder = camera.occlusionPlanes[camera.numOccluders]; + edgeOccluder = camera.occlusionEdges[camera.numOccluders]; + } else { + planeOccluder = camera.occlusionPlanes[camera.numOccluders] = new Vector.(); + edgeOccluder = camera.occlusionEdges[camera.numOccluders] = new Vector.(); + } + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n] else bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + // Расчёт нормали плоскости отсечения + planeOccluder[planeOccluderLength++] = bz*ay - by*az, planeOccluder[planeOccluderLength++] = bx*az - bz*ax, planeOccluder[planeOccluderLength++] = by*ax - bx*ay; + // Сохранение рёбер + edgeOccluder[edgeOccluderLength++] = ax, edgeOccluder[edgeOccluderLength++] = ay, edgeOccluder[edgeOccluderLength++] = az, edgeOccluder[edgeOccluderLength++] = bx, edgeOccluder[edgeOccluderLength++] = by, edgeOccluder[edgeOccluderLength++] = bz; + } else i -= 2; + } + if (planeOccluderLength > 0) { + // Проверка размера на экране + if (minSize > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((edgeOccluder.length = edgeOccluderLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, edgeOccluder, projectedEdges, uvts); + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + var square:Number = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + if (square/(camera.viewSizeX*camera.viewSizeY*8) < minSize) return; + } + // Добавление окклюдера + camera.numOccluders++; + planeOccluder.length = planeOccluderLength; + edgeOccluder.length = edgeOccluderLength; + } else { + if (occludeAll) camera.numOccluders = 0, camera.occludedAll = true else return; + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var inDebug:int = camera.checkInDebug(this); + if (inDebug == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + + // Рёбра + if (inDebug & Debug.EDGES) { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + inverseCameraMatrix.identity(); + inverseCameraMatrix.prepend(object.cameraMatrix); + inverseCameraMatrix.invert(); + center[0] = center[1] = center[2] = 0; + inverseCameraMatrix.transformVectors(center, center); + cameraX = center[0], cameraY = center[1], cameraZ = center[2]; + // Расчёт карты видимости граней + for (var i:int = 0, n:int = 0, normalsLength:int = normals.length >> 2, cameraInside:Boolean = true, infront:Boolean; i < normalsLength;) visibilityMap[i++] = infront = normals[n++]*cameraX + normals[n++]*cameraY + normals[n++]*cameraZ > normals[n++], cameraInside &&= !infront; + // Если камера внутри окклюдера + if (!cameraInside) { + // Подготовка окклюдера в камере + var occludeAll:Boolean = true, culling:int = object.culling, direction:Boolean, ax:Number, ay:Number, az:Number, bx:Number, by:Number, bz:Number, t:Number; + var debugEdgesLength:int = 0, viewEdgesLength:int = 0; + for (i = edges.length - 1; i > 0;) { + if ((direction = visibilityMap[edges[i--]]) != visibilityMap[edges[i--]]) { + // Определение порядка вершин (против часовой) + if (direction) ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n], bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n] else bx = cameraVertices[n = int(edges[i--]*3)], by = cameraVertices[++n], bz = cameraVertices[++n], ax = cameraVertices[n = int(edges[i--]*3)], ay = cameraVertices[++n], az = cameraVertices[++n]; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz), ax = ax + (bx - ax)*t, ay = ay + (by - ay)*t, az = az + (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz), bx = ax + (bx - ax)*t, by = ay + (by - ay)*t, bz = az + (bz - az)*t; + } + occludeAll = false; + } + debugEdges[debugEdgesLength++] = ax; + debugEdges[debugEdgesLength++] = ay; + debugEdges[debugEdgesLength++] = az; + debugEdges[debugEdgesLength++] = bx; + debugEdges[debugEdgesLength++] = by; + debugEdges[debugEdgesLength++] = bz; + } else i -= 2; + } + if (debugEdgesLength > 0) { + // Проецирование рёбер контура + var projectedEdgesLength:int = projectedEdges.length = ((debugEdges.length = debugEdgesLength)/3) << 1; + Utils3D.projectVectors(camera.projectionMatrix, debugEdges, projectedEdges, uvts); + // Проверка размера на экране + var square:Number = Number.MAX_VALUE; + if (minSize > 0) { + // Клиппинг рамки вьюпорта + if (culling > 0) { + if (culling & 4) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (culling & 8) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 16) viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY, viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = -camera.viewSizeY; + if (culling & 32) viewEdges[viewEdgesLength++] = -camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY, viewEdges[viewEdgesLength++] = camera.viewSizeX, viewEdges[viewEdgesLength++] = camera.viewSizeY; + if (viewEdgesLength > 0) { + for (i = 0; i < projectedEdgesLength;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + var nx:Number = ay - by, ny:Number = bx - ax, no:Number = nx*ax + ny*ay; + for (var j:int = 0, j2:int = 0; j < viewEdgesLength;) { + ax = viewEdges[j++], ay = viewEdges[j++], bx = viewEdges[j++], by = viewEdges[j++], az = ax*nx + ay*ny - no, bz = bx*nx + by*ny - no; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz), viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz), viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = ax + (bx - ax)*t, viewEdges[j2++] = ay + (by - ay)*t; + } else { + viewEdges[j2++] = ax, viewEdges[j2++] = ay, viewEdges[j2++] = bx, viewEdges[j2++] = by; + } + } + } + viewEdgesLength = j2; + if (viewEdgesLength == 0) break; + } + } + } + // Нахождение площади перекрытия + square = 0; + for (i = 0, az = projectedEdges[i++], bz = projectedEdges[i++], i += 2; i < projectedEdgesLength;) ax = projectedEdges[i++] - az, ay = projectedEdges[i++] - bz, bx = projectedEdges[i++] - az, by = projectedEdges[i++] - bz, square += bx*ay - by*ax; + for (i = 0; i < viewEdgesLength;) ax = viewEdges[i++] - az, ay = viewEdges[i++] - bz, bx = viewEdges[i++] - az, by = viewEdges[i++] - bz, square += bx*ay - by*ax; + } + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + var color:int, thickness:Number; + if (square/(camera.viewSizeX*camera.viewSizeY*8) >= minSize) { + color = 0x0000FF, thickness = 3; + } else { + color = 0x0077AA, thickness = 1; + } + for (i = 0; i < projectedEdges.length;) { + ax = projectedEdges[i++], ay = projectedEdges[i++], bx = projectedEdges[i++], by = projectedEdges[i++]; + canvas.gfx.moveTo(ax, ay); + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.lineTo(ax + (bx - ax)*0.8, ay + (by - ay)*0.8); + canvas.gfx.lineStyle(thickness, 0xFF0000); + canvas.gfx.lineTo(bx, by); + } + for (i = 0; i < viewEdgesLength;) { + canvas.gfx.moveTo(viewEdges[i++], viewEdges[i++]); + canvas.gfx.lineTo(viewEdges[i++], viewEdges[i++]); + } + } else { + if (occludeAll) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(6, 0xFF0000); + canvas.gfx.moveTo(-camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, camera.viewSizeY); + canvas.gfx.lineTo(camera.viewSizeX, -camera.viewSizeY); + canvas.gfx.lineTo(-camera.viewSizeX, -camera.viewSizeY); + } + } + } + } + // Оси, центры, имена, баунды + if (inDebug & Debug.AXES) object.drawAxes(camera, canvas); + if (inDebug & Debug.CENTERS) object.drawCenter(camera, canvas); + if (inDebug & Debug.NAMES) object.drawName(camera, canvas); + if (inDebug & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Reference.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Reference.as new file mode 100644 index 0000000..e3611a7 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Reference.as @@ -0,0 +1,56 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Объект-ссылка. + * Может ссылаться на любой трёхмерный объект, в том числе контейнер с любой вложенностью или Reference. + * При отрисовке он отрисовывает вместо себя объект, + * на который ссылается, подставляя только свою трансформацию, alpha, blendMode, colorTransform и filters. + */ + public class Reference extends Object3D { + + /** + * Объект, который подставляется при отрисовке вместо себя + */ + public var referenceObject:Object3D; + + public function Reference(referenceObject:Object3D = null) { + this.referenceObject = referenceObject; + } + + override alternativa3d function get canDraw():Boolean { + return referenceObject.canDraw; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.draw(camera, object, parentCanvas); + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.debug(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return referenceObject.getGeometry(camera, object); + } + + override public function get boundBox():BoundBox { + return referenceObject.boundBox; + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + return referenceObject.calculateBoundBox(matrix, boundBox); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/SkeletalMesh.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/SkeletalMesh.as new file mode 100644 index 0000000..dbc9795 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/SkeletalMesh.as @@ -0,0 +1,148 @@ +package alternativa.engine3d.objects { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + use namespace alternativa3d; + + public class SkeletalMesh extends Mesh { + + alternativa3d var _numBones:uint = 0; + alternativa3d var bones:Vector. = new Vector.(); + private var weights:Vector.>; + private var originalVertices:Vector.; + private var originalBonesMatrices:Vector.; + private var boneVertices:Vector.; + + public function initBones():void { + // Инициализируем массив весов и сохраняем оригинальные матрицы костей + originalBonesMatrices = new Vector.(_numBones, true); + weights = new Vector.>(_numBones, true); + for (var j:int = 0; j < _numBones; j++) { + originalBonesMatrices[j] = new Matrix3D(); + originalBonesMatrices[j].prepend(bones[j].matrix); + originalBonesMatrices[j].invert(); + weights[j] = new Vector.(numVertices, true); + } + // Формируем вспомогательные массивы для вершин + originalVertices = new Vector.(numVertices*3, true); + boneVertices = new Vector.(numVertices*3, true); + + // Обрабатываем вершины + var v:Vector3D = new Vector3D(); + for (var i:int = 0; i < numVertices; i++) { + var k:int = i*3; + // Сохраняем оригинальные координаты вершин + originalVertices[k] = vertices[k]; + originalVertices[k + 1] = vertices[k + 1]; + originalVertices[k + 2] = vertices[k + 2]; + + // Находим веса для каждой кости + var sumWeight:Number = 0; + for (j = 0; j < _numBones; j++) { + // Находим расстояние от вершины до кости + var b1:Vector3D = bones[j].matrix.transformVector(new Vector3D()); + var b2:Vector3D = bones[j].matrix.transformVector(new Vector3D(0, 0, bones[j].length)); + v.x = originalVertices[k]; + v.y = originalVertices[k + 1]; + v.z = originalVertices[k + 2]; + var w:Number = 1 - distanceToBone(b1, b2, v)/bones[j].distance; + //trace(w); + w = (w > 0) ? w : 0; + weights[j][i] = w; + sumWeight += w; + } + + // Нормализуем веса + if (sumWeight > 0) { + for (j = 0; j < _numBones; j++) { + weights[j][i] /= sumWeight; + } + } else { + // Если вершина не относится ни к какой кости, помечаем + for (j = 0; j < _numBones; j++) { + weights[j][i] = -1; + } + } + + } + } + + private function distanceToBone(b1:Vector3D, b2:Vector3D, p:Vector3D):Number { + var v:Vector3D = b2.subtract(b1); + var w:Vector3D = p.subtract(b1); + + var c1:Number = w.dotProduct(v); + if ( c1 <= 0 ) + return Vector3D.distance(p, b1); + + var c2:Number = v.dotProduct(v); + if ( c2 <= c1 ) + return Vector3D.distance(p, b2); + + v.scaleBy(c1 / c2); + var Pb:Vector3D = b1.add(v); + return Vector3D.distance(p, Pb); + + + } + + private var m:Matrix3D = new Matrix3D(); + public function calculateBones():void { + // Обнуление координат + for (var i:int = 0; i < numVertices*3; i++) { + vertices[i] = 0; + } + // Добавление трансформации через кости + for (var j:int = 0; j < _numBones; j++) { + m.identity(); + m.prepend(bones[j].matrix); + m.prepend(originalBonesMatrices[j]); + m.transformVectors(originalVertices, boneVertices); + var boneWeights:Vector. = weights[j]; + for (i = 0; i < numVertices; i++) { + var weight:Number = boneWeights[i]; + var k1:int = i*3; + var k2:int = k1 + 1; + var k3:int = k1 + 2; + if (weight >= 0) { + vertices[k1] += boneVertices[k1]*weight; + vertices[k2] += boneVertices[k2]*weight; + vertices[k3] += boneVertices[k3]*weight; + } else { + vertices[k1] = originalVertices[k1]; + vertices[k2] = originalVertices[k2]; + vertices[k3] = originalVertices[k3]; + } + } + } + } + + public function addBone(bone:Bone):void { + bones[_numBones++] = bone; + } +/* + public function removeChild(bone:Bone):void { + var i:int = bones.indexOf(bone); + if (i < 0) throw new ArgumentError(); + children.splice(i, 1); + _numBones--; + var len:uint = drawChildren.length; + for (i = 0; i < len; i++) { + if (drawChildren[i] == bone) { + drawChildren.splice(i, 1); + break; + } + } + bone._parent = null; + bone.setStage(null); + } +*/ + + + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/Sprite3D.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..89ccafa --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,506 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Fragment; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.MipMap; + import alternativa.engine3d.core.Object3D; + + import flash.display.BitmapData; + import flash.geom.Matrix; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Плоский, всегда развёрнутый к камере трёхмерный объект + */ + public class Sprite3D extends Object3D { + public var texture:BitmapData; + public var smooth:Boolean = false; + /** + * Режим сортировки на случай конфликта + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + * 3 - построение динамического BSP при отрисовке + */ + public var sorting:int = 0; + /** + * Применение мипмаппинга + * 0 - без мипмаппинга + * 1 - мипмаппинг по удалённости от камеры. Требуется установка свойства mipMap + */ + public var mipMapping:int = 0; + public var mipMap:MipMap; + /** + * X точки привязки + */ + public var originX:Number = 0.5; + /** + * Y точки привязки + */ + public var originY:Number = 0.5; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 0; + /** + * Угол поворота в радианах в плоскости экрана + */ + public var rotation:Number = 0; + /** + * Зависимость размера на экране от удалённости от камеры + */ + public var perspectiveScale:Boolean = true; + + // Вспомогательные + static private const vertices:Vector. = new Vector.(); + static private const axes:Vector. = Vector.([0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1]); + static private const cameraAxes:Vector. = new Vector.(12, true); + static private const textureMatrix:Matrix = new Matrix(); + static private var drawTexture:BitmapData; + private var projectionX:Number; + private var projectionY:Number; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var verticesLength:int = calculateVertices(object, camera); + if (verticesLength > 0) { + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.gfx.beginBitmapFill(drawTexture, textureMatrix, false, smooth); + var x:Number = vertices[0]*projectionX; + var y:Number = vertices[1]*projectionY; + if (rotation == 0) { + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + canvas.gfx.moveTo(x, y); + for (var i:int = 3; i < verticesLength; i++) { + x = vertices[i]*projectionX; i++; + y = vertices[i]*projectionY; i++; + canvas.gfx.lineTo(x, y); + } + } + } + } + + override alternativa3d function debug(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var debugResult:int = camera.checkInDebug(this); + if (debugResult == 0) return; + var canvas:Canvas = parentCanvas.getChildCanvas(true, false); + var i:int, length:int, x:Number, y:Number, t:Number = 0.1; + // Рёбра + if (debugResult & Debug.EDGES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(0, 0xFFFFFF); + if (rotation == 0) { + x = vertices[0]*projectionX, y = vertices[1]*projectionY; + canvas.gfx.drawRect(x, y, vertices[6]*projectionX - x, vertices[7]*projectionY - y); + } else { + // Отрисовка + canvas.gfx.moveTo(vertices[length - 3]*projectionX, vertices[length - 2]*projectionY); + for (i = 0; i < length; i++) canvas.gfx.lineTo(vertices[i++]*projectionX, vertices[i++]*projectionY); + } + } + } + // Вершины + if (debugResult & Debug.VERTICES) { + if ((length = calculateVertices(object, camera)) > 0) { + if (canvas == null) canvas = parentCanvas.getChildCanvas(true, false); + canvas.gfx.lineStyle(); + if (rotation == 0) { + var x1:Number = vertices[0]*projectionX, y1:Number = vertices[1]*projectionY; + var x2:Number = vertices[6]*projectionX, y2:Number = vertices[7]*projectionY; + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y1, 2); + } + if (x1 > -camera.viewSizeX + t && x1 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x1, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y2 > -camera.viewSizeY + t && y2 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y2, 2); + } + if (x2 > -camera.viewSizeX + t && x2 < camera.viewSizeX - t && y1 > -camera.viewSizeY + t && y1 < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x2, y1, 2); + } + } else { + for (i = 0; i < length; i++) { + x = vertices[i++]*projectionX, y = vertices[i++]*projectionY; + if (x > -camera.viewSizeX + t && x < camera.viewSizeX - t && y > -camera.viewSizeY + t && y < camera.viewSizeY - t) { + canvas.gfx.beginFill(0xFFFF00); + canvas.gfx.drawCircle(x, y, 2); + } + } + } + } + } + // Оси, центры, имена, баунды + if (debugResult & Debug.AXES) object.drawAxes(camera, canvas); + if (debugResult & Debug.CENTERS) object.drawCenter(camera, canvas); + if (debugResult & Debug.NAMES) object.drawName(camera, canvas); + if (debugResult & Debug.BOUNDS) object.drawBoundBox(camera, canvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var verticesLength:int = calculateVertices(object, camera); + if (verticesLength > 0) { + var geometry:Geometry = Geometry.create(); + geometry.fragment = Fragment.create(); + geometry.numVertices = verticesLength/3; + geometry.fragment.num = geometry.numVertices; + geometry.verticesLength = verticesLength; + if (geometry.uvts.length < verticesLength) { + geometry.uvts.length = verticesLength; + } + for (var i:int = 0, j:int = 0; i < geometry.numVertices; i++) { + geometry.vertices[j] = vertices[j]; j++; + geometry.vertices[j] = vertices[j]; j++; + geometry.vertices[j] = vertices[j]; j++; + geometry.fragment.indices[i] = i; + } + geometry.cameraMatrix.identity(); + geometry.cameraMatrix.prepend(object.cameraMatrix); + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.texture = drawTexture; + geometry.repeatTexture = false; + geometry.smooth = smooth; + if (camera.debugMode) { + geometry.debugResult = camera.checkInDebug(this); + } + geometry.viewAligned = true; + geometry.textureMatrix.a = textureMatrix.a; + geometry.textureMatrix.b = textureMatrix.b; + geometry.textureMatrix.c = textureMatrix.c; + geometry.textureMatrix.d = textureMatrix.d; + geometry.textureMatrix.tx = textureMatrix.tx; + geometry.textureMatrix.ty = textureMatrix.ty; + geometry.projectionX = projectionX; + geometry.projectionY = projectionY; + return geometry; + } else { + return null; + } + } + + private function calculateVertices(object:Object3D, camera:Camera3D):int { + // Трансформация локальных осей в камеру + object.cameraMatrix.transformVectors(axes, cameraAxes); + var x:Number = cameraAxes[0]; + var y:Number = cameraAxes[1]; + var z:Number = cameraAxes[2]; + if (z <= camera.nearClipping || z >= camera.farClipping) return 0; + // Проекция + projectionX = camera.viewSizeX/z; + projectionY = camera.viewSizeY/z; + var projectionZ:Number = camera.focalLength/z; + // Нахождение среднего размера спрайта + var ax:Number = (cameraAxes[3] - x)/camera.perspectiveScaleX; + var ay:Number = (cameraAxes[4] - y)/camera.perspectiveScaleY; + var az:Number = cameraAxes[5] - z; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[6] - x)/camera.perspectiveScaleX; + ay = (cameraAxes[7] - y)/camera.perspectiveScaleY; + az = cameraAxes[8] - z; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = (cameraAxes[9] - x)/camera.perspectiveScaleX; + ay = (cameraAxes[10] - y)/camera.perspectiveScaleY; + az = cameraAxes[11] - z; + size += Math.sqrt(ax*ax + ay*ay + az*az); + size /= 3; + // Определение текстуры и коррекция размера + if (mipMapping == 0) { + drawTexture = texture; + } else { + var level:int = mipMap.getLevel(z/size, camera); + size *= Math.pow(2, level); + drawTexture = mipMap.textures[level]; + } + // Учёт флага масштабирования + if (!perspectiveScale) { + size /= projectionZ; + } + var x1:Number; + var y1:Number; + var x2:Number; + var y2:Number; + // Если не задано вращение + if (rotation == 0) { + // Размеры спрайта в матрице камеры + var cameraWidth:Number = size*drawTexture.width*camera.perspectiveScaleX; + var cameraHeight:Number = size*drawTexture.height*camera.perspectiveScaleY; + + // Расчёт вершин в матрице камеры + x1 = x - originX*cameraWidth; + y1 = y - originY*cameraHeight; + x2 = x1 + cameraWidth; + y2 = y1 + cameraHeight; + + // Отсечение по вьюпорту + if (object.culling > 0 && (x1 > z || y1 > z || x2 < -z || y2 < -z)) return 0; + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = size*projectionZ; + textureMatrix.b = textureMatrix.c = 0; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + // Подрезка + if (clipping == 2) { + if (x1 < -z) x1 = -z; + if (y1 < -z) y1 = -z; + if (x2 > z) x2 = z; + if (y2 > z) y2 = z; + } + + // Заполняем вершины + vertices[0] = x1; + vertices[1] = y1; + vertices[2] = z; + vertices[3] = x1; + vertices[4] = y2; + vertices[5] = z; + vertices[6] = x2; + vertices[7] = y2; + vertices[8] = z; + vertices[9] = x2; + vertices[10] = y1; + vertices[11] = z; + + return 12; + + } else { + + // Размер спрайта в камере без коррекции под FOV90 + var textureWidth:Number = drawTexture.width; + var textureHeight:Number = drawTexture.height; + + // Расчёт векторов ширины и высоты + var sin:Number = Math.sin(rotation)*size; + var cos:Number = Math.cos(rotation)*size; + var cameraWidthX:Number = cos*textureWidth*camera.perspectiveScaleX; + var cameraWidthY:Number = -sin*textureWidth*camera.perspectiveScaleY; + var cameraHeightX:Number = sin*textureHeight*camera.perspectiveScaleX; + var cameraHeightY:Number = cos*textureHeight*camera.perspectiveScaleY; + + // Заполняем вершины + var length:int = 12; + vertices[0] = x1 = x - originX*cameraWidthX - originY*cameraHeightX; + vertices[1] = y1 = y - originX*cameraWidthY - originY*cameraHeightY; + vertices[2] = z; + vertices[3] = x1 + cameraHeightX; + vertices[4] = y1 + cameraHeightY; + vertices[5] = z; + vertices[6] = x1 + cameraWidthX + cameraHeightX; + vertices[7] = y1 + cameraWidthY + cameraHeightY; + vertices[8] = z; + vertices[9] = x1 + cameraWidthX; + vertices[10] = y1 + cameraWidthY; + vertices[11] = z; + + if (object.culling > 0) { + // Отсечение по вьюпорту + var i:int, infront:Boolean, behind:Boolean, inside:Boolean; + var clipLeft:Boolean = false; + if (object.culling & 4) { + for (i = 0; i < length; i += 3) if ((inside = -vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipLeft = true; + infront = false; behind = false; + } + var clipRight:Boolean = false; + if (object.culling & 8) { + for (i = 0; i < length; i += 3) if ((inside = vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipRight = true; + infront = false; behind = false; + } + var clipTop:Boolean = false; + if (object.culling & 16) { + for (i = 1; i < length; i += 3) if ((inside = -vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipTop = true; + infront = false; behind = false; + } + var clipBottom:Boolean = false; + if (object.culling & 32) { + for (i = 1; i < length; i += 3) if ((inside = vertices[i] < z) && (infront = true) && behind || !inside && (behind = true) && infront) break; + if (behind) if (!infront) return 0; else clipBottom = true; + } + // Подрезка + if (clipping == 2) { + var n:int = 0, t:Number, bx:Number, by:Number; + if (clipLeft) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (-bx < z && -ax >= z || -bx >= z && -ax < z) { + t = (ax + z)/(ax - bx); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (-bx < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipRight) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (bx < z && ax >= z || bx >= z && ax < z) { + t = (z - ax)/(bx - ax); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (bx < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipTop) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (-by < z && -ay >= z || -by >= z && -ay < z) { + t = (ay + z)/(ay - by); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (-by < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + if (clipBottom) { + ax = x = vertices[0]; ay = y = vertices[1]; + for (i = 3; i <= length; i++) { + if (i < length) { + bx = vertices[i++]; by = vertices[i++]; + } else { + bx = x; by = y; + } + if (by < z && ay >= z || by >= z && ay < z) { + t = (z - ay)/(by - ay); + vertices[n++] = ax + (bx - ax)*t; + vertices[n++] = ay + (by - ay)*t; + vertices[n++] = z; + } + if (by < z) { + vertices[n++] = bx; vertices[n++] = by; vertices[n++] = z; + } + ax = bx; ay = by; + } + if (n == 0) return 0; + length = n; n = 0; + } + } + } + + // Подготовка матрицы отрисовки + textureMatrix.a = textureMatrix.d = cos*projectionZ; + textureMatrix.b = -sin*projectionZ; + textureMatrix.c = sin*projectionZ; + textureMatrix.tx = x1*projectionX; + textureMatrix.ty = y1*projectionY; + + return length; + + } + } + + override public function calculateBoundBox(matrix:Matrix3D = null, boundBox:BoundBox = null):BoundBox { + // Если указан баунд-бокс + if (boundBox != null) { + boundBox.infinity(); + } else { + boundBox = new BoundBox(); + } + // Расчёт локального радиуса + var t:BitmapData = (mipMapping == 0) ? texture : mipMap.textures[0]; + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*t.width; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*t.height; + var radius:Number = Math.sqrt(w*w + h*h); + // Если указана матрица трансформации, переводим + if (matrix != null) { + matrix.transformVectors(axes, cameraAxes); + var cz:Number = cameraAxes[2]; + var cx:Number = cameraAxes[0]; + var cy:Number = cameraAxes[1]; + // Нахождение среднего размера спрайта + var ax:Number = cameraAxes[3] - cx; + var ay:Number = cameraAxes[4] - cy; + var az:Number = cameraAxes[5] - cz; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[6] - cx; + ay = cameraAxes[7] - cy; + az = cameraAxes[8] - cz; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = cameraAxes[9] - cx; + ay = cameraAxes[10] - cy; + az = cameraAxes[11] - cz; + size = radius*(size + Math.sqrt(ax*ax + ay*ay + az*az))/3; + boundBox.setSize(cx - size, cy - size, cz - size, cx + size, cy + size, cz + size); + } else { + boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + return boundBox; + } + + public function copyFrom(source:Sprite3D):void { + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + originX = source.originX; + originY = source.originY; + smooth = source.smooth; + clipping = source.clipping; + rotation = source.rotation; + perspectiveScale = source.perspectiveScale; + texture = source.texture; + mipMapping = source.mipMapping; + mipMap = source.mipMap; + matrix.identity(); + matrix.append(source.matrix); + if (source.boundBox != null) { + if (boundBox == null) boundBox = new BoundBox(); + boundBox.copyFrom(source.boundBox); + } else boundBox = null; + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/WireBoundBox.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/WireBoundBox.as new file mode 100644 index 0000000..6eb3e1a --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/WireBoundBox.as @@ -0,0 +1,93 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireBoundBox extends Object3D { + + static private const cameraVertices:Vector. = new Vector.(24, true); + static private const projectedVertices:Vector. = new Vector.(16, true); + static private const uvts:Vector. = new Vector.(24, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + + cameraVertices[0] = _boundBox.minX; + cameraVertices[1] = _boundBox.minY; + cameraVertices[2] = _boundBox.minZ; + + cameraVertices[3] = _boundBox.minX; + cameraVertices[4] = _boundBox.minY; + cameraVertices[5] = _boundBox.maxZ; + + cameraVertices[6] = _boundBox.minX; + cameraVertices[7] = _boundBox.maxY; + cameraVertices[8] = _boundBox.minZ; + + cameraVertices[9] = _boundBox.minX; + cameraVertices[10] = _boundBox.maxY; + cameraVertices[11] = _boundBox.maxZ; + + cameraVertices[12] = _boundBox.maxX; + cameraVertices[13] = _boundBox.minY; + cameraVertices[14] = _boundBox.minZ; + + cameraVertices[15] = _boundBox.maxX; + cameraVertices[16] = _boundBox.minY; + cameraVertices[17] = _boundBox.maxZ; + + cameraVertices[18] = _boundBox.maxX; + cameraVertices[19] = _boundBox.maxY; + cameraVertices[20] = _boundBox.minZ; + + cameraVertices[21] = _boundBox.maxX; + cameraVertices[22] = _boundBox.maxY; + cameraVertices[23] = _boundBox.maxZ; + + object.cameraMatrix.transformVectors(cameraVertices, cameraVertices); + for (var i:int = 0; i < 8; i++) { + if (cameraVertices[int(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.moveTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[8], projectedVertices[9]); + canvas.gfx.moveTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[10], projectedVertices[11]); + canvas.gfx.moveTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[12], projectedVertices[13]); + canvas.gfx.moveTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[14], projectedVertices[15]); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/objects/WireQuad.as b/Alternativa3D7/7.2/alternativa/engine3d/objects/WireQuad.as new file mode 100644 index 0000000..7103d08 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/objects/WireQuad.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.objects { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Utils3D; + + use namespace alternativa3d; + + public class WireQuad extends Object3D { + + public var vertices:Vector.; + static private const cameraVertices:Vector. = new Vector.(12, true); + static private const projectedVertices:Vector. = new Vector.(8, true); + static private const uvts:Vector. = new Vector.(12, true); + public var thickness:Number = 0; + public var color:uint = 0xFFFFFF; + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + object.cameraMatrix.transformVectors(vertices, cameraVertices); + for (var i:int = 0; i < 4; i++) { + if (cameraVertices[(i*3 + 2)] <= 0) return; + } + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + // Проецируем точки + Utils3D.projectVectors(camera.projectionMatrix, cameraVertices, projectedVertices, uvts); + + // Отрисовка + canvas.gfx.lineStyle(thickness, color); + canvas.gfx.moveTo(projectedVertices[0], projectedVertices[1]); + canvas.gfx.lineTo(projectedVertices[2], projectedVertices[3]); + canvas.gfx.lineTo(projectedVertices[4], projectedVertices[5]); + canvas.gfx.lineTo(projectedVertices[6], projectedVertices[7]); + canvas.gfx.lineTo(projectedVertices[0], projectedVertices[1]); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/primitives/Box.as b/Alternativa3D7/7.2/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..314e821 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,233 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class Box extends Mesh { + + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + var hp:uint = heightSegments + 1; + + createEmptyGeometry((wp*(lp + hp) + lp*hp) << 1, (widthSegments*(lengthSegments + heightSegments) + lengthSegments*heightSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var hh:Number = height*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Нижняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = (widthSegments - x)*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = -hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + indices[f++] = x*lp + y; + + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y; + } else { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = (x + 1)*lp + y; + + indices[f++] = x*lp + y; + indices[f++] = x*lp + y + 1; + indices[f++] = (x + 1)*lp + y + 1; + } + } + } + } + var o:uint = wp*lp; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = hh; + + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + indices[f++] = o + x*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y; + + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + x*lp + y; + } else { + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + + indices[f++] = o + x*lp + y; + indices[f++] = o + (x + 1)*lp + y + 1; + indices[f++] = o + x*lp + y + 1; + } + } + } + } + o += wp*lp; + + // Передняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = -lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z; + + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + + indices[f++] = o + x*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + } + } + } + } + o += wp*hp; + + // Задняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v] = x*ws - wh; + uvts[v++] = (widthSegments - x)*wd; + vertices[v] = lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (x < widthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + x*hp + z + 1; + + indices[f++] = o + (x + 1)*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + x*hp + z; + } else { + indices[f++] = o + x*hp + z; + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + + indices[f++] = o + x*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z + 1; + indices[f++] = o + (x + 1)*hp + z; + } + } + } + } + o += wp*hp; + + // Левая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = -wh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v] = y*ls - lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + } + } + } + } + o += lp*hp; + + // Правая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v] = wh; + uvts[v++] = y*ld; + vertices[v] = y*ls - lh; + uvts[v++] = (heightSegments - z)*hd; + vertices[v++] = z*hs - hh; + + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + indices[f++] = o + y*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z; + + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + y*hp + z; + } else { + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + + indices[f++] = o + y*hp + z; + indices[f++] = o + (y + 1)*hp + z + 1; + indices[f++] = o + y*hp + z + 1; + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, -hh, wh, lh, hh); + + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D7/7.2/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..c479589 --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,244 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class GeoSphere extends Mesh { + + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + + const sections:uint = 20; + + var i:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + + var v:uint = 0; + + var f:uint = sections*segments*segments; + createEmptyGeometry(f/2 + 2, f); + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = radius; + + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*2*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = subz; + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + vertices[v++] = subrad*cos; + vertices[v++] = subrad*sin; + vertices[v++] = -subz; + } + + vertices[v++] = 0; + vertices[v++] = 0; + vertices[v++] = -radius; + + for (i = 1; i < 6; i++) { + v = interpolate(0, i, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i % 5 + 1, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i + 5, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, (i + 3) % 5 + 6, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i + 5, i % 5 + 6, segments, v); + } + for (i = 6; i < 11; i++) { + v = interpolate(11, i, segments, v); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, v); + } + } + + for (i = 0; i < numVertices; i++) { + var j:uint = i*3; + uvts[j] = Math.atan2(vertices[j + 1], vertices[j])/(Math.PI*2); + uvts[j] = 0.5 + (reverse ? -uvts[j] : uvts[j]); + uvts[j + 1] = 0.5 + Math.asin(vertices[j + 2]/radius)/Math.PI; + } + + var num:uint = 0; + for (f = 0; f <= sections - 1; f++) { + for (var row:uint = 0; row <= segments - 1; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + + if (reverse) { + indices[num++] = a; + indices[num++] = c; + indices[num++] = b; + } else { + indices[num++] = a; + indices[num++] = b; + indices[num++] = c; + } + + if (column < row) { + var d:uint = findVertices(segments, f, row, column + 1); + if (reverse) { + indices[num++] = a; + indices[num++] = d; + indices[num++] = c; + } else { + indices[num++] = a; + indices[num++] = c; + indices[num++] = d; + } + } + } + } + } + + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-radius, -radius, -radius, radius, radius, radius); + } + + private function interpolate(a:uint, b:uint, num:uint, v:uint):uint { + if (num < 2) { + return v; + } + a *= 3; + b *= 3; + var ax:Number = vertices[a]; + var ay:Number = vertices[a + 1]; + var az:Number = vertices[a + 2]; + var bx:Number = vertices[b]; + var by:Number = vertices[b + 1]; + var bz:Number = vertices[b + 2]; + var cos:Number = (ax*bx + ay*by + az*bz)/(ax*ax + ay*ay + az*az); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + vertices[v++] = (ax*st2 + bx*st1)/sin; + vertices[v++] = (ay*st2 + by*st1)/sin; + vertices[v++] = (az*st2 + bz*st1)/sin; + } + return v; + } + + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + } +} diff --git a/Alternativa3D7/7.2/alternativa/engine3d/primitives/Plane.as b/Alternativa3D7/7.2/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..4719aba --- /dev/null +++ b/Alternativa3D7/7.2/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,67 @@ +package alternativa.engine3d.primitives { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.BoundBox; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * + */ + public class Plane extends Mesh { + + /** + * + * @param width + * @param length + * @param widthSegments + * @param lengthSegments + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1) { + + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + + createEmptyGeometry(wp*lp, (widthSegments*lengthSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = 0; + + if (x < widthSegments && y < lengthSegments) { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + } + } + } + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, 0, wh, lh, 0); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/Alternativa3D.as b/Alternativa3D7/7.3/alternativa/Alternativa3D.as new file mode 100644 index 0000000..b476703 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "7.0.0"; + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/alternativa3d.as b/Alternativa3D7/7.3/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/Animation.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/Animation.as new file mode 100644 index 0000000..b5b5410 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/Animation.as @@ -0,0 +1,58 @@ +package alternativa.engine3d.animation { + import flash.utils.getTimer; + + public class Animation { + + protected var _position:Number = 0; + private var _playing:Boolean = false; + private var time:int; + + /** + * ������ ������� ������������ �������� + */ + public function play():void { + time = getTimer(); + _playing = true; + } + + /** + * ��������� ������� �������� + */ + public function stop():void { + _playing = false; + } + + /** + * ���������� �������� ����������� ������ � ������������ � �������� � ������� �������� + */ + public function update():void { + if (_playing) { + var t:int = getTimer(); + position += (t - time)*0.001; + time = t; + control(); + + } + } + + public function get position():Number { + return _position; + } + + public function set position(value:Number):void { + _position = value; + control(); + } + + protected function control():void { + } + + /** + * ��������� �������� + * @return ������������� ��� ��� + */ + public function get playing():Boolean { + return _playing; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/ComplexAnimation.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/ComplexAnimation.as new file mode 100644 index 0000000..9532971 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/ComplexAnimation.as @@ -0,0 +1,58 @@ +package alternativa.engine3d.animation { + public class ComplexAnimation extends Animation { + + private var _numAnimations:int; + private var animations:Vector.; + + public function ComplexAnimation() { + animations = new Vector.(); + } + + public function addAnimation(animation:Animation):Animation { + if (animation == null) { + throw new Error("Animation cannot be null"); + } + animations[_numAnimations++] = animation; + return animation; + } + + public function removeAnimation(animation:Animation):Animation { + var index:int = animations.indexOf(animation); + if (index < 0) throw new ArgumentError("Animation not found"); + _numAnimations--; + var j:int = index + 1; + while (index < _numAnimations) { + animations[index] = animations[j]; + index++; + j++; + } + animations.length = _numAnimations; + return animation; + } + + public function get numAnimations():int { + return _numAnimations; + } + + public function getAnimationAt(index:int):Animation { + return animations[index]; + } + + override public function update():void { + super.update(); + for (var i:int = 0; i < _numAnimations; i++) { + var animation:Animation = animations[i]; + animation.update(); + } + } + + override public function set position(value:Number):void { + super.position = value; + for (var i:int = 0; i < _numAnimations; i++) { + var animation:Animation = animations[i]; + animation.position = value; + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/Key.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/Key.as new file mode 100644 index 0000000..8aedf3f --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/Key.as @@ -0,0 +1,21 @@ +package alternativa.engine3d.animation { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Key { + // ����� � ������������� + public var time:Number; + // ��������� ���� + public var next:Key; + + public function Key(time:Number) { + this.time = time; + } + + alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + return key; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixAnimation.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixAnimation.as new file mode 100644 index 0000000..0314b5d --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixAnimation.as @@ -0,0 +1,16 @@ +package alternativa.engine3d.animation { + public class MatrixAnimation extends ObjectAnimation { + + public var matrix:Track; + + private var matrixKey:MatrixKey = new MatrixKey(0, null); + + override protected function control():void { + if (matrix != null) { + matrix.getKey(_position, matrixKey); + object.setMatrix(matrixKey.matrix); + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixKey.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixKey.as new file mode 100644 index 0000000..4d5165c --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/MatrixKey.as @@ -0,0 +1,33 @@ +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + public class MatrixKey extends Key { + + public var matrix:Matrix3D; + + public function MatrixKey(time:Number, matrix:Matrix3D) { + super(time); + this.matrix = matrix; + } + + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + if (key != null) { + key.time = time; + MatrixKey(key).matrix = matrix; + return key; + } else { + return new MatrixKey(time, matrix); + } + } + + public function toString():String { + return "[MatrixKey " + time + ":" + matrix.rawData + "]"; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/ObjectAnimation.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/ObjectAnimation.as new file mode 100644 index 0000000..de439c9 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/ObjectAnimation.as @@ -0,0 +1,9 @@ +package alternativa.engine3d.animation { + import alternativa.engine3d.core.Object3D; + + public class ObjectAnimation extends Animation { + + public var object:Object3D; + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/PointKey.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/PointKey.as new file mode 100644 index 0000000..894b3e1 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/PointKey.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.animation { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class PointKey extends Key { + public var x:Number; + public var y:Number; + public var z:Number; + + public function PointKey(time:Number, x:Number, y:Number, z:Number) { + super(time); + this.x = x; + this.y = y; + this.z = z; + } + + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + var x:Number; + var y:Number; + var z:Number; + if (next != null) { + var t:Number = (time - this.time)/(next.time - this.time); + x = this.x + (PointKey(next).x - this.x)*t; + y = this.y + (PointKey(next).y - this.y)*t; + z = this.z + (PointKey(next).z - this.z)*t; + } else { + x = this.x; + y = this.y; + z = this.z; + } + if (key != null) { + key.time = time; + PointKey(key).x = x; + PointKey(key).y = y; + PointKey(key).z = z; + return key; + } else { + return new PointKey(time, x, y, z); + } + } + + public function toString():String { + return "[PointKey " + time.toFixed(3) + ":" + x + "," + y + "," + z + "]"; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/Track.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/Track.as new file mode 100644 index 0000000..f58dd05 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/Track.as @@ -0,0 +1,89 @@ +package alternativa.engine3d.animation { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Track { + + public var keyList:Key; + + public function addKey(key:Key):void { + key.next = keyList; + keyList = key; + } + + public function sortKeys():void { + keyList = sortKeysByTime(keyList); + } + + public function getKey(time:Number, key:Key = null):Key { + var prev:Key; + var next:Key = keyList; + while (next != null && next.time < time) { + prev = next; + next = next.next; + } + if (prev != null) return prev.interpolate(time, next, key); + if (next != null) return next.interpolate(time, null, key); + return null; + } + + private function sortKeysByTime(list:Key):Key { + var left:Key = list; + var right:Key = list.next; + while (right != null && right.next != null) { + list = list.next; + right = right.next.next; + } + right = list.next; + list.next = null; + if (left.next != null) { + left = sortKeysByTime(left); + } + if (right.next != null) { + right = sortKeysByTime(right); + } + var flag:Boolean = left.time < right.time; + if (flag) { + list = left; + left = left.next; + } else { + list = right; + right = right.next; + } + var last:Key = list; + while (true) { + if (left == null) { + last.next = right; + return list; + } else if (right == null) { + last.next = left; + return list; + } + if (flag) { + if (left.time < right.time) { + last = left; + left = left.next; + } else { + last.next = right; + last = right; + right = right.next; + flag = false; + } + } else { + if (right.time < left.time) { + last = right; + right = right.next; + } else { + last.next = left; + last = left; + left = left.next; + flag = true; + } + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/TransformAnimation.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/TransformAnimation.as new file mode 100644 index 0000000..465825d --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/TransformAnimation.as @@ -0,0 +1,83 @@ +package alternativa.engine3d.animation { + public class TransformAnimation extends ObjectAnimation { + + public var translation:Track; + public var rotation:Track; + public var scale:Track; + + public var x:Track; + public var y:Track; + public var z:Track; + + public var rotationX:Track; + public var rotationY:Track; + public var rotationZ:Track; + + public var scaleX:Track; + public var scaleY:Track; + public var scaleZ:Track; + + private var valueKey:ValueKey = new ValueKey(0, 0); + private var pointKey:PointKey = new PointKey(0, 0, 0, 0); + + override protected function control():void { + if (translation != null) { + translation.getKey(_position, pointKey); + object.x = pointKey.x; + object.y = pointKey.y; + object.z = pointKey.z; + } else { + if (x != null) { + x.getKey(_position, valueKey); + object.x = valueKey.value; + } + if (y != null) { + y.getKey(_position, valueKey); + object.y = valueKey.value; + } + if (z != null) { + z.getKey(_position, valueKey); + object.z = valueKey.value; + } + } + if (rotation != null) { + rotation.getKey(_position, pointKey); + object.rotationX = pointKey.x; + object.rotationY = pointKey.y; + object.rotationZ = pointKey.z; + } else { + if (rotationX != null) { + rotationX.getKey(_position, valueKey); + object.rotationX = valueKey.value; + } + if (rotationY != null) { + rotationY.getKey(_position, valueKey); + object.rotationY = valueKey.value; + } + if (rotationZ != null) { + rotationZ.getKey(_position, valueKey); + object.rotationZ = valueKey.value; + } + } + if (scale != null) { + scale.getKey(_position, pointKey); + object.scaleX = pointKey.x; + object.scaleY = pointKey.y; + object.scaleZ = pointKey.z; + } else { + if (scaleX != null) { + scaleX.getKey(_position, valueKey); + object.scaleX = valueKey.value; + } + if (scaleY != null) { + scaleY.getKey(_position, valueKey); + object.scaleY = valueKey.value; + } + if (scaleZ != null) { + scaleZ.getKey(_position, valueKey); + object.scaleZ = valueKey.value; + } + } + } + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/animation/ValueKey.as b/Alternativa3D7/7.3/alternativa/engine3d/animation/ValueKey.as new file mode 100644 index 0000000..dcb81c4 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/animation/ValueKey.as @@ -0,0 +1,34 @@ +package alternativa.engine3d.animation { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class ValueKey extends Key { + public var value:Number; + + public function ValueKey(time:Number, value:Number) { + super(time); + this.value = value; + } + + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + var value:Number; + if (next != null) { + value = this.value + (ValueKey(next).value - this.value)*(time - this.time)/(next.time - this.time); + } else { + value = this.value; + } + if (key != null) { + key.time = time; + ValueKey(key).value = value; + return key; + } else { + return new ValueKey(time, value); + } + } + + public function toString():String { + return "[ValueKey " + time + ":" + value + "]"; + } + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/containers/ConflictContainer.as b/Alternativa3D7/7.3/alternativa/engine3d/containers/ConflictContainer.as new file mode 100644 index 0000000..a93561e --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/containers/ConflictContainer.as @@ -0,0 +1,1079 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class ConflictContainer extends Object3DContainer { + + public var resolveByAABB:Boolean = true; + public var resolveByOOBB:Boolean = true; + + //public var isolateAABBConflicts:Boolean = false; + //public var isolateOOBBConflicts:Boolean = false; + + public var threshold:Number = 0.1; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + // Если есть видимая геометрия + if (geometry != null) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Если объектов несколько + if (geometry.next != null) { + var current:Geometry; + // Расчёт инверсной матрицы камеры и позиции камеры в контейнере + calculateInverseMatrix(object); + // AABB + if (resolveByAABB) { + for (current = geometry; current != null; current = current.next) { + current.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + } + drawAABBGeometry(camera, object, canvas, geometry); + // OOBB + } else if (resolveByOOBB) { + for (current = geometry; current != null; current = current.next) { + current.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } else { + geometry.draw(camera, object, canvas, threshold); + geometry.destroy(); + } + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawAABBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var coord:Number; + var coordMin:Number; + var coordMax:Number; + var axisX:Boolean; + var axisY:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + // Поиск сплита + while (current != null) { + // Сплиты по оси X + coord = current.boundMinX; + coordMin = coord - threshold; + coordMax = coord + threshold; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxX <= coordMax) { + outside = true; + } else if (compared.boundMinX < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + coord = current.boundMaxX; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinX >= coordMin) { + outside = true; + } else if (compared.boundMaxX > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + // Сплиты по оси Y + coord = current.boundMinY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxY <= coordMax) { + outside = true; + } else if (compared.boundMinY < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + coord = current.boundMaxY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinY >= coordMin) { + outside = true; + } else if (compared.boundMaxY > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + // Сплиты по оси Z + coord = current.boundMinZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxZ <= coordMax) { + outside = true; + } else if (compared.boundMinZ < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + coord = current.boundMaxZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinZ >= coordMin) { + outside = true; + } else if (compared.boundMaxZ > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + while (geometry != null) { + next = geometry.next; + var min:Number = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + var max:Number = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max > coordMax) { + geometry.next = positive; + positive = geometry; + } else if (min < coordMin) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Определение положения камеры + if (axisX && imd > coord || axisY && imh > coord || !axisX && !axisY && iml > coord) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else if (resolveByOOBB) { + for (current = geometry; current != null; current = current.next) { + current.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawOOBBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var vertex:Vertex; + var plane:Vertex; + var wrapper:Wrapper; + var o:Number; + var planeX:Number; + var planeY:Number; + var planeZ:Number; + var planeOffset:Number; + var behind:Boolean; + var infront:Boolean; + var current:Geometry; + var compared:Geometry; + // Поиск сплита + for (current = geometry; current != null; current = current.next) { + if (current.viewAligned) { + planeOffset = current.ml; + for (compared = geometry; compared != null; compared = compared.next) { + if (!compared.viewAligned) { + behind = false; + infront = false; + // Перебор точек + for (vertex = compared.boundVertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ > planeOffset) { + if (behind) { + break; + } else { + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (vertex != null) break; + } + } + // Если не встретилось препятствий + if (compared == null) break; + } else { + // Перебор плоскостей + for (plane = current.boundPlaneList; plane != null; plane = plane.next) { + planeX = plane.cameraX; + planeY = plane.cameraY; + planeZ = plane.cameraZ; + planeOffset = plane.offset; + var outside:Boolean = false; + for (compared = geometry; compared != null; compared = compared.next) { + if (current != compared) { + behind = false; + infront = false; + // Перебор точек + if (compared.viewAligned) { + for (wrapper = compared.faceStruct.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ >= planeOffset - threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + if (wrapper != null) break; + } else { + for (vertex = compared.boundVertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ >= planeOffset - threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + if (vertex != null) break; + } + } + } + // Если не встретилось препятствий и есть объекты по обе стороны + if (compared == null && outside) break; + } + // Если найдена разделяющая плоскость + if (plane != null) break; + } + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (current.viewAligned) { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + o = geometry.ml - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else { + for (vertex = geometry.boundVertexList; vertex != null; vertex = vertex.next) { + o = vertex.cameraZ - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + break; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + break; + } + } + if (vertex == null) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } else { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + for (wrapper = geometry.faceStruct.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + o = vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (wrapper == null) { + geometry.next = middle; + middle = geometry; + } + } else { + for (vertex = geometry.boundVertexList; vertex != null; vertex = vertex.next) { + o = vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (vertex == null) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } + // Определение положения камеры + if (current.viewAligned || planeOffset < 0) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawConflictGeometry(camera:Camera3D, object:Object3D, parentCanvas:Canvas, geometry:Geometry):void { + var canvas:Canvas; + var face:Face; + var next:Face; + var nextGeometry:Geometry; + // Геометрия с сортировкой предрасчитанное BSP + var bspGeometry:Geometry; + // Геометрия, которая присутствует в конфликте + var conflict:Geometry; + // Грани с сортировкой динамическое BSP + var dynamicBSPFirst:Face; + var dynamicBSPLast:Face; + // Грани с сортировкой по средним Z + var averageZFirst:Face; + var averageZLast:Face; + // Перебор геометрических объектов + for (; geometry != null; geometry = nextGeometry) { + nextGeometry = geometry.next; + // Сортировка по предрасчитанному BSP + if (geometry.sorting == 3) { + geometry.next = bspGeometry; + bspGeometry = geometry; + } else { + // Сортировка по динамическому BSP + if (geometry.sorting == 2) { + if (dynamicBSPFirst != null) { + dynamicBSPLast.processNext = geometry.faceStruct; + } else { + dynamicBSPFirst = geometry.faceStruct; + } + dynamicBSPLast = geometry.faceStruct; + dynamicBSPLast.geometry = geometry; + while (dynamicBSPLast.processNext != null) { + dynamicBSPLast = dynamicBSPLast.processNext; + dynamicBSPLast.geometry = geometry; + } + // Сортировка по средним Z + } else { + if (averageZFirst != null) { + averageZLast.processNext = geometry.faceStruct; + } else { + averageZFirst = geometry.faceStruct; + } + averageZLast = geometry.faceStruct; + averageZLast.geometry = geometry; + while (averageZLast.processNext != null) { + averageZLast = averageZLast.processNext; + averageZLast.geometry = geometry; + } + } + geometry.faceStruct = null; + geometry.next = conflict; + conflict = geometry; + } + } + // Соединение списков + if (conflict != null) { + geometry = conflict; + while (geometry.next != null) { + geometry = geometry.next; + } + geometry.next = bspGeometry; + } else { + conflict = bspGeometry; + } + // Сбор первоначальной кучи граней + var list:Face; + if (dynamicBSPFirst != null) { + list = dynamicBSPFirst; + dynamicBSPLast.processNext = averageZFirst; + } else { + list = averageZFirst; + } + // Если есть статические BSP + if (bspGeometry != null) { + // Встройка кучи в первый bsp с внутренней сортировкой + bspGeometry.faceStruct.geometry = bspGeometry; + list = collectNode(bspGeometry.faceStruct, list, camera, threshold, true); + bspGeometry.faceStruct = null; + // Встройка кучи в остальные bsp без внутренней сортировки + for (bspGeometry = bspGeometry.next; bspGeometry != null; bspGeometry = bspGeometry.next) { + bspGeometry.faceStruct.geometry = bspGeometry; + list = collectNode(bspGeometry.faceStruct, list, camera, threshold, false); + bspGeometry.faceStruct = null; + } + // Если есть динамические BSP + } else if (dynamicBSPFirst != null) { + list = collectNode(null, list, camera, threshold, true); + // Если есть сортировка по средним Z + } else if (averageZFirst != null) { + list = sortByAverageZ(list); + } + // Сбор отрисовочных вызовов + var first:Face; + var last:Face; + var drawList:Face; + for (face = list; face != null; face = next) { + next = face.processNext; + geometry = face.geometry; + face.geometry = null; + var changeGeometry:Boolean = next == null || geometry != next.geometry; + // Если сменилась геометрия или материал + if (changeGeometry || face.material != next.material) { + // Разрыв на стыке + face.processNext = null; + // Если сменилась геометрия + if (changeGeometry) { + if (first != null) { + last.negative = list; + first = null; + last = null; + } else { + list.positive = drawList; + drawList = list; + drawList.geometry = geometry; + } + // Если сменился материал + } else { + if (first != null) { + last.negative = list; + } else { + list.positive = drawList; + drawList = list; + drawList.geometry = geometry; + first = list; + } + last = list; + } + list = next; + } + } + // Дебаг + if (camera.debug) { + canvas = parentCanvas.getChildCanvas(object, true, false); + for (list = drawList; list != null; list = list.positive) { + if (list.geometry.debug & Debug.EDGES) { + for (face = list; face != null; face = face.negative) { + Debug.drawEdges(camera, canvas, face, 0xFF0000); + } + } + } + } + // Отрисовка + while (drawList != null) { + list = drawList; + drawList = list.positive; + list.positive = null; + geometry = list.geometry; + list.geometry = null; + canvas = parentCanvas.getChildCanvas(geometry.interactiveObject, true, false, geometry.alpha, geometry.blendMode, geometry.colorTransform, geometry.filters); + for (; list != null; list = next) { + next = list.negative; + list.negative = null; + if (list.material != null) { + // Отрисовка + if (geometry.viewAligned) { + list.material.drawViewAligned(camera, canvas, list, geometry.ml, geometry.tma, geometry.tmb, geometry.tmc, geometry.tmd, geometry.tmtx, geometry.tmty); + } else { + list.material.draw(camera, canvas, list, geometry.ml); + } + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + } + } + // Зачистка + for (geometry = conflict; geometry != null; geometry = nextGeometry) { + nextGeometry = geometry.next; + geometry.destroy(); + } + } + + private function collectNode(splitter:Face, list:Face, camera:Camera3D, threshold:Number, sort:Boolean, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var normalX:Number; + var normalY:Number; + var normalZ:Number; + var offset:Number; + var splitterLast:Face; + var negativeNode:Face; + var positiveNode:Face; + var geometry:Geometry; + // Статическая нода + if (splitter != null) { + geometry = splitter.geometry; + if (splitter.offset < 0) { + negativeNode = splitter.negative; + positiveNode = splitter.positive; + normalX = splitter.normalX; + normalY = splitter.normalY; + normalZ = splitter.normalZ; + offset = splitter.offset; + } else { + negativeNode = splitter.positive; + positiveNode = splitter.negative; + normalX = -splitter.normalX; + normalY = -splitter.normalY; + normalZ = -splitter.normalZ; + offset = -splitter.offset; + } + splitter.negative = null; + splitter.positive = null; + if (splitter.wrapper != null) { + splitterLast = splitter; + while (splitterLast.processNext != null) { + splitterLast = splitterLast.processNext; + splitterLast.geometry = geometry; + } + } else { + splitter.geometry = null; + splitter = null; + } + // Динамическая грань + } else { + splitter = list; + list = splitter.processNext; + splitterLast = splitter; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + normalX = 0; + normalY = 0; + normalZ = 1; + offset = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + if (splitter != null) { + splitterLast.processNext = face; + } else { + splitter = face; + } + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + negative.geometry = face.geometry; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + positive.geometry = face.geometry; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + for (w = face.wrapper.next.next; w.next != null; w = w.next); + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + face.geometry = null; + } + } + // Передняя часть + if (positiveNode != null) { + positiveNode.geometry = geometry; + if (positiveLast != null) positiveLast.processNext = null; + result = collectNode(positiveNode, positiveFirst, camera, threshold, sort, result); + } else if (positiveFirst != null) { + // Если нужно сортировать + if (sort && positiveFirst != positiveLast) { + if (positiveLast != null) positiveLast.processNext = null; + if (positiveFirst.geometry.sorting == 2) { + result = collectNode(null, positiveFirst, camera, threshold, sort, result); + } else { + positiveFirst = sortByAverageZ(positiveFirst); + // Не красиво + for (positiveLast = positiveFirst.processNext; positiveLast.processNext != null; positiveLast = positiveLast.processNext); + positiveLast.processNext = result; + result = positiveFirst; + } + } else { + positiveLast.processNext = result; + result = positiveFirst; + } + } + // Средння часть + if (splitter != null) { + splitterLast.processNext = result; + result = splitter; + } + // Задняя часть + if (negativeNode != null) { + negativeNode.geometry = geometry; + if (negativeLast != null) negativeLast.processNext = null; + result = collectNode(negativeNode, negativeFirst, camera, threshold, sort, result); + } else if (negativeFirst != null) { + // Если нужно сортировать + if (sort && negativeFirst != negativeLast) { + if (negativeLast != null) negativeLast.processNext = null; + if (negativeFirst.geometry.sorting == 2) { + result = collectNode(null, negativeFirst, camera, threshold, sort, result); + } else { + negativeFirst = sortByAverageZ(negativeFirst); + // Не красиво + for (negativeLast = negativeFirst.processNext; negativeLast.processNext != null; negativeLast = negativeLast.processNext); + negativeLast.processNext = result; + result = negativeFirst; + } + } else { + negativeLast.processNext = result; + result = negativeFirst; + } + } + return result; + } + + private function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/containers/KDTree.as b/Alternativa3D7/7.3/alternativa/engine3d/containers/KDTree.as new file mode 100644 index 0000000..eab6664 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/containers/KDTree.as @@ -0,0 +1,1386 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import __AS3__.vec.Vector; + import alternativa.engine3d.materials.TextureMaterial; + import flash.geom.ColorTransform; + import alternativa.engine3d.objects.Reference; + import alternativa.engine3d.objects.Mesh; + import flash.utils.Dictionary; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.core.Sorting; + import alternativa.engine3d.core.Clipping; + import alternativa.engine3d.core.MipMapping; + import alternativa.engine3d.materials.FillMaterial; + import flash.geom.Matrix3D; + import flash.display.BitmapData; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого помещены в бинарную древовидную структуру. + * Для построения дерева нужно добавить статические дочерние объекты + * с помощью addStaticChild(), затем вызвать createTree(). По баундам статических объектов + * построится ориентированная по осям бинарная древовидная структура (KD - частный случай BSP). + * Динамические объекты, можно в любое время добавлять addDynamicChild() и удалять removeDynamicChild(). + * Объекты, добавленные с помощью addChild() будут отрисовываться поверх всего в порядке добавления. + */ + public class KDTree extends ConflictContainer { + + public var debugAlphaFade:Number = 0.8; + + private var root:KDNode; + + // Плоскости отсечения камеры в контейнере + private var nearPlaneX:Number; + private var nearPlaneY:Number; + private var nearPlaneZ:Number; + private var nearPlaneOffset:Number; + private var farPlaneX:Number; + private var farPlaneY:Number; + private var farPlaneZ:Number; + private var farPlaneOffset:Number; + private var leftPlaneX:Number; + private var leftPlaneY:Number; + private var leftPlaneZ:Number; + private var leftPlaneOffset:Number; + private var rightPlaneX:Number; + private var rightPlaneY:Number; + private var rightPlaneZ:Number; + private var rightPlaneOffset:Number; + private var topPlaneX:Number; + private var topPlaneY:Number; + private var topPlaneZ:Number; + private var topPlaneOffset:Number; + private var bottomPlaneX:Number; + private var bottomPlaneY:Number; + private var bottomPlaneZ:Number; + private var bottomPlaneOffset:Number; + + // Перекрытия + private var occluders:Vector. = new Vector.(); + private var numOccluders:int; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Если есть корневая нода + if (root != null) { + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Расчёт плоскостей камеры в контейнере + calculateCameraPlanes(camera.nearClipping, camera.farClipping); + // Проверка на видимость рутовой ноды + var culling:int = cullingInContainer(object.culling, root.boundMinX, root.boundMinY, root.boundMinZ, root.boundMaxX, root.boundMaxY, root.boundMaxZ); + if (culling >= 0) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.NODES) { + debugNode(root, culling, camera, object, canvas, 1); + Debug.drawBounds(camera, canvas, object, root.boundMinX, root.boundMinY, root.boundMinZ, root.boundMaxX, root.boundMaxY, root.boundMaxZ, 0xDD33DD); + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Окклюдеры + numOccluders = 0; + if (camera.numOccluders > 0) { + updateOccluders(camera); + } + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + for (var current:Geometry = geometry; current != null; current = current.next) { + current.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + } + // Отрисовка дерева + drawNode(root, culling, camera, object, canvas, geometry); + // Зачиска окклюдеров + for (var i:int = 0; i < numOccluders; i++) { + var first:Vertex = occluders[i]; + for (var last:Vertex = first; last.next != null; last = last.next); + last.next = Vertex.collector; + Vertex.collector = first; + occluders[i] = null; + } + numOccluders = 0; + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } else { + super.draw(camera, object, parentCanvas); + } + } else { + super.draw(camera, object, parentCanvas); + } + } + + private function debugNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, alpha:Number):void { + if (node.negative != null) { + var negativeCulling:int = (culling > 0) ? cullingInContainer(culling, node.negative.boundMinX, node.negative.boundMinY, node.negative.boundMinZ, node.negative.boundMaxX, node.negative.boundMaxY, node.negative.boundMaxZ) : 0; + var positiveCulling:int = (culling > 0) ? cullingInContainer(culling, node.positive.boundMinX, node.positive.boundMinY, node.positive.boundMinZ, node.positive.boundMaxX, node.positive.boundMaxY, node.positive.boundMaxZ) : 0; + if (negativeCulling >= 0) { + debugNode(node.negative, negativeCulling, camera, object, canvas, alpha*debugAlphaFade); + } + Debug.drawKDNode(camera, canvas, object, node.axis, node.coord, node.boundMinX, node.boundMinY, node.boundMinZ, node.boundMaxX, node.boundMaxY, node.boundMaxZ, alpha); + if (positiveCulling >= 0) { + debugNode(node.positive, positiveCulling, camera, object, canvas, alpha*debugAlphaFade); + } + } + } + + private function drawNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (camera.occludedAll) { + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + return; + } + var nodeObjects:Vector. = node.objects; + var nodeObjectsLength:int = node.objectsLength; + var nodeOccluders:Vector. = node.occluders; + var nodeOccludersLength:int = node.occludersLength; + var child:Object3D; + // Узловая нода + if (node.negative != null) { + var negativeCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(culling, node.negative.boundMinX, node.negative.boundMinY, node.negative.boundMinZ, node.negative.boundMaxX, node.negative.boundMaxY, node.negative.boundMaxZ) : 0; + var positiveCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(culling, node.positive.boundMinX, node.positive.boundMinY, node.positive.boundMinZ, node.positive.boundMaxX, node.positive.boundMaxY, node.positive.boundMaxZ) : 0; + var axisX:Boolean = node.axis == 0; + var axisY:Boolean = node.axis == 1; + var min:Number; + var max:Number; + // Если видны обе дочерние ноды + if (negativeCulling >= 0 && positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + if (min < node.minCoord) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + geometry.split(camera, (node.axis == 0) ? 1 : 0, (node.axis == 1) ? 1 : 0, (node.axis == 2) ? 1 : 0, node.coord, threshold); + // Если негативный не пустой + if (geometry.next != null) { + geometry.next.next = negative; + negative = geometry.next; + } + // Если позитивный не пустой + if (geometry.faceStruct != null) { + geometry.next = positive; + positive = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка дочерних нод и объектов в плоскости + if (axisX && imd > node.coord || axisY && imh > node.coord || !axisX && !axisY && iml > node.coord) { + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + middle.draw(camera, object, canvas, threshold); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + } else { + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + middle.draw(camera, object, canvas, threshold); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + } + // Если видна только негативная + } else if (negativeCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + geometry.next = negative; + negative = geometry; + } else if (min >= node.minCoord) { + geometry.destroy(); + } else { + geometry.crop(camera, (node.axis == 0) ? -1 : 0, (node.axis == 1) ? -1 : 0, (node.axis == 2) ? -1 : 0, -node.coord, threshold); + // Если негативный не пустой + if (geometry.faceStruct != null) { + geometry.next = negative; + negative = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Если видна только позитивная + } else if (positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + geometry.destroy(); + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + geometry.crop(camera, (node.axis == 0) ? 1 : 0, (node.axis == 1) ? 1 : 0, (node.axis == 2) ? 1 : 0, node.coord, threshold); + // Если позитивный не пустой + if (geometry.faceStruct != null) { + geometry.next = positive; + positive = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Если обе ноды не видны + } else { + // Уничтожение динамиков + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + } + // Конечная нода + } else { + // Если есть статические объекты, не считая окклюдеры + if (nodeObjectsLength > 0) { + // Если есть конфликт + if (nodeObjectsLength > 1 || geometry != null) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Превращение статиков в геометрию + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + geometry = child.getGeometry(camera, child); + while (geometry != null) { + next = geometry.next; + geometry.next = middle; + middle = geometry; + geometry = next; + } + } + } + // Разруливаем конфликт + if (middle != null) { + if (middle.next != null) { + drawConflictGeometry(camera, object, canvas, middle); + } else { + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + } + } + } else { + // Если только один статик + child = nodeObjects[i]; + if (child.visible) { + child.composeAndAppend(object); + child.culling = culling; + child.draw(camera, child, canvas); + } + } + // Если нет статических объектов + } else { + // Если есть динамические объекты + if (geometry != null) { + // Если динамических объектов несколько + if (geometry.next != null) { + // Если есть окклюдеры + if (numOccluders > 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Если остались объекты + if (middle != null) { + if (middle.next != null) { + // Разруливание + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + for (geometry = middle; geometry != null; geometry = geometry.next) { + geometry.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + drawConflictGeometry(camera, object, canvas, middle); + } + } else { + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + } + } + } else { + // Разруливание + middle = geometry; + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + for (geometry = middle; geometry != null; geometry = geometry.next) { + geometry.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + drawConflictGeometry(camera, object, canvas, middle); + } + } + } else { + // Проверка с окклюдерами и отрисовка + if (geometry.numOccluders >= numOccluders || !occludeGeometry(camera, geometry)) { + geometry.draw(camera, object, canvas, threshold); + } + geometry.destroy(); + } + } + } + // Отрисовка окклюдеров + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, child.boundMinX, child.boundMinY, child.boundMinZ, child.boundMaxX, child.boundMaxY, child.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + } + } + + public function createTree(staticObjects:Vector., staticOccluders:Vector. = null):void { + var i:int; + var object:Object3D; + var staticObjectsLength:int = staticObjects.length; + var staticOccludersLength:int = (staticOccluders != null) ? staticOccluders.length : 0; + var objects:Vector.; + var objectsLength:int = 0; + var occluders:Vector.; + var occludersLength:int = 0; + // Баунд корневой ноды + var minX:Number = 1e+22; + var minY:Number = 1e+22; + var minZ:Number = 1e+22; + var maxX:Number = -1e+22; + var maxY:Number = -1e+22; + var maxZ:Number = -1e+22; + // Обработка объектов + for (i = 0; i < staticObjectsLength; i++) { + object = staticObjects[i]; + object.composeMatrix(); + // Расчёт баунда в координатах дерева + object.boundMinX = 1e+22; + object.boundMinY = 1e+22; + object.boundMinZ = 1e+22; + object.boundMaxX = -1e+22; + object.boundMaxY = -1e+22; + object.boundMaxZ = -1e+22; + object.updateBounds(object, object); + // Если объект не пустой + if (object.boundMinX <= object.boundMaxX) { + if (objects == null) objects = new Vector.(); + objects[objectsLength++] = object; + // Коррекция баунда корневой ноды + if (object.boundMinX < minX) minX = object.boundMinX; + if (object.boundMaxX > maxX) maxX = object.boundMaxX; + if (object.boundMinY < minY) minY = object.boundMinY; + if (object.boundMaxY > maxY) maxY = object.boundMaxY; + if (object.boundMinZ < minZ) minZ = object.boundMinZ; + if (object.boundMaxZ > maxZ) maxZ = object.boundMaxZ; + } + } + // Обработка окклюдеров + for (i = 0; i < staticOccludersLength; i++) { + object = staticOccluders[i]; + object.composeMatrix(); + // Расчёт баунда в координатах дерева + object.boundMinX = 1e+22; + object.boundMinY = 1e+22; + object.boundMinZ = 1e+22; + object.boundMaxX = -1e+22; + object.boundMaxY = -1e+22; + object.boundMaxZ = -1e+22; + object.updateBounds(object, object); + // Если объект не пустой + if (object.boundMinX <= object.boundMaxX) { + // Проверка выхода окклюдера за границы ноды + if (object.boundMinX < minX || object.boundMaxX > maxX || object.boundMinY < minY || object.boundMaxY > maxY || object.boundMinZ < minZ || object.boundMaxZ > maxZ) { + trace("Incorrect occluder size or position"); + } else { + if (occluders == null) occluders = new Vector.(); + occluders[occludersLength++] = object; + } + } + } + // Если есть непустые объекты + if (objectsLength > 0) { + root = new KDNode(); + root.boundMinX = minX; + root.boundMinY = minY; + root.boundMinZ = minZ; + root.boundMaxX = maxX; + root.boundMaxY = maxY; + root.boundMaxZ = maxZ; + createNode(root, objects, objectsLength, occluders, occludersLength); + } else { + root = null; + } + } + + public function optimizeTree(disposeSource:Boolean = true):Vector. { + var meshes:Dictionary = new Dictionary(); + var materials:Vector. = new Vector.(); + //optimizeNode(root, materials, meshes); + optimizeNode2(root, meshes); + + if (disposeSource) { + for (var key:* in meshes) { + var mesh:Mesh = key; + var material:TextureMaterial = mesh.faceList.material as TextureMaterial; + material.texture.dispose(); + material.disposeMipMap(); + } + } + return materials; + } + + private function optimizeNode2(node:KDNode, src:Dictionary):void { + if (node != null) { + + var newObjects:Vector. = new Vector.(); + + for (var i:int = 0; i < node.objectsLength; i++) { + var object:Object3D = node.objects[i]; + object.composeMatrix(); + + var source:Object3D = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + var mesh:Mesh = source as Mesh; + var material:TextureMaterial = mesh.faceList.material as TextureMaterial; + + src[mesh] = true; + + var newMesh:Mesh = mesh.clone() as Mesh; + newMesh.clipping = Clipping.FACE_CLIPPING; + newMesh.sorting = Sorting.AVERAGE_Z; + newMesh.setMatrix(new Matrix3D()); + + material.disposeMipMap(); + var newMaterial:TextureMaterial = new TextureMaterial(material.texture.clone(), material.repeat, material.smooth); + newMaterial.calculateMipMaps(3); + newMaterial.resolution = 5; + newMaterial.mipMapping = MipMapping.PER_PIXEL; + + for (var face:Face = newMesh.faceList; face != null; face = face.next) { + face.material = newMaterial; + } + for (var vertex:Vertex = newMesh.vertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.x = object.ma*x + object.mb*y + object.mc*z + object.md; + vertex.y = object.me*x + object.mf*y + object.mg*z + object.mh; + vertex.z = object.mi*x + object.mj*y + object.mk*z + object.ml; + } + newMesh.calculateBounds(); + newMesh.calculateNormals(true); + if (mesh.sorting == Sorting.STATIC_BSP) { + newMesh.calculateBSP(true); + } + newMesh.sorting = mesh.sorting; + newMesh.clipping = mesh.clipping; + newObjects.push(newMesh); + } + + node.objects = newObjects; + + optimizeNode2(node.negative, src); + optimizeNode2(node.positive, src); + } + } + + private function optimizeNode(node:KDNode, collector:Vector., src:Dictionary):void { + if (node != null) { + var i:int; + var vertex:Vertex; + var face:Face; + var mesh:Mesh; + var newMesh:Mesh; + var sizeMapKey:String; + var object:Object3D; + var source:Object3D; + var objects:Vector.; + var texture:BitmapData; + var point:Point = new Point(); + var rect:Rectangle = new Rectangle(); + // Узловая нода с двумя или более объектами + if (node.negative != null && node.objectsLength > 1) { + + // Объекты по размеру битмапы + var sizeMap:Object = new Object(); + for (i = 0; i < node.objectsLength; i++) { + object = node.objects[i]; + source = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + texture = ((source as Mesh).faceList.material as TextureMaterial).texture; + sizeMapKey = texture.width + "_" + texture.height; + objects = sizeMap[sizeMapKey]; + if (objects == null) { + objects = new Vector.(); + sizeMap[sizeMapKey] = objects; + } + objects.push(object); + } + + newMesh = new Mesh(); + newMesh.sorting = Sorting.NONE; + + for (var key:* in sizeMap) { + sizeMapKey = key; + objects = sizeMap[sizeMapKey]; + var objectsLength:int = objects.length; + var ti:int = sizeMapKey.indexOf("_"); + var tw:int = int(sizeMapKey.substring(0, ti)); + var th:int = int(sizeMapKey.substring(ti + 1)); + rect.width = tw; + rect.height = th; + var numX:int = 4096/tw; + var numY:int = objectsLength/numX; + if (numY > 0) { + var mod:int = objectsLength % numX; + while (mod > 0 && numX - mod > numY) { + numX--; + mod = objectsLength % numX; + } + } else { + numX = objectsLength; + } + numY++; + + var atlas:BitmapData = new BitmapData(numX*tw, numY*th, false, 0x000000); + var atlasMaterial:TextureMaterial = new TextureMaterial(atlas); + collector.push(atlasMaterial); + for (i = 0; i < objectsLength; i++) { + object = objects[i]; + object.composeMatrix(); + source = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + mesh = source as Mesh; + src[mesh] = true; + texture = (mesh.faceList.material as TextureMaterial).texture; + point.x = (i % numX)*tw; + point.y = int(i/numX)*th; + atlas.copyPixels(texture, rect, point); + + for (face = mesh.faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.material = atlasMaterial; + var lastWrapper:Wrapper = null; + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + vertex = wrapper.vertex; + var newVertex:Vertex = new Vertex(); + newVertex.next = newMesh.vertexList; + newMesh.vertexList = newVertex; + newVertex.x = object.ma*vertex.x + object.mb*vertex.y + object.mc*vertex.z + object.md; + newVertex.y = object.me*vertex.x + object.mf*vertex.y + object.mg*vertex.z + object.mh; + newVertex.z = object.mi*vertex.x + object.mj*vertex.y + object.mk*vertex.z + object.ml; + newVertex.u = vertex.u/numX + point.x/(numX*tw); + newVertex.v = vertex.v/numY + point.y/(numY*th); + newWrapper.vertex = newVertex; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + newFace.next = newMesh.faceList; + newMesh.faceList = newFace; + } + + } + atlasMaterial.calculateMipMaps(); + atlasMaterial.mipMapping = MipMapping.PER_PIXEL; + atlasMaterial.resolution = 5; + atlasMaterial.correctUV = true; + } + + newMesh.calculateBounds(); + newMesh.calculateNormals(true); + + node.objectsLength = 1; + node.objects.length = 1; + node.objects[0] = newMesh; + + // Листовая нода или узловая с одним или менее объектом + } else { + for (i = 0; i < node.objectsLength; i++) { + object = node.objects[i]; + object.composeMatrix(); + source = object; + while (source is Reference) { + source = (source as Reference).referenceObject; + } + mesh = source as Mesh; + src[mesh] = true; + newMesh = mesh.clone() as Mesh; + newMesh.x = 0; + newMesh.y = 0; + newMesh.z = 0; + newMesh.rotationX = 0; + newMesh.rotationY = 0; + newMesh.rotationZ = 0; + newMesh.scaleX = 1; + newMesh.scaleY = 1; + newMesh.scaleZ = 1; + var material:TextureMaterial = mesh.faceList.material as TextureMaterial; + var newMaterial:TextureMaterial = new TextureMaterial(material.texture.clone(), material.repeat, material.smooth); + collector.push(newMaterial); + newMaterial.calculateMipMaps(); + newMaterial.resolution = 5; + newMaterial.mipMapping = MipMapping.PER_PIXEL; + newMesh.setMaterialToAllFaces(newMaterial); + for (vertex = newMesh.vertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.x = object.ma*x + object.mb*y + object.mc*z + object.md; + vertex.y = object.me*x + object.mf*y + object.mg*z + object.mh; + vertex.z = object.mi*x + object.mj*y + object.mk*z + object.ml; + } + newMesh.calculateBounds(); + newMesh.calculateNormals(true); + if (mesh.sorting == Sorting.STATIC_BSP) { + newMesh.calculateBSP(true); + } + newMesh.sorting = mesh.sorting; + node.objects[i] = newMesh; + } + } + optimizeNode(node.negative, collector, src); + optimizeNode(node.positive, collector, src); + } + } + + static private const splitCoordsX:Vector. = new Vector.(); + static private const splitCoordsY:Vector. = new Vector.(); + static private const splitCoordsZ:Vector. = new Vector.(); + + private function createNode(node:KDNode, objects:Vector., numObjects:int, occluders:Vector., numOccluders:int):void { + var i:int; + var j:int; + var object:Object3D; + var coord:Number; + // Сбор потенциальных координат сплита без дубликатов + var numSplitCoordsX:int = 0; + var numSplitCoordsY:int = 0; + var numSplitCoordsZ:int = 0; + for (i = 0; i < numObjects; i++) { + object = objects[i]; + if (object.boundMaxX - object.boundMinX <= threshold + threshold) { + coord = (object.boundMinX <= node.boundMinX + threshold) ? node.boundMinX : ((object.boundMaxX >= node.boundMaxX - threshold) ? node.boundMaxX : (object.boundMinX + object.boundMaxX)*0.5); + for (j = 0; j < numSplitCoordsX; j++) if (coord >= splitCoordsX[j] - threshold && coord <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = coord; + } else { + if (object.boundMinX > node.boundMinX + threshold) { + for (j = 0; j < numSplitCoordsX; j++) if (object.boundMinX >= splitCoordsX[j] - threshold && object.boundMinX <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = object.boundMinX; + } + if (object.boundMaxX < node.boundMaxX - threshold) { + for (j = 0; j < numSplitCoordsX; j++) if (object.boundMaxX >= splitCoordsX[j] - threshold && object.boundMaxX <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = object.boundMaxX; + } + } + if (object.boundMaxY - object.boundMinY <= threshold + threshold) { + coord = (object.boundMinY <= node.boundMinY + threshold) ? node.boundMinY : ((object.boundMaxY >= node.boundMaxY - threshold) ? node.boundMaxY : (object.boundMinY + object.boundMaxY)*0.5); + for (j = 0; j < numSplitCoordsY; j++) if (coord >= splitCoordsY[j] - threshold && coord <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = coord; + } else { + if (object.boundMinY > node.boundMinY + threshold) { + for (j = 0; j < numSplitCoordsY; j++) if (object.boundMinY >= splitCoordsY[j] - threshold && object.boundMinY <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = object.boundMinY; + } + if (object.boundMaxY < node.boundMaxY - threshold) { + for (j = 0; j < numSplitCoordsY; j++) if (object.boundMaxY >= splitCoordsY[j] - threshold && object.boundMaxY <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = object.boundMaxY; + } + } + if (object.boundMaxZ - object.boundMinZ <= threshold + threshold) { + coord = (object.boundMinZ <= node.boundMinZ + threshold) ? node.boundMinZ : ((object.boundMaxZ >= node.boundMaxZ - threshold) ? node.boundMaxZ : (object.boundMinZ + object.boundMaxZ)*0.5); + for (j = 0; j < numSplitCoordsZ; j++) if (coord >= splitCoordsZ[j] - threshold && coord <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = coord; + } else { + if (object.boundMinZ > node.boundMinZ + threshold) { + for (j = 0; j < numSplitCoordsZ; j++) if (object.boundMinZ >= splitCoordsZ[j] - threshold && object.boundMinZ <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = object.boundMinZ; + } + if (object.boundMaxZ < node.boundMaxZ - threshold) { + for (j = 0; j < numSplitCoordsZ; j++) if (object.boundMaxZ >= splitCoordsZ[j] - threshold && object.boundMaxZ <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = object.boundMaxZ; + } + } + } + // Поиск лучшего сплита + var splitAxis:int = -1; + var splitCoord:Number; + var bestCost:Number = Number.MAX_VALUE; + var numNegative:int; + var numPositive:int; + var area:Number; + var areaNegative:Number; + var areaPositive:Number; + var cost:Number; + area = (node.boundMaxY - node.boundMinY)*(node.boundMaxZ - node.boundMinZ); + for (i = 0; i < numSplitCoordsX; i++) { + coord = splitCoordsX[i]; + areaNegative = area*(coord - node.boundMinX); + areaPositive = area*(node.boundMaxX - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + object = objects[j]; + if (object.boundMaxX <= coord + threshold) { + if (object.boundMinX < coord - threshold) numNegative++; + } else { + if (object.boundMinX >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 0; + splitCoord = coord; + } + } + } + area = (node.boundMaxX - node.boundMinX)*(node.boundMaxZ - node.boundMinZ); + for (i = 0; i < numSplitCoordsY; i++) { + coord = splitCoordsY[i]; + areaNegative = area*(coord - node.boundMinY); + areaPositive = area*(node.boundMaxY - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + object = objects[j]; + if (object.boundMaxY <= coord + threshold) { + if (object.boundMinY < coord - threshold) numNegative++; + } else { + if (object.boundMinY >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 1; + splitCoord = coord; + } + } + } + area = (node.boundMaxX - node.boundMinX)*(node.boundMaxY - node.boundMinY); + for (i = 0; i < numSplitCoordsZ; i++) { + coord = splitCoordsZ[i]; + areaNegative = area*(coord - node.boundMinZ); + areaPositive = area*(node.boundMaxZ - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + object = objects[j]; + if (object.boundMaxZ <= coord + threshold) { + if (object.boundMinZ < coord - threshold) numNegative++; + } else { + if (object.boundMinZ >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 2; + splitCoord = coord; + } + } + } + // Если сплит не найден + if (splitAxis < 0) { + node.objects = objects; + node.objectsLength = numObjects; + node.occluders = occluders; + node.occludersLength = numOccluders; + } else { + node.axis = splitAxis; + node.coord = splitCoord; + node.minCoord = splitCoord - threshold; + node.maxCoord = splitCoord + threshold; + // Списки разделения + var negativeObjects:Vector.; + var negativeObjectsLength:int = 0; + var negativeOccluders:Vector.; + var negativeOccludersLength:int = 0; + var positiveObjects:Vector.; + var positiveObjectsLength:int = 0; + var positiveOccluders:Vector.; + var positiveOccludersLength:int = 0; + var min:Number; + var max:Number; + // Разделение объектов + for (i = 0; i < numObjects; i++) { + object = objects[i]; + min = (splitAxis == 0) ? object.boundMinX : ((splitAxis == 1) ? object.boundMinY : object.boundMinZ); + max = (splitAxis == 0) ? object.boundMaxX : ((splitAxis == 1) ? object.boundMaxY : object.boundMaxZ); + if (max <= splitCoord + threshold) { + if (min < splitCoord - threshold) { + // Объект в негативной стороне + if (negativeObjects == null) negativeObjects = new Vector.(); + negativeObjects[negativeObjectsLength++] = object; + } else { + // Остаётся в ноде + if (node.objects == null) node.objects = new Vector.(); + node.objects[node.objectsLength++] = object; + } + } else { + if (min >= splitCoord - threshold) { + // Объект в положительной стороне + if (positiveObjects == null) positiveObjects = new Vector.(); + positiveObjects[positiveObjectsLength++] = object; + } else { + // Распилился + } + } + } + // Разделение окклюдеров + for (i = 0; i < numOccluders; i++) { + object = occluders[i]; + min = (splitAxis == 0) ? object.boundMinX : ((splitAxis == 1) ? object.boundMinY : object.boundMinZ); + max = (splitAxis == 0) ? object.boundMaxX : ((splitAxis == 1) ? object.boundMaxY : object.boundMaxZ); + if (max <= splitCoord + threshold) { + if (min < splitCoord - threshold) { + // Объект в негативной стороне + if (negativeOccluders == null) negativeOccluders = new Vector.(); + negativeOccluders[negativeOccludersLength++] = object; + } else { + // Остаётся в ноде + if (node.occluders == null) node.occluders = new Vector.(); + node.occluders[node.occludersLength++] = object; + } + } else { + if (min >= splitCoord - threshold) { + // Объект в положительной стороне + if (positiveOccluders == null) positiveOccluders = new Vector.(); + positiveOccluders[positiveOccludersLength++] = object; + } else { + // Распилился + trace("Incorrect occluder size or position"); + } + } + } + // Создание дочерних нод + node.negative = new KDNode(); + node.positive = new KDNode(); + // Назначение баундов + node.negative.boundMinX = node.boundMinX; + node.negative.boundMinY = node.boundMinY; + node.negative.boundMinZ = node.boundMinZ; + node.negative.boundMaxX = node.boundMaxX; + node.negative.boundMaxY = node.boundMaxY; + node.negative.boundMaxZ = node.boundMaxZ; + node.positive.boundMinX = node.boundMinX; + node.positive.boundMinY = node.boundMinY; + node.positive.boundMinZ = node.boundMinZ; + node.positive.boundMaxX = node.boundMaxX; + node.positive.boundMaxY = node.boundMaxY; + node.positive.boundMaxZ = node.boundMaxZ; + // Коррекция + if (splitAxis == 0) { + node.negative.boundMaxX = splitCoord; + node.positive.boundMinX = splitCoord; + } else if (splitAxis == 1) { + node.negative.boundMaxY = splitCoord; + node.positive.boundMinY = splitCoord; + } else { + node.negative.boundMaxZ = splitCoord; + node.positive.boundMinZ = splitCoord; + } + // Разделение дочерних нод + if (negativeObjectsLength > 0) { + createNode(node.negative, negativeObjects, negativeObjectsLength, negativeOccluders, negativeOccludersLength); + } else if (negativeOccludersLength > 0) { + trace("Incorrect occluder size or position"); + } + if (positiveObjectsLength > 0) { + createNode(node.positive, positiveObjects, positiveObjectsLength, positiveOccluders, positiveOccludersLength); + } else if (positiveOccludersLength > 0) { + trace("Incorrect occluder size or position"); + } + } + } + + private function calculateCameraPlanes(near:Number, far:Number):void { + // Ближняя плоскость + nearPlaneX = imc; + nearPlaneY = img; + nearPlaneZ = imk; + nearPlaneOffset = (imc*near + imd)*nearPlaneX + (img*near + imh)*nearPlaneY + (imk*near + iml)*nearPlaneZ; + // Дальняя плоскость + farPlaneX = -imc; + farPlaneY = -img; + farPlaneZ = -imk; + farPlaneOffset = (imc*far + imd)*farPlaneX + (img*far + imh)*farPlaneY + (imk*far + iml)*farPlaneZ; + // Верхняя плоскость + var ax:Number = -ima - imb + imc; + var ay:Number = -ime - imf + img; + var az:Number = -imi - imj + imk; + var bx:Number = ima - imb + imc; + var by:Number = ime - imf + img; + var bz:Number = imi - imj + imk; + topPlaneX = bz*ay - by*az; + topPlaneY = bx*az - bz*ax; + topPlaneZ = by*ax - bx*ay; + topPlaneOffset = imd*topPlaneX + imh*topPlaneY + iml*topPlaneZ; + // Правая плоскость + ax = bx; + ay = by; + az = bz; + bx = ima + imb + imc; + by = ime + imf + img; + bz = imi + imj + imk; + rightPlaneX = bz*ay - by*az; + rightPlaneY = bx*az - bz*ax; + rightPlaneZ = by*ax - bx*ay; + rightPlaneOffset = imd*rightPlaneX + imh*rightPlaneY + iml*rightPlaneZ; + // Нижняя плоскость + ax = bx; + ay = by; + az = bz; + bx = -ima + imb + imc; + by = -ime + imf + img; + bz = -imi + imj + imk; + bottomPlaneX = bz*ay - by*az; + bottomPlaneY = bx*az - bz*ax; + bottomPlaneZ = by*ax - bx*ay; + bottomPlaneOffset = imd*bottomPlaneX + imh*bottomPlaneY + iml*bottomPlaneZ; + // Левая плоскость + ax = bx; + ay = by; + az = bz; + bx = -ima - imb + imc; + by = -ime - imf + img; + bz = -imi - imj + imk; + leftPlaneX = bz*ay - by*az; + leftPlaneY = bx*az - bz*ax; + leftPlaneZ = by*ax - bx*ay; + leftPlaneOffset = imd*leftPlaneX + imh*leftPlaneY + iml*leftPlaneZ; + } + + private function updateOccluders(camera:Camera3D):void { + for (var i:int = numOccluders; i < camera.numOccluders; i++) { + var occluder:Vertex = null; + for (var cameraOccluder:Vertex = camera.occluders[i]; cameraOccluder != null; cameraOccluder = cameraOccluder.next) { + var newOccluder:Vertex = cameraOccluder.create(); + newOccluder.next = occluder; + occluder = newOccluder; + var ax:Number = ima*cameraOccluder.x + imb*cameraOccluder.y + imc*cameraOccluder.z; + var ay:Number = ime*cameraOccluder.x + imf*cameraOccluder.y + img*cameraOccluder.z; + var az:Number = imi*cameraOccluder.x + imj*cameraOccluder.y + imk*cameraOccluder.z; + var bx:Number = ima*cameraOccluder.u + imb*cameraOccluder.v + imc*cameraOccluder.offset; + var by:Number = ime*cameraOccluder.u + imf*cameraOccluder.v + img*cameraOccluder.offset; + var bz:Number = imi*cameraOccluder.u + imj*cameraOccluder.v + imk*cameraOccluder.offset; + occluder.x = bz*ay - by*az; + occluder.y = bx*az - bz*ax; + occluder.z = by*ax - bx*ay; + occluder.offset = imd*occluder.x + imh*occluder.y + iml*occluder.z; + } + occluders[numOccluders] = occluder; + numOccluders++; + } + } + + private function cullingInContainer(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int { + if (culling > 0) { + // Отсечение по ниар + if (culling & 1) { + if (nearPlaneX >= 0) if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } + } + // Отсечение по фар + if (culling & 2) { + if (farPlaneX >= 0) if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } + } + // Отсечение по левой стороне + if (culling & 4) { + if (leftPlaneX >= 0) if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } + } + // Отсечение по правой стороне + if (culling & 8) { + if (rightPlaneX >= 0) if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } + } + // Отсечение по верхней стороне + if (culling & 16) { + if (topPlaneX >= 0) if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } + } + // Отсечение по нижней стороне + if (culling & 32) { + if (bottomPlaneX >= 0) if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } + } + } + // Отсечение по окклюдерам + for (var i:int = 0; i < numOccluders; i++) { + for (var occluder:Vertex = occluders[i]; occluder != null; occluder = occluder.next) { + if (occluder.x >= 0) if (occluder.y >= 0) if (occluder.z >= 0) { + if (boundMaxX*occluder.x + boundMaxY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMaxX*occluder.x + boundMaxY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (boundMaxX*occluder.x + boundMinY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMaxX*occluder.x + boundMinY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.y >= 0) if (occluder.z >= 0) { + if (boundMinX*occluder.x + boundMaxY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMinX*occluder.x + boundMaxY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (boundMinX*occluder.x + boundMinY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMinX*occluder.x + boundMinY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } + } + if (occluder == null) return -1; + } + return culling; + } + + private function occludeGeometry(camera:Camera3D, geometry:Geometry):Boolean { + for (var i:int = geometry.numOccluders; i < numOccluders; i++) { + for (var occluder:Vertex = occluders[i]; occluder != null; occluder = occluder.next) { + if (occluder.x >= 0) if (occluder.y >= 0) if (occluder.z >= 0) { + if (geometry.boundMaxX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMaxX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (geometry.boundMaxX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMaxX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.y >= 0) if (occluder.z >= 0) { + if (geometry.boundMinX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMinX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (geometry.boundMinX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMinX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } + } + if (occluder == null) return true; + } + geometry.numOccluders = numOccluders; + return false; + } + + } +} + +import alternativa.engine3d.core.Object3D; + +class KDNode { + + public var negative:KDNode; + public var positive:KDNode; + + public var axis:int; + public var coord:Number; + public var minCoord:Number; + public var maxCoord:Number; + + public var boundMinX:Number; + public var boundMinY:Number; + public var boundMinZ:Number; + public var boundMaxX:Number; + public var boundMaxY:Number; + public var boundMaxZ:Number; + + public var objects:Vector.; + public var objectsLength:int = 0; + public var occluders:Vector.; + public var occludersLength:int = 0; + +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/containers/ZSortContainer.as b/Alternativa3D7/7.3/alternativa/engine3d/containers/ZSortContainer.as new file mode 100644 index 0000000..35a18b2 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/containers/ZSortContainer.as @@ -0,0 +1,64 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого отрисовываются по удалённости от камеры + */ + public class ZSortContainer extends Object3DContainer { + + static private const sortingStack:Vector. = new Vector.(); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var j:int; + var l:int = 0; + var r:int = numVisibleChildren - 1; + var child:Object3D; + var stackIndex:int; + var left:Number; + var median:Number; + var right:Number; + sortingStack[0] = l; + sortingStack[1] = r; + stackIndex = 2; + while (stackIndex > 0) { + r = sortingStack[--stackIndex]; + l = sortingStack[--stackIndex]; + j = r; + i = l; + child = visibleChildren[(r + l) >> 1]; + median = child.ml; + do { + while ((left = (visibleChildren[i] as Object3D).ml) > median) i++; + while ((right = (visibleChildren[j] as Object3D).ml) < median) j--; + if (i <= j) { + child = visibleChildren[i]; + visibleChildren[i++] = visibleChildren[j]; + visibleChildren[j--] = child; + } + } while (i <= j); + if (l < j) { + sortingStack[stackIndex++] = l; + sortingStack[stackIndex++] = j; + } + if (i < r) { + sortingStack[stackIndex++] = i; + sortingStack[stackIndex++] = r; + } + } + // Отрисовка + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/controllers/SimpleObjectController.as b/Alternativa3D7/7.3/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..1e46069 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,461 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + + import flash.display.InteractiveObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.ui.Keyboard; + import flash.utils.getTimer; + + /** + * + */ + public class SimpleObjectController { + + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + + public var speed:Number; + public var speedMultiplier:Number; + public var mouseSensitivity:Number; + public var maxPitch:Number = Number.MAX_VALUE; + public var minPitch:Number = -Number.MAX_VALUE; + + private var eventSource:InteractiveObject; + private var _object:Object3D; + + private var _up:Boolean; + private var _down:Boolean; + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _accelerate:Boolean; + + private var displacement:Vector3D = new Vector3D(); + private var mousePoint:Point = new Point(); + private var mouseLook:Boolean; + private var objectTransform:Vector.; + + private var time:int; + + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответствующая команде + * клавиша. + */ + private var actionBindings:Object = {}; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Object = {}; + + /** + * + * @param eventSource источник событий для контроллера + * @param speed скорость поступательного перемещения объекта + * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши + */ + public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { + this.eventSource = eventSource; + this.object = object; + this.speed = speed; + this.speedMultiplier = speedMultiplier; + this.mouseSensitivity = mouseSensitivity; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_ACCELERATE] = accelerate; + + setDefaultBindings(); + + enable(); + } + + /** + * Активирует контроллер. + */ + public function enable():void { + eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Деактивирует контроллер. + */ + public function disable():void { + eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + stopMouseLook(); + } + + /** + * + */ + private function onMouseDown(e:MouseEvent):void { + startMouseLook(); + } + + /** + * + */ + private function onMouseUp(e:MouseEvent):void { + stopMouseLook(); + } + + /** + * Включает режим взгляда мышью. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Отключает режим взгляда мышью. + */ + public function stopMouseLook():void { + mouseLook = false; + } + + /** + * + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + + /** + * Управляемый объект. + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * + */ + public function get object():Object3D { + return _object; + } + + /** + * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.getMatrix().decompose(); + } + + /** + * Вычисляет новое положение объекта, используя внутренний счётчик времени. + */ + public function update():void { + if (_object == null) return; + + var frameTime:Number = time; + time = getTimer(); + frameTime = 0.001*(time - frameTime); + if (frameTime > 0.1) frameTime = 0.1; + + var moved:Boolean = false; + + if (mouseLook) { + var dx:Number = eventSource.mouseX - mousePoint.x; + var dy:Number = eventSource.mouseY - mousePoint.y; + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + var v:Vector3D = objectTransform[1]; + v.x -= dy*Math.PI/180*mouseSensitivity; + if (v.x > maxPitch) v.x = maxPitch; + if (v.x < minPitch) v.x = minPitch; + v.z -= dx*Math.PI/180*mouseSensitivity; + moved = true; + } + + displacement.x = _right ? 1 : (_left ? -1 : 0); + displacement.y = _forward ? 1 : (_back ? -1 : 0); + displacement.z = _up ? 1 : (_down ? -1 : 0); + if (displacement.lengthSquared > 0) { + if (_object is Camera3D) { + var tmp:Number = displacement.z; + displacement.z = displacement.y; + displacement.y = -tmp; + } + deltaTransformVector(displacement); + if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); + else displacement.scaleBy(speed*frameTime/displacement.length); + (objectTransform[0] as Vector3D).incrementBy(displacement); + moved = true; + } + + if (moved) { + var m:Matrix3D = new Matrix3D(); + m.recompose(objectTransform); + _object.setMatrix(m); + } + } + + /** + * + * @param pos + */ + public function setObjectPos(pos:Vector3D):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = pos.x; + v.y = pos.y; + v.z = pos.z; + } + } + + /** + * + * @param x + * @param y + * @param z + */ + public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = x; + v.y = y; + v.z = z; + } + } + + /** + * + * @param point + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * + * @param x + * @param y + * @param z + */ + public function lookAtXYZ(x:Number, y:Number, z:Number):void { + if (_object == null) return; + var v:Vector3D = objectTransform[0]; + var dx:Number = x - v.x; + var dy:Number = y - v.y; + var dz:Number = z - v.z; + v = objectTransform[1]; + v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); + if (_object is Camera3D) v.x -= 0.5*Math.PI; + v.y = 0; + v.z = -Math.atan2(dx, dy); + var m:Matrix3D = _object.getMatrix(); + m.recompose(objectTransform); + _object.setMatrix(m); + } + + private var _vin:Vector. = new Vector.(3); + private var _vout:Vector. = new Vector.(3); + + private function deltaTransformVector(v:Vector3D):void { + _vin[0] = v.x; + _vin[1] = v.y; + _vin[2] = v.z; + _object.getMatrix().transformVectors(_vin, _vout); + var c:Vector3D = objectTransform[0]; + v.x = _vout[0] - c.x; + v.y = _vout[1] - c.y; + v.z = _vout[2] - c.z; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:String in keyBindings) delete keyBindings[key]; + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + bindKey(87, ACTION_FORWARD); + bindKey(83, ACTION_BACK); + bindKey(65, ACTION_LEFT); + bindKey(68, ACTION_RIGHT); + bindKey(69, ACTION_UP); + bindKey(67, ACTION_DOWN); + bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); + + bindKey(Keyboard.UP, ACTION_FORWARD); + bindKey(Keyboard.DOWN, ACTION_BACK); + bindKey(Keyboard.LEFT, ACTION_LEFT); + bindKey(Keyboard.RIGHT, ACTION_RIGHT); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Camera3D.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..46db26e --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,587 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.events.Event; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.System; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + import flash.utils.getTimer; + + use namespace alternativa3d; + + public class Camera3D extends Object3D { + + /** + * Вьюпорт + */ + public var view:View; + /** + * Поле зрения в радианах. + */ + public var fov:Number = Math.PI/2; + + public var nearClipping:Number = 0; + public var farClipping:Number = 5000; + + // Параметры перспективы + alternativa3d var viewSizeX:Number; + alternativa3d var viewSizeY:Number; + alternativa3d var focalLength:Number; + + // Перекрытия + alternativa3d var occluders:Vector. = new Vector.(); + alternativa3d var numOccluders:int; + alternativa3d var occludedAll:Boolean; + + alternativa3d var numDraws:int; + alternativa3d var numPolygons:int; + alternativa3d var numTriangles:int; + + /** + * Отрисовка иерархии объектов, в которой находится камера. + * Перед render(), если менялись параметры камеры, нужно вызвать updateProjection(). + */ + public function render():void { + if (view != null) { + // Расчёт параметров проецирования + viewSizeX = view._width*0.5; + viewSizeY = view._height*0.5; + focalLength = Math.sqrt(viewSizeX*viewSizeX + viewSizeY*viewSizeY)/Math.tan(fov*0.5); + // Расчёт матрицы перевода из глобального пространства в камеру + composeMatrix(); + var root:Object3D = this; + while (root._parent != null) { + root = root._parent; + root.composeMatrix(); + appendMatrix(root); + } + invertMatrix(); + // Сброс окклюдеров + numOccluders = 0; + occludedAll = false; + // Сброс счётчиков + numDraws = 0; + numPolygons = 0; + numTriangles = 0; + // Сброс отрисовок + view.numDraws = 0; + // Отрисовка + if (root != this && root.visible) { + root.appendMatrix(this); + if (root.cullingInCamera(this, root, 63) >= 0) { + root.draw(this, root, view); + // Отложенное удаление вершин и граней в коллектор + deferredDestroy(); + // Зачистка окклюдеров + clearOccluders(); + } + } + // Зачистка ненужных канвасов + view.removeChildren(view.numDraws); + // Обработка интерактивности после рендера + if (view._interactive) { + view.onMouseMove(); + } + } + } + + override alternativa3d function composeMatrix():void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX*viewSizeX/focalLength; + var sinXscaleY:Number = sinX*scaleY*viewSizeY/focalLength; + var cosXscaleY:Number = cosX*scaleY*viewSizeY/focalLength; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + ma = cosZ*cosYscaleX; + mb = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + mc = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + md = x; + me = sinZ*cosYscaleX; + mf = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + mg = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + mh = y; + mi = -sinY*scaleX; + mj = cosY*sinXscaleY; + mk = cosY*cosXscaleZ; + ml = z; + } + + private function invertMatrix():void { + var a:Number = ma; + var b:Number = mb; + var c:Number = mc; + var d:Number = md; + var e:Number = me; + var f:Number = mf; + var g:Number = mg; + var h:Number = mh; + var i:Number = mi; + var j:Number = mj; + var k:Number = mk; + var l:Number = ml; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + ma = (-g*j + f*k)*det; + mb = (c*j - b*k)*det; + mc = (-c*f + b*g)*det; + md = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + me = (g*i - e*k)*det; + mf = (-c*i + a*k)*det; + mg = (c*e - a*g)*det; + mh = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + mi = (-f*i + e*j)*det; + mj = (b*i - a*j)*det; + mk = (-b*e + a*f)*det; + ml = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + } + + /** + * @param v + * @param result + */ + public function projectGlobal(v:Vector3D, result:Vector3D):void { + /*_tmpv[0] = v.x; _tmpv[1] = v.y; _tmpv[2] = v.z; + cameraMatrix.transformVectors(_tmpv, _tmpv); + projectionMatrix.transformVectors(_tmpv, _tmpv); + result.z = _tmpv[2]; + result.x = _tmpv[0]/result.z; + result.y = _tmpv[1]/result.z;*/ + } + + // DEBUG + + // Режим отладки + public var debug:Boolean = false; + + // Список объектов дебага + private var debugSet:Object = new Object(); + + // Добавить в дебаг + public function addToDebug(debug:int, ... rest):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + for (var i:int = 0; i < rest.length;) debugSet[debug][rest[i++]] = true; + } + + // Убрать из дебага + public function removeFromDebug(debug:int, ... rest):void { + if (debugSet[debug]) { + for (var i:int = 0; i < rest.length;) delete debugSet[debug][rest[i++]]; + var key:*; + for (key in debugSet[debug]) break; + if (!key) delete debugSet[debug]; + } + } + + // Проверка, находится ли объект или один из классов, от которых он нследован, в дебаге + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 512; debug <<= 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + public var diagram:Sprite = createDiagram(); + + private var fpsTextField:TextField; + private var memoryTextField:TextField; + private var drawsTextField:TextField; + private var polygonsTextField:TextField; + private var trianglesTextField:TextField; + private var timerTextField:TextField; + private var graph:Bitmap; + private var rect:Rectangle; + + private var _diagramAlign:String = "TR"; + private var _diagramHorizontalMargin:Number = 2; + private var _diagramVerticalMargin:Number = 2; + + public var fpsUpdatePeriod:int = 10; + private var fpsUpdateCounter:int; + private var previousFrameTime:int; + private var previousPeriodTime:int; + + private var maxMemory:int; + + public var timerUpdatePeriod:int = 10; + private var timerUpdateCounter:int; + private var timeSum:int; + private var timeCount:int; + private var timer:int; + + private function createDiagram():Sprite { + var diagram:Sprite = new Sprite(); + diagram.mouseEnabled = false; + diagram.mouseChildren = false; + // Инициализация диаграммы + diagram.addEventListener(Event.ADDED_TO_STAGE, function():void { + // FPS + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.LEFT; + fpsTextField.text = "FPS:"; + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + diagram.addChild(fpsTextField); + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.RIGHT; + fpsTextField.text = Number(diagram.stage.frameRate).toFixed(2); + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + fpsTextField.width = 65; + diagram.addChild(fpsTextField); + // Время выполнения метода + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.LEFT; + timerTextField.text = "MS:"; + timerTextField.selectable = false; + timerTextField.x = -3; + timerTextField.y = 4; + diagram.addChild(timerTextField); + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.RIGHT; + timerTextField.text = ""; + timerTextField.selectable = false; + timerTextField.x = -3; + timerTextField.y = 4; + timerTextField.width = 65; + diagram.addChild(timerTextField); + // Память + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.LEFT; + memoryTextField.text = "MEM:"; + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 13; + diagram.addChild(memoryTextField); + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.RIGHT; + memoryTextField.text = bytesToString(System.totalMemory); + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 13; + memoryTextField.width = 65; + diagram.addChild(memoryTextField); + // Отрисовочные вызовы + drawsTextField = new TextField(); + drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00); + drawsTextField.autoSize = TextFieldAutoSize.LEFT; + drawsTextField.text = "DRW:"; + drawsTextField.selectable = false; + drawsTextField.x = -3; + drawsTextField.y = 22; + diagram.addChild(drawsTextField); + drawsTextField = new TextField(); + drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00); + drawsTextField.autoSize = TextFieldAutoSize.RIGHT; + drawsTextField.text = "0"; + drawsTextField.selectable = false; + drawsTextField.x = -3; + drawsTextField.y = 22; + drawsTextField.width = 52; + diagram.addChild(drawsTextField); + // Полигоны + polygonsTextField = new TextField(); + polygonsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF0033); + polygonsTextField.autoSize = TextFieldAutoSize.LEFT; + polygonsTextField.text = "PLG:"; + polygonsTextField.selectable = false; + polygonsTextField.x = -3; + polygonsTextField.y = 31; + diagram.addChild(polygonsTextField); + polygonsTextField = new TextField(); + polygonsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF0033); + polygonsTextField.autoSize = TextFieldAutoSize.RIGHT; + polygonsTextField.text = "0"; + polygonsTextField.selectable = false; + polygonsTextField.x = -3; + polygonsTextField.y = 31; + polygonsTextField.width = 52; + diagram.addChild(polygonsTextField); + // Треугольники + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.LEFT; + trianglesTextField.text = "TRI:"; + trianglesTextField.selectable = false; + trianglesTextField.x = -3; + trianglesTextField.y = 40; + diagram.addChild(trianglesTextField); + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.RIGHT; + trianglesTextField.text = "0"; + trianglesTextField.selectable = false; + trianglesTextField.x = -3; + trianglesTextField.y = 40; + trianglesTextField.width = 52; + diagram.addChild(trianglesTextField); + // График + graph = new Bitmap(new BitmapData(60, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40); + graph.x = 0; + graph.y = 54; + diagram.addChild(graph); + // Сброс параметров + previousPeriodTime = getTimer(); + previousFrameTime = previousPeriodTime; + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + // Подписка + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Деинициализация диаграммы + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void { + // Обнуление + diagram.removeChild(fpsTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(drawsTextField); + diagram.removeChild(polygonsTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(timerTextField); + diagram.removeChild(graph); + fpsTextField = null; + memoryTextField = null; + drawsTextField = null; + polygonsTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Отписка + diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram); + diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram); + }); + return diagram; + } + + private function resizeDiagram(e:Event = null):void { + if (diagram.stage != null) { + var coord:Point = diagram.parent.globalToLocal(new Point()); + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) { + diagram.x = Math.round(coord.x + _diagramHorizontalMargin); + } + if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth/2 - graph.width/2); + } + if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width); + } + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) { + diagram.y = Math.round(coord.y + _diagramVerticalMargin); + } + if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight/2 - (graph.y + graph.height)/2); + } + if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height); + } + } + } + + private function updateDiagram(e:Event):void { + var value:Number; + var mod:int; + var time:int = getTimer(); + var stageFrameRate:int = diagram.stage.frameRate; + + // FPS текст + if (++fpsUpdateCounter == fpsUpdatePeriod) { + value = 1000*fpsUpdatePeriod/(time - previousPeriodTime); + if (value > stageFrameRate) value = stageFrameRate; + mod = value*100 % 100; + fpsTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS график + value = 1000/(time - previousFrameTime); + if (value > stageFrameRate) value = stageFrameRate; + graph.bitmapData.scroll(1, 0); + graph.bitmapData.fillRect(rect, 0x20FFFFFF); + graph.bitmapData.setPixel32(0, 40*(1 - value/stageFrameRate), 0xFFCCCCCC); + previousFrameTime = time; + + // Время текст + if (++timerUpdateCounter == timerUpdatePeriod) { + if (timeCount > 0) { + value = timeSum/timeCount; + mod = value*100 % 100; + timerTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = ""; + } + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + } + + // Память текст + var memory:int = System.totalMemory; + value = memory/1048576; + mod = value*100 % 100; + memoryTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + + // Память график + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00); + + // Отрисовочные вызовы текст + drawsTextField.text = String(numDraws); + + // Полигоны текст + polygonsTextField.text = String(numPolygons); + + // Треугольники текст + trianglesTextField.text = String(numTriangles); + } + + public function startTimer():void { + timer = getTimer(); + } + + public function stopTimer():void { + timeSum += getTimer() - timer; + timeCount++; + } + + public function get diagramAlign():String { + return _diagramAlign; + } + + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + public function get diagramVerticalMargin():Number { + return _diagramVerticalMargin; + } + + public function set diagramVerticalMargin(value:Number):void { + _diagramVerticalMargin = value; + resizeDiagram(); + } + + private function bytesToString(bytes:int):String { + if (bytes < 1024) return bytes + "b"; + else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb"; + else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb"; + else if (bytes < 1048576) return (bytes >> 10) + "kb"; + else if (bytes < 10485760) return (bytes/1048576).toFixed(2);// + "mb"; + else if (bytes < 104857600) return (bytes/1048576).toFixed(1);// + "mb"; + else return String(bytes >> 20);// + "mb"; + } + + // Отложенное удаление в коллектор + + private var firstVertex:Vertex = new Vertex(); + private var firstFace:Face = new Face(); + private var firstWrapper:Wrapper = new Wrapper(); + alternativa3d var lastWrapper:Wrapper = firstWrapper; + alternativa3d var lastVertex:Vertex = firstVertex; + alternativa3d var lastFace:Face = firstFace; + + private function deferredDestroy():void { + for (var face:Face = firstFace.next; face != null; face = face.next) { + var w:Wrapper = face.wrapper; + if (w != null) { + for (var lw:Wrapper = null; w != null; lw = w,w = w.next) { + w.vertex = null; + } + lastWrapper.next = face.wrapper; + lastWrapper = lw; + } + face.material = null; + face.wrapper = null; + //face.processNext = null; + //face.geometry = null; + } + if (firstFace != lastFace) { + lastFace.next = Face.collector; + Face.collector = firstFace.next; + firstFace.next = null; + lastFace = firstFace; + } + if (firstWrapper != lastWrapper) { + lastWrapper.next = Wrapper.collector; + Wrapper.collector = firstWrapper.next; + firstWrapper.next = null; + lastWrapper = firstWrapper; + } + if (firstVertex != lastVertex) { + lastVertex.next = Vertex.collector; + Vertex.collector = firstVertex.next; + firstVertex.next = null; + lastVertex = firstVertex; + } + } + + alternativa3d function clearOccluders():void { + for (var i:int = 0; i < numOccluders; i++) { + var first:Vertex = occluders[i]; + //for (var last:Vertex = first; last.next != null; last = last.next); + var last:Vertex = first; + while (last.next != null) last = last.next; + last.next = Vertex.collector; + Vertex.collector = first; + occluders[i] = null; + } + numOccluders = 0; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Canvas.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Canvas.as new file mode 100644 index 0000000..9366221 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Canvas.as @@ -0,0 +1,108 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Canvas extends Sprite { + + static private const defaultColorTransform:ColorTransform = new ColorTransform(); + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + alternativa3d var gfx:Graphics = graphics; + + private var modifiedGraphics:Boolean; + private var modifiedAlpha:Boolean; + private var modifiedBlendMode:Boolean; + private var modifiedColorTransform:Boolean; + private var modifiedFilters:Boolean; + + alternativa3d var _numChildren:int = 0; + alternativa3d var numDraws:int = 0; + + alternativa3d var interactiveObject:Object3D; + + /*public function Canvas() { + mouseEnabled = false; + mouseChildren = false; + }*/ + + alternativa3d function getChildCanvas(interactiveObject:Object3D, useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas; + var displayObject:DisplayObject; + // Зачистка не канвасов + while (_numChildren > numDraws && !((displayObject = getChildAt(_numChildren - 1 - numDraws)) is Canvas)) { + removeChild(displayObject); + _numChildren--; + } + // Получение канваса + if (_numChildren > numDraws++) { + canvas = displayObject as Canvas; + // Зачистка + if (canvas.modifiedGraphics) { + canvas.gfx.clear(); + } + if (canvas._numChildren > 0 && !useChildren) { + canvas.removeChildren(0); + } + } else { + canvas = (collectorLength > 0) ? collector[--collectorLength] : new Canvas(); + addChildAt(canvas, 0); + _numChildren++; + } + // Сохранение интерактивного объекта + canvas.interactiveObject = interactiveObject; + // Пометка о том, что в graphics будет что-то нарисовано + canvas.modifiedGraphics = useGraphics; + // Установка свойств + if (alpha != 1) { + canvas.alpha = alpha; + canvas.modifiedAlpha = true; + } else if (canvas.modifiedAlpha) { + canvas.alpha = 1; + canvas.modifiedAlpha = false; + } + if (blendMode != "normal") { + canvas.blendMode = blendMode; + canvas.modifiedBlendMode = true; + } else if (canvas.modifiedBlendMode) { + canvas.blendMode = "normal"; + canvas.modifiedBlendMode = false; + } + if (colorTransform != null) { + colorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = colorTransform; + canvas.modifiedColorTransform = true; + } else if (canvas.modifiedColorTransform) { + defaultColorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = defaultColorTransform; + canvas.modifiedColorTransform = false; + } + if (filters != null) { + canvas.filters = filters; + canvas.modifiedFilters = true; + } else if (canvas.modifiedFilters) { + canvas.filters = null; + canvas.modifiedFilters = false; + } + return canvas; + } + + alternativa3d function removeChildren(keep:int):void { + for (var canvas:Canvas; _numChildren > keep; _numChildren--) { + if ((canvas = removeChildAt(0) as Canvas) != null) { + canvas.interactiveObject = null; + if (canvas.modifiedGraphics) canvas.gfx.clear(); + if (canvas._numChildren > 0) canvas.removeChildren(0); + collector[collectorLength++] = canvas; + } + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Clipping.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Clipping.as new file mode 100644 index 0000000..573bc32 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Clipping.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Clipping { + + /** + * Объект отсекается целиком, если он полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const BOUND_CULLING:int = 0; + /** + * Грань отсекается целиком, если она полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const FACE_CULLING:int = 1; + /** + * Грань подрезается пирамидой видимости камеры. + */ + static public const FACE_CLIPPING:int = 2; + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Debug.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..5af6f97 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Debug.as @@ -0,0 +1,190 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Debug { + + static public const NAMES:int = 1; + static public const AXES:int = 2; + static public const CENTERS:int = 4; + static public const BOUNDS:int = 8; + + static public const EDGES:int = 16; + static public const VERTICES:int = 32; + static public const NORMALS:int = 64; + + static public const NODES:int = 128; + static public const SPLITS:int = 256; + static public const BONES:int = 512; + + static alternativa3d function drawEdges(camera:Camera3D, canvas:Canvas, list:Face, color:int):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var t:Number; + canvas.gfx.lineStyle(0, color); + for (var face:Face = list; face != null; face = face.processNext) { + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + var x:Number = vertex.cameraX*viewSizeX*t; + var y:Number = vertex.cameraY*viewSizeY*t; + canvas.gfx.moveTo(x, y); + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX*t, vertex.cameraY*viewSizeY*t); + } + canvas.gfx.lineTo(x, y); + } + } + + static private const boundVertexList:Vertex = Vertex.createList(8); + + static alternativa3d function drawBounds(camera:Camera3D, canvas:Canvas, object:Object3D, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, color:int = -1):void { + var vertex:Vertex; + // Заполнение + var a:Vertex = boundVertexList; + a.x = boundMinX; + a.y = boundMinY; + a.z = boundMinZ; + var b:Vertex = a.next; + b.x = boundMaxX; + b.y = boundMinY; + b.z = boundMinZ; + var c:Vertex = b.next; + c.x = boundMinX; + c.y = boundMaxY; + c.z = boundMinZ; + var d:Vertex = c.next; + d.x = boundMaxX; + d.y = boundMaxY; + d.z = boundMinZ; + var e:Vertex = d.next; + e.x = boundMinX; + e.y = boundMinY; + e.z = boundMaxZ; + var f:Vertex = e.next; + f.x = boundMaxX; + f.y = boundMinY; + f.z = boundMaxZ; + var g:Vertex = f.next; + g.x = boundMinX; + g.y = boundMaxY; + g.z = boundMaxZ; + var h:Vertex = g.next; + h.x = boundMaxX; + h.y = boundMaxY; + h.z = boundMaxZ; + // Трансформация в камеру + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = object.ma*vertex.x + object.mb*vertex.y + object.mc*vertex.z + object.md; + vertex.cameraY = object.me*vertex.x + object.mf*vertex.y + object.mg*vertex.z + object.mh; + vertex.cameraZ = object.mi*vertex.x + object.mj*vertex.y + object.mk*vertex.z + object.ml; + if (vertex.cameraZ <= 0) return; + } + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Отрисовка + canvas.gfx.lineStyle(0, (color < 0) ? ((object.culling > 0) ? 0xFFFF00 : 0x00FF00) : color); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + canvas.gfx.moveTo(e.cameraX, e.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.moveTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.moveTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + } + + static private const nodeVertexList:Vertex = Vertex.createList(4); + + static alternativa3d function drawKDNode(camera:Camera3D, canvas:Canvas, object:Object3D, axis:int, coord:Number, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, alpha:Number):void { + var vertex:Vertex; + // Заполнение + var a:Vertex = nodeVertexList; + var b:Vertex = a.next; + var c:Vertex = b.next; + var d:Vertex = c.next; + if (axis == 0) { + a.x = coord; + a.y = boundMinY; + a.z = boundMaxZ; + b.x = coord; + b.y = boundMaxY; + b.z = boundMaxZ; + c.x = coord; + c.y = boundMaxY; + c.z = boundMinZ; + d.x = coord; + d.y = boundMinY; + d.z = boundMinZ; + } else if (axis == 1) { + a.x = boundMaxX; + a.y = coord; + a.z = boundMaxZ; + b.x = boundMinX; + b.y = coord; + b.z = boundMaxZ; + c.x = boundMinX; + c.y = coord; + c.z = boundMinZ; + d.x = boundMaxX; + d.y = coord; + d.z = boundMinZ; + } else { + a.x = boundMinX; + a.y = boundMinY; + a.z = coord; + b.x = boundMaxX; + b.y = boundMinY; + b.z = coord; + c.x = boundMaxX; + c.y = boundMaxY; + c.z = coord; + d.x = boundMinX; + d.y = boundMaxY; + d.z = coord; + } + // Трансформация в камеру + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = object.ma*vertex.x + object.mb*vertex.y + object.mc*vertex.z + object.md; + vertex.cameraY = object.me*vertex.x + object.mf*vertex.y + object.mg*vertex.z + object.mh; + vertex.cameraZ = object.mi*vertex.x + object.mj*vertex.y + object.mk*vertex.z + object.ml; + if (vertex.cameraZ <= 0) return; + } + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Отрисовка + canvas.gfx.lineStyle(0, (axis == 0) ? 0xFF0000 : ((axis == 1) ? 0x00FF00 : 0x0000FF), alpha); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Face.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..ef6a4cd --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Face.as @@ -0,0 +1,179 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.materials.Material; + + public class Face { + + public var material:Material; + + public var next:Face; + + public var negative:Face; + public var positive:Face; + + public var wrapper:Wrapper; + + public var normalX:Number; + public var normalY:Number; + public var normalZ:Number; + public var offset:Number; + + public var processNext:Face; + + public var distance:Number; + + public var geometry:Geometry; + + static public var collector:Face; + + static public function create():Face { + if (collector != null) { + var res:Face = collector; + collector = res.next; + res.next = null; + /*if (res.processNext != null) trace("!!!processNext!!!"); + if (res.geometry != null) trace("!!!geometry!!!"); + if (res.negative != null) trace("!!!negative!!!"); + if (res.positive != null) trace("!!!positive!!!");*/ + return res; + } else { + //trace("new Face"); + return new Face(); + } + } + + public function create():Face { + if (collector != null) { + var res:Face = collector; + collector = res.next; + res.next = null; + /*if (res.processNext != null) trace("!!!processNext!!!"); + if (res.geometry != null) trace("!!!geometry!!!"); + if (res.negative != null) trace("!!!negative!!!"); + if (res.positive != null) trace("!!!positive!!!");*/ + return res; + } else { + //trace("new Face"); + return new Face(); + } + } + + /** + * Расчёт нормали + * @param normalize Флаг нормализации + */ + /*public function calculateNormal(normalize:Boolean = false):void { + var w:Wrapper = wrapper; + var a:Vertex = w.vertex; w = w.next; + var b:Vertex = w.vertex; w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + normalX = acz*aby - acy*abz; + normalY = acx*abz - acz*abx; + normalZ = acy*abx - acx*aby; + if (normalize) { + var length:Number = normalX*normalX + normalY*normalY + normalZ*normalZ; + if (length > 0.001) { + length = 1/Math.sqrt(length); + normalX *= length; + normalY *= length; + normalZ *= length; + } + } + offset = a.x*normalX + a.y*normalY + a.z*normalZ; + }*/ + + /** + * Выстраивание вершин в лучшую последовательность и расчёт нормали + * @return Если грань не вырождена - true, иначе false + */ + public function calculateBestSequenceAndNormal():void { + if (wrapper.next.next.next != null) { + var max:Number = -1e+22; + var s:Wrapper; + var sm:Wrapper; + var sp:Wrapper; + for (w = wrapper; w != null; w = w.next) { + var wn:Wrapper = (w.next != null) ? w.next : wrapper; + var wm:Wrapper = (wn.next != null) ? wn.next : wrapper; + a = w.vertex; + b = wn.vertex; + c = wm.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = nx*nx + ny*ny + nz*nz; + if (nl > max) { + max = nl; + s = w; + } + } + if (s != wrapper) { + //for (sm = wrapper.next.next.next; sm.next != null; sm = sm.next); + sm = wrapper.next.next.next; + while (sm.next != null) sm = sm.next; + //for (sp = wrapper; sp.next != s && sp.next != null; sp = sp.next); + sp = wrapper; + while (sp.next != s && sp.next != null) sp = sp.next; + sm.next = wrapper; + sp.next = null; + wrapper = s; + } + } + var w:Wrapper = wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > 0) { + nl = 1/Math.sqrt(nl); + nx *= nl; + ny *= nl; + nz *= nl; + normalX = nx; + normalY = ny; + normalZ = nz; + } + offset = a.x*nx + a.y*ny + a.z*nz; + } + + /*public function destroy():void { + var w:Wrapper = wrapper; + w.vertex = null; + do { + w = w.next; + w.vertex = null; + } while (w.next != null); + w.next = Wrapper.collector; + Wrapper.collector = wrapper; + material = null; + wrapper = null; + //temporary = false; + processNext = null; + next = collector; + collector = this; + }*/ + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Geometry.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Geometry.as new file mode 100644 index 0000000..3b878ba --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Geometry.as @@ -0,0 +1,1233 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Geometry { + + public var next:Geometry; + + public var faceStruct:Face; + + public var transformID:int = 0; + + public var numOccluders:int; + + public var interactiveObject:Object3D; + + // Передаваемые при сплите свойства + public var alpha:Number; + public var blendMode:String; + public var colorTransform:ColorTransform; + public var filters:Array; + public var sorting:int; + public var debug:int = 0; + public var boundType:int = 0; + public var viewAligned:Boolean = false; + public var tma:Number; + public var tmb:Number; + public var tmc:Number; + public var tmd:Number; + public var tmtx:Number; + public var tmty:Number; + + // Матрица перевода из локальных координат объекта в камеру + public var ma:Number; + public var mb:Number; + public var mc:Number; + public var md:Number; + public var me:Number; + public var mf:Number; + public var mg:Number; + public var mh:Number; + public var mi:Number; + public var mj:Number; + public var mk:Number; + public var ml:Number; + + // Матрица перевода из камеры в локальные координаты объекта + public var ima:Number; + public var imb:Number; + public var imc:Number; + public var imd:Number; + public var ime:Number; + public var imf:Number; + public var img:Number; + public var imh:Number; + public var imi:Number; + public var imj:Number; + public var imk:Number; + public var iml:Number; + + // AABB + public var boundMinX:Number; + public var boundMinY:Number; + public var boundMinZ:Number; + public var boundMaxX:Number; + public var boundMaxY:Number; + public var boundMaxZ:Number; + + // OOBB + public var boundVertexList:Vertex = Vertex.createList(8); + public var boundPlaneList:Vertex = Vertex.createList(6); + + static private var collector:Geometry; + + static public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + public function destroy():void { + if (faceStruct != null) { + destroyFaceStruct(faceStruct); + faceStruct = null; + } + interactiveObject = null; + viewAligned = false; + colorTransform = null; + filters = null; + numOccluders = 0; + debug = 0; + transformID = 0; + boundType = 0; + next = collector; + collector = this; + } + + private function destroyFaceStruct(struct:Face):void { + if (struct.negative != null) { + destroyFaceStruct(struct.negative); + struct.negative = null; + } + if (struct.positive != null) { + destroyFaceStruct(struct.positive); + struct.positive = null; + } + //for (var next:Face = struct.processNext; next != null; struct.processNext = null, struct = next, next = struct.processNext); + var next:Face = struct.processNext; + while (next != null) { + struct.processNext = null; + struct = next; + next = struct.processNext; + } + } + + public function calculateAABB(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + calculateBB(faceStruct, a, b, c, d, e, f, g, h, i, j, k, l); + // Тип баунда + boundType = 1; + } + + public function calculateOOBB():void { + if (viewAligned) { + + } else { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + calculateBB(faceStruct, ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + // Костыль + if (boundMaxX - boundMinX < 1) { + boundMaxX = boundMinX + 1; + } + if (boundMaxY - boundMinY < 1) { + boundMaxY = boundMinY + 1; + } + if (boundMaxZ - boundMinZ < 1) { + boundMaxZ = boundMinZ + 1; + } + // Заполнениее вершин баунда + var a:Vertex = boundVertexList; + a.x = boundMinX; + a.y = boundMinY; + a.z = boundMinZ; + var b:Vertex = a.next; + b.x = boundMaxX; + b.y = boundMinY; + b.z = boundMinZ; + var c:Vertex = b.next; + c.x = boundMinX; + c.y = boundMaxY; + c.z = boundMinZ; + var d:Vertex = c.next; + d.x = boundMaxX; + d.y = boundMaxY; + d.z = boundMinZ; + var e:Vertex = d.next; + e.x = boundMinX; + e.y = boundMinY; + e.z = boundMaxZ; + var f:Vertex = e.next; + f.x = boundMaxX; + f.y = boundMinY; + f.z = boundMaxZ; + var g:Vertex = f.next; + g.x = boundMinX; + g.y = boundMaxY; + g.z = boundMaxZ; + var h:Vertex = g.next; + h.x = boundMaxX; + h.y = boundMaxY; + h.z = boundMaxZ; + // Перевод вершин баунда из локальных координат в камеру + for (var vertex:Vertex = a; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = ma*x + mb*y + mc*z + md; + vertex.cameraY = me*x + mf*y + mg*z + mh; + vertex.cameraZ = mi*x + mj*y + mk*z + ml; + } + // Заполнение плоскостей баунда + var front:Vertex = boundPlaneList; + var back:Vertex = front.next; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var acx:Number = e.cameraX - ax; + var acy:Number = e.cameraY - ay; + var acz:Number = e.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + front.cameraX = nx; + front.cameraY = ny; + front.cameraZ = nz; + front.offset = ax*nx + ay*ny + az*nz; + back.cameraX = -nx; + back.cameraY = -ny; + back.cameraZ = -nz; + back.offset = -c.cameraX*nx - c.cameraY*ny - c.cameraZ*nz; + var left:Vertex = back.next; + var right:Vertex = left.next; + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + abx = e.cameraX - ax; + aby = e.cameraY - ay; + abz = e.cameraZ - az; + acx = c.cameraX - ax; + acy = c.cameraY - ay; + acz = c.cameraZ - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + left.cameraX = nx; + left.cameraY = ny; + left.cameraZ = nz; + left.offset = ax*nx + ay*ny + az*nz; + right.cameraX = -nx; + right.cameraY = -ny; + right.cameraZ = -nz; + right.offset = -b.cameraX*nx - b.cameraY*ny - b.cameraZ*nz; + var top:Vertex = right.next; + var bottom:Vertex = top.next; + ax = e.cameraX; + ay = e.cameraY; + az = e.cameraZ; + abx = f.cameraX - ax; + aby = f.cameraY - ay; + abz = f.cameraZ - az; + acx = g.cameraX - ax; + acy = g.cameraY - ay; + acz = g.cameraZ - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + top.cameraX = nx; + top.cameraY = ny; + top.cameraZ = nz; + top.offset = ax*nx + ay*ny + az*nz; + bottom.cameraX = -nx; + bottom.cameraY = -ny; + bottom.cameraZ = -nz; + bottom.offset = -a.cameraX*nx - a.cameraY*ny - a.cameraZ*nz; + } + // Тип баунда + boundType = 2; + } + + private function calculateBB(struct:Face, a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + for (var face:Face = struct; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + var cameraX:Number = vertex.cameraX; + var cameraY:Number = vertex.cameraY; + var cameraZ:Number = vertex.cameraZ; + var x:Number = a*cameraX + b*cameraY + c*cameraZ + d; + var y:Number = e*cameraX + f*cameraY + g*cameraZ + h; + var z:Number = i*cameraX + j*cameraY + k*cameraZ + l; + vertex.x = x; + vertex.y = y; + vertex.z = z; + if (x < boundMinX) boundMinX = x; + if (x > boundMaxX) boundMaxX = x; + if (y < boundMinY) boundMinY = y; + if (y > boundMaxY) boundMaxY = y; + if (z < boundMinZ) boundMinZ = z; + if (z > boundMaxZ) boundMaxZ = z; + vertex.transformID = transformID; + } + } + } + if (struct.negative != null) calculateBB(struct.negative, a, b, c, d, e, f, g, h, i, j, k, l); + if (struct.positive != null) calculateBB(struct.positive, a, b, c, d, e, f, g, h, i, j, k, l); + } + + public function updateBB(struct:Face):void { + for (var face:Face = struct; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + if (vertex.x < boundMinX) boundMinX = vertex.x; + if (vertex.x > boundMaxX) boundMaxX = vertex.x; + if (vertex.y < boundMinY) boundMinY = vertex.y; + if (vertex.y > boundMaxY) boundMaxY = vertex.y; + if (vertex.z < boundMinZ) boundMinZ = vertex.z; + if (vertex.z > boundMaxZ) boundMaxZ = vertex.z; + vertex.transformID = transformID; + } + } + } + if (struct.negative != null) updateBB(struct.negative); + if (struct.positive != null) updateBB(struct.positive); + } + + // Сам объект с другим набором граней остаётся спереди, его next указывает на негативную часть + public function split(camera:Camera3D, planeX:Number, planeY:Number, planeZ:Number, planeOffset:Number, threshold:Number):void { + // Разбиение + var result:Face = faceStruct.create(); + splitFaceStruct(camera, faceStruct, result, planeX, planeY, planeZ, planeOffset, planeOffset - threshold, planeOffset + threshold); + // Копирование свойств + if (result.negative != null) { + var negative:Geometry = create(); + next = negative; + negative.faceStruct = result.negative; + result.negative = null; + negative.ma = ma; + negative.mb = mb; + negative.mc = mc; + negative.md = md; + negative.me = me; + negative.mf = mf; + negative.mg = mg; + negative.mh = mh; + negative.mi = mi; + negative.mj = mj; + negative.mk = mk; + negative.ml = ml; + negative.interactiveObject = interactiveObject; + negative.alpha = alpha; + negative.blendMode = blendMode; + negative.colorTransform = colorTransform; + negative.filters = filters; + negative.sorting = sorting; + negative.debug = debug; + negative.boundType = boundType; + negative.viewAligned = viewAligned; + if (viewAligned) { + negative.tma = tma; + negative.tmb = tmb; + negative.tmc = tmc; + negative.tmd = tmd; + negative.tmtx = tmtx; + negative.tmty = tmty; + } else { + negative.ima = ima; + negative.imb = imb; + negative.imc = imc; + negative.imd = imd; + negative.ime = ime; + negative.imf = imf; + negative.img = img; + negative.imh = imh; + negative.imi = imi; + negative.imj = imj; + negative.imk = imk; + negative.iml = iml; + } + negative.boundMinX = 1e+22; + negative.boundMinY = 1e+22; + negative.boundMinZ = 1e+22; + negative.boundMaxX = -1e+22; + negative.boundMaxY = -1e+22; + negative.boundMaxZ = -1e+22; + negative.transformID = transformID + 1; + negative.updateBB(negative.faceStruct); + } + if (result.positive != null) { + faceStruct = result.positive; + result.positive = null; + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + updateBB(faceStruct); + } else { + faceStruct = null; + } + result.next = Face.collector; + Face.collector = result; + } + + // Всегда отсекается негативная часть, поэтому если нужно получить негативную, следует перевернуть плоскость + public function crop(camera:Camera3D, planeX:Number, planeY:Number, planeZ:Number, planeOffset:Number, threshold:Number):void { + // Подрезка + faceStruct = cropFaceStruct(camera, faceStruct, planeX, planeY, planeZ, planeOffset, planeOffset - threshold, planeOffset + threshold); + if (faceStruct != null) { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + updateBB(faceStruct); + } + } + + private function splitFaceStruct(camera:Camera3D, struct:Face, result:Face, normalX:Number, normalY:Number, normalZ:Number, offset:Number, offsetMin:Number, offsetMax:Number):void { + var face:Face; + var next:Face; + var w:Wrapper; + var v:Vertex; + var v2:Vertex; + // Разделение дочерних нод + var negativeNegative:Face; + var negativePositive:Face; + var positiveNegative:Face; + var positivePositive:Face; + if (struct.negative != null) { + splitFaceStruct(camera, struct.negative, result, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.negative = null; + negativeNegative = result.negative; + negativePositive = result.positive; + } + if (struct.positive != null) { + splitFaceStruct(camera, struct.positive, result, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.positive = null; + positiveNegative = result.negative; + positivePositive = result.positive; + } + // Разделение ноды + var negativeFirst:Face; + var negativeLast:Face; + var positiveFirst:Face; + var positiveLast:Face; + if (struct.wrapper != null) { + for (face = struct; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v2 = v.create(); + camera.lastVertex.next = v2; + camera.lastVertex = v2; + v2.x = v.x; + v2.y = v.y; + v2.z = v.z; + v2.u = v.u; + v2.v = v.v; + v2.cameraX = v.cameraX; + v2.cameraY = v.cameraY; + v2.cameraZ = v.cameraZ; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v2; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo < offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } else if (bo > offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } else { + v2 = b.create(); + camera.lastVertex.next = v2; + camera.lastVertex = v2; + v2.x = b.x; + v2.y = b.y; + v2.z = b.z; + v2.u = b.u; + v2.v = b.v; + v2.cameraX = b.cameraX; + v2.cameraY = b.cameraY; + v2.cameraZ = b.cameraZ; + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v2; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + } + // Если сзади от сплита есть грани или обе дочерние ноды + if (negativeFirst != null || negativeNegative != null && positiveNegative != null) { + // Создание пустой ноды + if (negativeFirst == null) { + negativeFirst = struct.create(); + camera.lastFace.next = negativeFirst; + camera.lastFace = negativeFirst; + } else { + negativeLast.processNext = null; + } + if (sorting == 3) { + negativeFirst.normalX = struct.normalX; + negativeFirst.normalY = struct.normalY; + negativeFirst.normalZ = struct.normalZ; + negativeFirst.offset = struct.offset; + } + negativeFirst.negative = negativeNegative; + negativeFirst.positive = positiveNegative; + result.negative = negativeFirst; + } else { + result.negative = (negativeNegative != null) ? negativeNegative : positiveNegative; + } + // Если спереди от сплита есть грани или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + // Создание пустой ноды + if (positiveFirst == null) { + positiveFirst = struct.create(); + camera.lastFace.next = positiveFirst; + camera.lastFace = positiveFirst; + } else { + positiveLast.processNext = null; + } + if (sorting == 3) { + positiveFirst.normalX = struct.normalX; + positiveFirst.normalY = struct.normalY; + positiveFirst.normalZ = struct.normalZ; + positiveFirst.offset = struct.offset; + } + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + result.positive = positiveFirst; + } else { + result.positive = (negativePositive != null) ? negativePositive : positivePositive; + } + } + + private function cropFaceStruct(camera:Camera3D, struct:Face, normalX:Number, normalY:Number, normalZ:Number, offset:Number, offsetMin:Number, offsetMax:Number):Face { + var face:Face; + var next:Face; + var w:Wrapper; + var v:Vertex; + // Разделение дочерних нод + var negativePositive:Face; + var positivePositive:Face; + if (struct.negative != null) { + negativePositive = cropFaceStruct(camera, struct.negative, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.negative = null; + } + if (struct.positive != null) { + positivePositive = cropFaceStruct(camera, struct.positive, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.positive = null; + } + // Разделение ноды + var positiveFirst:Face; + var positiveLast:Face; + if (struct.wrapper != null) { + for (face = struct; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } else if (!infront) { + face.processNext = null; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + } + // Если спереди от сплита есть грани или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + // Создание пустой ноды + if (positiveFirst == null) { + positiveFirst = struct.create(); + camera.lastFace.next = positiveFirst; + camera.lastFace = positiveFirst; + } else { + positiveLast.processNext = null; + } + if (sorting == 3) { + positiveFirst.normalX = struct.normalX; + positiveFirst.normalY = struct.normalY; + positiveFirst.normalZ = struct.normalZ; + positiveFirst.offset = struct.offset; + } + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + return positiveFirst; + } else { + return (negativePositive != null) ? negativePositive : positivePositive; + } + } + + public function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas, threshold:Number):void { + var canvas:Canvas; + var list:Face; + if (viewAligned) { + list = faceStruct; + // Дебаг + if (debug > 0) { + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, (boundType != 2) ? 0xFFFFFF : 0xFF9900); + if (debug & Debug.BOUNDS) { + if (boundType == 1) { + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0x99FF00); + } + } + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false, alpha, blendMode, colorTransform, filters); + list.material.drawViewAligned(camera, canvas, list, ml, tma, tmb, tmc, tmd, tmtx, tmty); + } else { + // Сортировка + switch (sorting) { + case 0: + list = faceStruct; + break; + case 1: + list = (faceStruct.processNext != null) ? sortByAverageZ(faceStruct) : faceStruct; + break; + case 2: + list = (faceStruct.processNext != null) ? sortByDynamicBSP(faceStruct, camera, threshold) : faceStruct; + break; + case 3: + list = collectNode(faceStruct); + break; + } + // Дебаг + if (debug > 0) { + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) { + if (boundType == 1) { + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0x99FF00); + } else if (boundType == 2) { + var oma:Number = object.ma, omb:Number = object.mb, omc:Number = object.mc, omd:Number = object.md, ome:Number = object.me, omf:Number = object.mf, omg:Number = object.mg, omh:Number = object.mh, omi:Number = object.mi, omj:Number = object.mj, omk:Number = object.mk, oml:Number = object.ml; + object.ma = ma; + object.mb = mb; + object.mc = mc; + object.md = md; + object.me = me; + object.mf = mf; + object.mg = mg; + object.mh = mh; + object.mi = mi; + object.mj = mj; + object.mk = mk; + object.ml = ml; + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0xFF9900); + object.ma = oma; + object.mb = omb; + object.mc = omc; + object.md = omd; + object.me = ome; + object.mf = omf; + object.mg = omg; + object.mh = omh; + object.mi = omi; + object.mj = omj; + object.mk = omk; + object.ml = oml; + } + } + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false, alpha, blendMode, colorTransform, filters); + for (var face:Face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, object.ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + faceStruct = null; + } + + private function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + private function sortByDynamicBSP(list:Face, camera:Camera3D, threshold:Number, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var splitter:Face = list; + list = splitter.processNext; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var normalX:Number = 0; + var normalY:Number = 0; + var normalZ:Number = 1; + var offset:Number = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + splitterLast.processNext = face; + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + if (positiveFirst != null) { + positiveLast.processNext = null; + if (positiveFirst.processNext != null) { + result = sortByDynamicBSP(positiveFirst, camera, threshold, result); + } else { + positiveFirst.processNext = result; + result = positiveFirst; + } + } + splitterLast.processNext = result; + result = splitter; + if (negativeFirst != null) { + negativeLast.processNext = null; + if (negativeFirst.processNext != null) { + result = sortByDynamicBSP(negativeFirst, camera, threshold, result); + } else { + negativeFirst.processNext = result; + result = negativeFirst; + } + } + return result; + } + + private function collectNode(tree:Face, result:Face = null):Face { + var last:Face; + var negative:Face; + var positive:Face; + if (tree.offset < 0) { + negative = tree.negative; + positive = tree.positive; + } else { + negative = tree.positive; + positive = tree.negative; + } + tree.negative = null; + tree.positive = null; + if (positive != null) result = collectNode(positive, result); + if (tree.wrapper != null) { + //for (last = tree; last.processNext != null; last = last.processNext); + last = tree; + while (last.processNext != null) last = last.processNext; + last.processNext = result; + result = tree; + } + if (negative != null) result = collectNode(negative, result); + return result; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/MipMapping.as b/Alternativa3D7/7.3/alternativa/engine3d/core/MipMapping.as new file mode 100644 index 0000000..18b2eae --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/MipMapping.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.core { + + public class MipMapping { + + /** + * Нет мипмаппинга. + */ + static public const NONE:int = 0; + /** + * Мипмаппинг по удалённости объекта от камеры. + */ + static public const OBJECT_DISTANCE:int = 1; + /** + * Мипмаппинг для каждой грани по удалённости от камеры. + */ + static public const PER_POLYGON_BY_DISTANCE:int = 2; + /** + * Мипмаппинг для каждой грани по перспективному искажению рёбер. + */ + static public const PER_POLYGON_BY_DISTORTION:int = 3; + /** + * Мипмаппинг для каждого пиксела по удалённости от камеры. + */ + static public const PER_PIXEL:int = 4; + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/MouseEvent3D.as b/Alternativa3D7/7.3/alternativa/engine3d/core/MouseEvent3D.as new file mode 100644 index 0000000..daefe33 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/MouseEvent3D.as @@ -0,0 +1,87 @@ +package alternativa.engine3d.core { + import flash.events.Event; + + public class MouseEvent3D extends Event { + /** + * Значение свойства type для объекта события click. + * @eventType click + */ + public static const CLICK:String = "click"; + /** + * Значение свойства type для объекта события doubleClick. + * @eventType doubleClick + */ + public static const DOUBLE_CLICK:String = "doubleClick"; + /** + * Значение свойства type для объекта события mouseDown. + * @eventType mouseDown + */ + public static const MOUSE_DOWN:String = "mouseDown"; + /** + * Значение свойства type для объекта события mouseUp. + * @eventType mouseUp + */ + public static const MOUSE_UP:String = "mouseUp"; + /** + * Значение свойства type для объекта события mouseOver. + * @eventType mouseOver + */ + public static const MOUSE_OVER:String = "mouseOver"; + /** + * Значение свойства type для объекта события mouseOut. + * @eventType mouseOut + */ + public static const MOUSE_OUT:String = "mouseOut"; + /** + * Значение свойства type для объекта события rollOver. + * @eventType rollOver + */ + public static const ROLL_OVER:String = "rollOver"; + /** + * Значение свойства type для объекта события rollOut. + * @eventType rollOut + */ + public static const ROLL_OUT:String = "rollOut"; + /** + * Значение свойства type для объекта события mouseMove. + * @eventType mouseMove + */ + public static const MOUSE_MOVE:String = "mouseMove"; + /** + * Значение свойства type для объекта события mouseWheel. + * @eventType mouseWheel + */ + public static const MOUSE_WHEEL:String = "mouseWheel"; + + /** + * Объект, с которым связано событие. + */ + private var _target:Object3D; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Alt. + */ + public var altKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Control. + */ + public var ctrlKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Shift. + */ + public var shiftKey:Boolean; + /** + * Количество линий прокрутки при вращении колеса мыши. + */ + public var delta:int; + + public function MouseEvent3D(type:String, target:Object3D, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) { + super(type); + _target = target; + } + + override public function get target():Object { + return _target; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Object3D.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..11f5007 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,601 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь последовательно 2 раза в нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Событие сработает только если время между первым и вторым кликом вписывается в заданный в системе временной интервал. + * @eventType alternativa.engine3d.events.MouseEvent3D.DOUBLE_CLICK + */ + [Event (name="doubleClick", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OVER + */ + [Event (name="rollOver", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OUT + */ + [Event (name="rollOut", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.core.MouseEvent3D")] + + /** + * Базовый трёхмерный объект + */ + public class Object3D { + + static private const boundVertexList:Vertex = Vertex.createList(8); + + public var name:String; + + public var x:Number = 0; + public var y:Number = 0; + public var z:Number = 0; + public var rotationX:Number = 0; + public var rotationY:Number = 0; + public var rotationZ:Number = 0; + public var scaleX:Number = 1; + public var scaleY:Number = 1; + public var scaleZ:Number = 1; + + public var visible:Boolean = true; + + public var alpha:Number = 1; + public var blendMode:String = "normal"; + public var colorTransform:ColorTransform = null; + public var filters:Array = null; + + public var interactiveAlpha:Number = 0; + public var mouseEnabled:Boolean = true; + public var mouseChildren:Boolean = true; + public var doubleClickEnabled:Boolean = false; + public var useHandCursor:Boolean = false; + + public var boundMinX:Number = -1e+22; + public var boundMinY:Number = -1e+22; + public var boundMinZ:Number = -1e+22; + public var boundMaxX:Number = 1e+22; + public var boundMaxY:Number = 1e+22; + public var boundMaxZ:Number = 1e+22; + + alternativa3d var _parent:Object3DContainer; + + alternativa3d var culling:int = 0; + + // Матрица + alternativa3d var ma:Number; + alternativa3d var mb:Number; + alternativa3d var mc:Number; + alternativa3d var md:Number; + alternativa3d var me:Number; + alternativa3d var mf:Number; + alternativa3d var mg:Number; + alternativa3d var mh:Number; + alternativa3d var mi:Number; + alternativa3d var mj:Number; + alternativa3d var mk:Number; + alternativa3d var ml:Number; + + // Инверсная матрица + alternativa3d var ima:Number; + alternativa3d var imb:Number; + alternativa3d var imc:Number; + alternativa3d var imd:Number; + alternativa3d var ime:Number; + alternativa3d var imf:Number; + alternativa3d var img:Number; + alternativa3d var imh:Number; + alternativa3d var imi:Number; + alternativa3d var imj:Number; + alternativa3d var imk:Number; + alternativa3d var iml:Number; + + alternativa3d var listeners:Object; + + public function get parent():Object3DContainer { + return _parent; + } + + public function getMatrix():Matrix3D { + var m:Matrix3D = new Matrix3D(); + var t:Vector3D = new Vector3D(x, y, z); + var r:Vector3D = new Vector3D(rotationX, rotationY, rotationZ); + var s:Vector3D = new Vector3D(scaleX, scaleY, scaleZ); + var v:Vector. = new Vector.(); + v[0] = t; + v[1] = r; + v[2] = s; + m.recompose(v); + return m; + } + + public function setMatrix(value:Matrix3D):void { + var v:Vector. = value.decompose(); + var t:Vector3D = v[0]; + var r:Vector3D = v[1]; + var s:Vector3D = v[2]; + x = t.x; + y = t.y; + z = t.z; + rotationX = r.x; + rotationY = r.y; + rotationZ = r.z; + scaleX = s.x; + scaleY = s.y; + scaleZ = s.z; + } + + public function get position():Vector3D { + return new Vector3D(x, y, z); + } + + public function set position(value:Vector3D):void { + x = value.x; + y = value.y; + z = value.z; + } + + public function calculateBounds():void { + // Выворачивание баунда + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + // Заполнение баунда + updateBounds(this, null); + // Если баунд вывернут + if (boundMinX > boundMaxX) { + boundMinX = -1e+22; + boundMinY = -1e+22; + boundMinZ = -1e+22; + boundMaxX = 1e+22; + boundMaxY = 1e+22; + boundMaxZ = 1e+22; + } + } + + alternativa3d function composeMatrix():void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX; + var sinXscaleY:Number = sinX*scaleY; + var cosXscaleY:Number = cosX*scaleY; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + ma = cosZ*cosYscaleX; + mb = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + mc = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + md = x; + me = sinZ*cosYscaleX; + mf = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + mg = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + mh = y; + mi = -sinY*scaleX; + mj = cosY*sinXscaleY; + mk = cosY*cosXscaleZ; + ml = z; + } + + alternativa3d function appendMatrix(object:Object3D):void { + var a:Number = ma; + var b:Number = mb; + var c:Number = mc; + var d:Number = md; + var e:Number = me; + var f:Number = mf; + var g:Number = mg; + var h:Number = mh; + var i:Number = mi; + var j:Number = mj; + var k:Number = mk; + var l:Number = ml; + ma = object.ma*a + object.mb*e + object.mc*i; + mb = object.ma*b + object.mb*f + object.mc*j; + mc = object.ma*c + object.mb*g + object.mc*k; + md = object.ma*d + object.mb*h + object.mc*l + object.md; + me = object.me*a + object.mf*e + object.mg*i; + mf = object.me*b + object.mf*f + object.mg*j; + mg = object.me*c + object.mf*g + object.mg*k; + mh = object.me*d + object.mf*h + object.mg*l + object.mh; + mi = object.mi*a + object.mj*e + object.mk*i; + mj = object.mi*b + object.mj*f + object.mk*j; + mk = object.mi*c + object.mj*g + object.mk*k; + ml = object.mi*d + object.mj*h + object.mk*l + object.ml; + } + + alternativa3d function composeAndAppend(object:Object3D):void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX; + var sinXscaleY:Number = sinX*scaleY; + var cosXscaleY:Number = cosX*scaleY; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + var a:Number = cosZ*cosYscaleX; + var b:Number = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + var c:Number = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + var d:Number = x; + var e:Number = sinZ*cosYscaleX; + var f:Number = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + var g:Number = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + var h:Number = y; + var i:Number = -sinY*scaleX; + var j:Number = cosY*sinXscaleY; + var k:Number = cosY*cosXscaleZ; + var l:Number = z; + ma = object.ma*a + object.mb*e + object.mc*i; + mb = object.ma*b + object.mb*f + object.mc*j; + mc = object.ma*c + object.mb*g + object.mc*k; + md = object.ma*d + object.mb*h + object.mc*l + object.md; + me = object.me*a + object.mf*e + object.mg*i; + mf = object.me*b + object.mf*f + object.mg*j; + mg = object.me*c + object.mf*g + object.mg*k; + mh = object.me*d + object.mf*h + object.mg*l + object.mh; + mi = object.mi*a + object.mj*e + object.mk*i; + mj = object.mi*b + object.mj*f + object.mk*j; + mk = object.mi*c + object.mj*g + object.mk*k; + ml = object.mi*d + object.mj*h + object.mk*l + object.ml; + } + + alternativa3d function calculateInverseMatrix(object:Object3D):void { + var a:Number = object.ma; + var b:Number = object.mb; + var c:Number = object.mc; + var d:Number = object.md; + var e:Number = object.me; + var f:Number = object.mf; + var g:Number = object.mg; + var h:Number = object.mh; + var i:Number = object.mi; + var j:Number = object.mj; + var k:Number = object.mk; + var l:Number = object.ml; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + ima = (-g*j + f*k)*det; + imb = (c*j - b*k)*det; + imc = (-c*f + b*g)*det; + imd = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + ime = (g*i - e*k)*det; + imf = (-c*i + a*k)*det; + img = (c*e - a*g)*det; + imh = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + imi = (-f*i + e*j)*det; + imj = (b*i - a*j)*det; + imk = (-b*e + a*f)*det; + iml = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + } + + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + } + + alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return null; + } + + alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + } + + alternativa3d function split(normalX:Number, normalY:Number, normalZ:Number, offset:Number, threshold:Number):Vector. { + return new Vector.(2); + } + + alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + if (camera.occludedAll) return -1; + var numOccluders:int = camera.numOccluders; + var vertex:Vertex; + // Расчёт точек баунда в координатах камеры + if (culling > 0 || numOccluders > 0) { + // Заполнение + vertex = boundVertexList; + vertex.x = boundMinX; + vertex.y = boundMinY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMinY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMaxY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMaxY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMinY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMinY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMaxY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMaxY; + vertex.z = boundMaxZ; + // Трансформация в камеру + for (vertex = boundVertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = object.ma*x + object.mb*y + object.mc*z + object.md; + vertex.cameraY = object.me*x + object.mf*y + object.mg*z + object.mh; + vertex.cameraZ = object.mi*x + object.mj*y + object.mk*z + object.ml; + } + } + // Куллинг + if (culling > 0) { + var infront:Boolean; + var behind:Boolean; + if (culling & 1) { + var near:Number = camera.nearClipping; + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ > near) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 62; + } + } + if (culling & 2) { + var far:Number = camera.farClipping; + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ < far) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 61; + } + } + if (culling & 4) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (-vertex.cameraX < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 59; + } + } + if (culling & 8) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraX < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 55; + } + } + if (culling & 16) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (-vertex.cameraY < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 47; + } + } + if (culling & 32) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraY < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 31; + } + } + } + // Окклюдинг + if (numOccluders > 0) { + for (var i:int = 0; i < numOccluders; i++) { + for (var plane:Vertex = camera.occluders[i]; plane != null; plane = plane.next) { + for (vertex = boundVertexList; vertex != null; vertex = vertex.next) { + if (plane.cameraX*vertex.cameraX + plane.cameraY*vertex.cameraY + plane.cameraZ*vertex.cameraZ >= 0) break; + } + if (vertex != null) break; + } + if (plane == null) return -1; + } + } + object.culling = culling; + return culling; + } + + public function clone():Object3D { + var res:Object3D = new Object3D(); + res.cloneBaseProperties(this); + return res; + } + + protected function cloneBaseProperties(source:Object3D):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + if (source.colorTransform != null) { + colorTransform = new ColorTransform(); + colorTransform.concat(source.colorTransform); + } + if (source.filters != null) { + filters = new Array().concat(source.filters); + } + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + } + + /** + * Добавляет обработчик события. + * @param type тип события + * @param listener обработчик события + */ + public function addEventListener(type:String, listener:Function):void { + if (listeners == null) listeners = new Object(); + var vector:Vector. = listeners[type]; + if (vector == null) { + vector = new Vector.(); + listeners[type] = vector; + } + if (vector.indexOf(listener) < 0) { + vector.push(listener); + } + } + + /** + * Удаляет обработчик события. + * @param type тип события + * @param listener обработчик события + */ + public function removeEventListener(type:String, listener:Function):void { + if (listeners != null) { + var vector:Vector. = listeners[type]; + if (vector != null) { + var i:int = vector.indexOf(listener); + if (i >= 0) { + var length:int = vector.length; + for (var j:int = i + 1; j < length; j++,i++) { + vector[i] = vector[j]; + } + if (length > 1) { + vector.length = length - 1; + } else { + delete listeners[type]; + var key:*; + for (key in listeners) break; + if (!key) listeners = null; + } + } + } + } + } + + /** + * Проверяет наличие зарегистрированных обработчиков события указанного типа. + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + return listeners != null && listeners[type]; + } + + public function toString():String { + var className:String = getQualifiedClassName(this); + return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]"; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Object3DContainer.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Object3DContainer.as new file mode 100644 index 0000000..8cad8f9 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Object3DContainer.as @@ -0,0 +1,191 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + /** + * Базовый контейнер трёхмерных объектов. + * Логика контейнеров и child-parent-отношений идентична логике + * displayObject'ов во Flash. + */ + public class Object3DContainer extends Object3D { + + protected var children:Vector. = new Vector.(); + protected var _numChildren:int = 0; + + protected var visibleChildren:Vector. = new Vector.(); + protected var numVisibleChildren:int = 0; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Сбор видимых объектов + numVisibleChildren = 0; + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible) { + child.composeAndAppend(object); + if (child.cullingInCamera(camera, child, object.culling) >= 0) { + visibleChildren[numVisibleChildren] = child; + numVisibleChildren++; + } + } + } + // Если есть видимые объекты + if (numVisibleChildren > 0) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, canvas); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + for (var i:int = numVisibleChildren - 1; i >= 0; i--) { + var child:Object3D = visibleChildren[i]; + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var i:int; + var first:Geometry; + var last:Geometry; + var geometry:Geometry; + for (i = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (child.visible) { + child.composeAndAppend(object); + if (child.cullingInCamera(camera, child, object.culling) >= 0) { + geometry = child.getGeometry(camera, child); + if (geometry != null) { + if (first != null) { + last.next = geometry; + } else { + first = geometry; + last = geometry; + } + while (last.next != null) { + last = last.next; + } + } + } + } + } + if (object.alpha != 1) { + geometry = first; + while (geometry != null) { + geometry.alpha *= object.alpha; + geometry = geometry.next; + } + } + if (object.blendMode != "normal") { + geometry = first; + while (geometry != null) { + if (geometry.blendMode == "normal") { + geometry.blendMode = object.blendMode; + } + geometry = geometry.next; + } + } + if (object.colorTransform != null) { + geometry = first; + while (geometry != null) { + if (geometry.colorTransform != null) { + var ct:ColorTransform = new ColorTransform(object.colorTransform.redMultiplier, object.colorTransform.greenMultiplier, object.colorTransform.blueMultiplier, object.colorTransform.alphaMultiplier, object.colorTransform.redOffset, object.colorTransform.greenOffset, object.colorTransform.blueOffset, object.colorTransform.alphaOffset); + ct.concat(geometry.colorTransform); + geometry.colorTransform = ct; + } else { + geometry.colorTransform = object.colorTransform; + } + geometry = geometry.next; + } + } + if (object.filters != null) { + geometry = first; + while (geometry != null) { + if (geometry.filters != null) { + var fs:Array = new Array(); + var fsLength:int = 0; + var num:int = geometry.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = geometry.filters[i]; + fsLength++; + } + num = object.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = object.filters[i]; + fsLength++; + } + geometry.filters = fs; + } else { + geometry.filters = object.filters; + } + geometry = geometry.next; + } + } + return first; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var i:int = 0; i < _numChildren; i++) { + var child:Object3D = children[i]; + if (transformation != null) { + child.composeAndAppend(transformation); + } else { + child.composeMatrix(); + } + child.updateBounds(bounds, child); + } + } + + public function addChild(child:Object3D):void { + children[_numChildren] = child; + child._parent = this; + _numChildren++; + } + + public function removeChild(child:Object3D):void { + var i:int = children.indexOf(child); + if (i < 0) throw new ArgumentError("Child not found"); + _numChildren--; + var j:int = i + 1; + ; + while (i < _numChildren) { + children[i] = children[j]; + i++; + j++; + } + children.length = _numChildren; + child._parent = null; + } + + public function hasChild(child:Object3D):Boolean { + return children.indexOf(child) > -1; + } + + public function getChildAt(index:uint):Object3D { + return children[index]; + } + + public function get numChildren():uint { + return _numChildren; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Sorting.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Sorting.as new file mode 100644 index 0000000..7512c9a --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Sorting.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + public class Sorting { + + /** + * Грани не сортируются. + */ + static public const NONE:int = 0; + /** + * Грани сортируются по средним Z. + */ + static public const AVERAGE_Z:int = 1; + /** + * Грани при отрисовке образуют BSP-дерево. + */ + static public const DYNAMIC_BSP:int = 2; + /** + * Грани находятся в BSP-дереве. + */ + static public const STATIC_BSP:int = 3; + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Vertex.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..b3b1be4 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,78 @@ +package alternativa.engine3d.core { + + public class Vertex { + + public var next:Vertex; + + public var x:Number = 0; + public var y:Number = 0; + public var z:Number = 0; + + public var u:Number = 0; + public var v:Number = 0; + + public var cameraX:Number; + public var cameraY:Number; + public var cameraZ:Number; + + public var offset:Number; + + public var transformID:int = 0; + public var drawID:int = 0; + + public var index:int; + + public var value:Vertex; + + static public var collector:Vertex; + + static public function createList(num:int):Vertex { + var res:Vertex = collector; + var last:Vertex; + if (res != null) { + for (last = res; num > 1; last = last.next,num--) { + last.transformID = 0; + last.drawID = 0; + if (last.next == null) { + //for (; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--); + while (num > 1) { + last.next = new Vertex(); + last = last.next; + num--; + } + break; + } + } + collector = last.next; + last.transformID = 0; + last.drawID = 0; + last.next = null; + } else { + //for (res = new Vertex(), /*trace("new Vertex"), */last = res; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--); + res = new Vertex(); + last = res; + while (num > 1) { + last.next = new Vertex(); + last = last.next; + num--; + } + } + return res; + } + + public function create():Vertex { + if (collector != null) { + var res:Vertex = collector; + collector = res.next; + res.next = null; + res.transformID = 0; + res.drawID = 0; + return res; + } else { + //trace("new Vertex"); + return new Vertex(); + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/View.as b/Alternativa3D7/7.3/alternativa/engine3d/core/View.as new file mode 100644 index 0000000..876ace8 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/View.as @@ -0,0 +1,328 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.ColorTransform; + import flash.geom.Point; + + use namespace alternativa3d; + + public class View extends Canvas { + + static private const mouse:Point = new Point(); + static private const listeners:Vector. = new Vector.(); + + alternativa3d var _width:Number; + alternativa3d var _height:Number; + + alternativa3d var _interactive:Boolean = false; + + private var altKey:Boolean; + private var ctrlKey:Boolean; + private var shiftKey:Boolean; + + private var pressedObject:Object3D; + private var clickedObject:Object3D; + private var overedObject:Object3D; + private var overedBranch:Vector. = new Vector.(); + + public function View(width:Number, height:Number, interactive:Boolean = false) { + _width = width; + _height = height; + _interactive = interactive; + mouseEnabled = false; + mouseChildren = false; + tabEnabled = false; + tabChildren = false; + + addEventListener(Event.ADDED_TO_STAGE, onAddToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemoveFromStage); + } + + private function onAddToStage(e:Event):void { + if (_interactive) addListeners(); + } + + private function onRemoveFromStage(e:Event):void { + if (_interactive) removeListeners(); + } + + public function get interactive():Boolean { + return _interactive; + } + + public function set interactive(value:Boolean):void { + if (_interactive != value) { + if (stage != null) { + if (value) { + addListeners(); + } else { + removeListeners(); + } + } + _interactive = value; + } + } + + private function addListeners():void { + stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + stage.addEventListener(MouseEvent.CLICK, onClick); + stage.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); + stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + stage.addEventListener(KeyboardEvent.KEY_UP, onKey); + } + + private function removeListeners():void { + stage.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + stage.removeEventListener(MouseEvent.CLICK, onClick); + stage.removeEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); + stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + stage.removeEventListener(KeyboardEvent.KEY_UP, onKey); + pressedObject = null; + clickedObject = null; + overedObject = null; + overedBranch.length = 0; + } + + private function onMouseDown(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + pressedObject = targetCanvas.interactiveObject; + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_DOWN, targetCanvas); + } + } + + private function onClick(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_UP, targetCanvas); + if (pressedObject == targetCanvas.interactiveObject) { + clickedObject = targetCanvas.interactiveObject; + dispatchEventToCanvasHierarchy(MouseEvent3D.CLICK, targetCanvas); + } + } + pressedObject = null; + } + + private function onDoubleClick(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_UP, targetCanvas); + if (pressedObject == targetCanvas.interactiveObject) { + dispatchEventToCanvasHierarchy(clickedObject == targetCanvas.interactiveObject && targetCanvas.interactiveObject.doubleClickEnabled ? MouseEvent3D.DOUBLE_CLICK : MouseEvent3D.CLICK, targetCanvas); + } + } + clickedObject = null; + pressedObject = null; + } + + alternativa3d function onMouseMove(mouseEvent:MouseEvent = null):void { + if (mouseEvent != null) { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + } + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + var i:int; + var canvas:Canvas; + var object:Object3D; + if (overedObject != targetCanvas.interactiveObject) { + if (overedObject != null) { + var length:int = overedBranch.length; + dispatchEventToObjectHierarchy(MouseEvent3D.MOUSE_OUT); + for (i = 0; i < length; i++) { + object = overedBranch[i]; + canvas = targetCanvas; + while (canvas != this) { + if (object == canvas.interactiveObject) break; + canvas = Canvas(canvas.parent); + } + if (canvas == this) { + dispatchEvent3D(object, MouseEvent3D.ROLL_OUT, object); + } + } + canvas = targetCanvas; + while (canvas != this) { + object = canvas.interactiveObject; + for (i = 0; i < length; i++) { + if (object == overedBranch[i]) break; + } + if (i == length) { + dispatchEvent3D(object, MouseEvent3D.ROLL_OVER, object); + } + canvas = Canvas(canvas.parent); + } + } else { + dispatchEventToCanvasHierarchy(MouseEvent3D.ROLL_OVER, targetCanvas); + } + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_OVER, targetCanvas); + } + if (mouseEvent != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_MOVE, targetCanvas); + } + i = 0; + canvas = targetCanvas; + while (canvas != this) { + overedBranch[i] = canvas.interactiveObject; + i++; + canvas = Canvas(canvas.parent); + } + overedObject = targetCanvas.interactiveObject; + overedBranch.length = i; + } else if (overedObject != null) { + dispatchEventToObjectHierarchy(MouseEvent3D.MOUSE_OUT); + dispatchEventToObjectHierarchy(MouseEvent3D.ROLL_OUT); + overedObject = null; + overedBranch.length = 0; + } + } + + private function onMouseWheel(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_WHEEL, targetCanvas, mouseEvent.delta); + } + } + + private function dispatchEventToCanvasHierarchy(type:String, canvas:Canvas, delta:int = 0):void { + var target:Object3D = canvas.interactiveObject; + while (canvas != this) { + dispatchEvent3D(canvas.interactiveObject, type, target, delta); + canvas = Canvas(canvas.parent); + } + } + + private function dispatchEventToObjectHierarchy(type:String, delta:int = 0):void { + var target:Object3D = overedBranch[0]; + var length:int = overedBranch.length; + for (var i:int = 0; i < length; i++) { + dispatchEvent3D(overedBranch[i], type, target, delta); + } + } + + private function dispatchEvent3D(object:Object3D, type:String, target:Object3D, delta:int = 0):void { + if (object.listeners != null) { + var vector:Vector. = object.listeners[type]; + if (vector != null) { + var i:int; + var length:int = vector.length; + for (i = 0; i < length; i++) { + listeners[i] = vector[i]; + } + for (i = 0; i < length; i++) { + (listeners[i] as Function).call(null, new MouseEvent3D(type, target, altKey, ctrlKey, shiftKey, delta)); + } + } + } + } + + private function defineTargetCanvas():Canvas { + // Если мышь внутри области вьюпорта + if (mouseX >= 0 && mouseY >= 0 && mouseX <= _width && mouseY <= _height) { + // Получение объектов под мышью + mouse.x = stage.mouseX; + mouse.y = stage.mouseY; + var displayObjects:Array = stage.getObjectsUnderPoint(mouse); + // Перебор объектов + for (var i:int = displayObjects.length - 1; i >= 0; i--) { + var displayObject:DisplayObject = displayObjects[i]; + // Поиск канваса + while (displayObject != null && !(displayObject is Canvas)) { + displayObject = displayObject.parent; + } + // Если канвас найден + if (displayObject != null) { + var canvas:Canvas = Canvas(displayObject); + // Проверка на прозрачность + if (canvas.interactiveObject.interactiveAlpha <= 1) { + // TODO: проверка порога альфы if (canvas.interactiveObject.interactiveAlpha > 0) + // Определение целевого объекта + var target:Canvas = null; + while (canvas != this) { + if (!canvas.interactiveObject.mouseChildren) { + target = null; + } + if (target == null && canvas.interactiveObject.mouseEnabled) { + target = canvas; + } + canvas = Canvas(canvas.parent); + } + if (target != null) { + return target; + } + } + } else { + // TODO: тут должна быть проверка на интерактивность, чтобы понять перекрывает что-то вьюпорт или нет + } + } + } + return null; + } + + private function onKey(keyboardEvent:KeyboardEvent):void { + altKey = keyboardEvent.altKey; + ctrlKey = keyboardEvent.ctrlKey; + shiftKey = keyboardEvent.shiftKey; + } + + override public function get width():Number { + return _width; + } + + override public function set width(value:Number):void { + _width = value; + } + + override public function get height():Number { + return _height; + } + + override public function set height(value:Number):void { + _height = value; + } + + public function clear():void { + removeChildren(0); + numDraws = 0; + } + + override alternativa3d function getChildCanvas(interactiveObject:Object3D, useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas = super.getChildCanvas(interactiveObject, useGraphics, useChildren, alpha, blendMode, colorTransform, filters); + canvas.x = _width/2; + canvas.y = _height/2; + return canvas; + } + + override alternativa3d function removeChildren(keep:int):void { + for (var i:int = 0; i < _numChildren - keep; i++) { + var canvas:Canvas = getChildAt(i) as Canvas; + if (canvas != null) { + canvas.x = 0; + canvas.y = 0; + } + } + super.removeChildren(keep); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/core/Wrapper.as b/Alternativa3D7/7.3/alternativa/engine3d/core/Wrapper.as new file mode 100644 index 0000000..7ba913c --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/core/Wrapper.as @@ -0,0 +1,36 @@ +package alternativa.engine3d.core { + + public class Wrapper { + + public var next:Wrapper; + + public var vertex:Vertex; + + static public var collector:Wrapper; + + static public function create():Wrapper { + if (collector != null) { + var res:Wrapper = collector; + collector = collector.next; + res.next = null; + return res; + } else { + //trace("new Wrapper"); + return new Wrapper(); + } + } + + public function create():Wrapper { + if (collector != null) { + var res:Wrapper = collector; + collector = collector.next; + res.next = null; + return res; + } else { + //trace("new Wrapper"); + return new Wrapper(); + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/MaterialLoader.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/MaterialLoader.as new file mode 100644 index 0000000..3663dc5 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/MaterialLoader.as @@ -0,0 +1,232 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderErrorEvent; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + import alternativa.engine3d.materials.TextureMaterial; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Рассылается вначале загрузки очередного материала. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается после окончания этапа очередного материала. + * В событии в свойстве target содержится загруженный материал. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается после окончания загрузки всех материалов. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Рассылается, если в процессе загрузки возникает ошибка. + * + * @eventType alternativa.engine3d.loaders.events.LoaderErrorEvent.LOADER_ERROR + */ + [Event (name="loaderError", type="alternativa.engine3d.loaders.events.LoaderErrorEvent")] + + /** + * Загрузчик текстур материалов + */ + public class MaterialLoader extends EventDispatcher { + + static private var stub:BitmapData; + + private var loader:Loader; + private var context:LoaderContext; + + private var materials:Vector.; + private var urls:Vector.; + private var filesTotal:int; + private var filesLoaded:int; + + private var diffuse:BitmapData; + private var currentURL:String; + private var index:int; + + /** + * Начинает загрузку текстур материалов + * + * @param materials список материалов для загрузки их текстур + * @param context + */ + public function load(materials:Vector., context:LoaderContext = null):void { + this.context = context; + this.materials = materials; + urls = new Vector.(); + for (var i:int = 0, j:int = 0; i < materials.length; i++) { + var material:TextureMaterial = materials[i]; + urls[j++] = material.diffuseMapURL; + filesTotal++; + if (material.opacityMapURL != null) { + urls[j++] = material.opacityMapURL; + filesTotal++; + } else { + urls[j++] = null; + } + } + filesLoaded = 0; + index = -1; + loadNext(null); + } + + /** + * Останавливает загрузку и выполняет зачистку загрузчика. + */ + public function close():void { + destroyLoader(); + materials = null; + urls = null; + diffuse = null; + currentURL = null; + context = null; + } + + private function destroyLoader():void { + if (loader != null) { + loader.unload(); + loader.contentLoaderInfo.removeEventListener(Event.OPEN, onPartOpen); + loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadNext); + loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, onFileProgress); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.DISK_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.NETWORK_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.VERIFY_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext); + loader = null; + } + } + + private function loadNext(e:Event):void { + if (index >= 0) { + if (index % 2 == 0) { + // Завершение загрузки диффузии + if (e is ErrorEvent) { + diffuse = getStub(); + onFileError((e as ErrorEvent).text); + } else { + diffuse = (loader.content as Bitmap).bitmapData; + } + filesLoaded++; + } else { + // Завершение загрузки альфы + var material:TextureMaterial = materials[(index - 1) >> 1]; + if (e == null) { + material.texture = diffuse; + } else { + if (e is ErrorEvent) { + material.texture = diffuse; + onFileError((e as ErrorEvent).text); + } else { + material.texture = merge(diffuse, (loader.content as Bitmap).bitmapData); + } + filesLoaded++; + } + onPartComplete((index - 1) >> 1, material); + diffuse = null; + } + destroyLoader(); + } + if (++index >= urls.length) { + // Завершение всей загрузки + close(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } else { + // Загрузка следующего файла + currentURL = urls[index]; + if (currentURL != null && (diffuse == null || diffuse != stub)) { + loader = new Loader(); + if (index % 2 == 0) { + loader.contentLoaderInfo.addEventListener(Event.OPEN, onPartOpen); + } + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNext); + loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onFileProgress); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.DISK_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext); + loader.load(new URLRequest(currentURL), context); + } else { + loadNext(null); + } + } + } + + private function onPartOpen(e:Event):void { + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, urls.length >> 1, index >> 1, materials[index >> 1])); + } + } + + private function onPartComplete(partsLoaded:int, material:TextureMaterial):void { + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, urls.length >> 1, partsLoaded, material)); + } + } + + private function onFileProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, filesTotal, filesLoaded, (filesLoaded + e.bytesLoaded/e.bytesTotal)/filesTotal, e.bytesLoaded, e.bytesTotal)); + } + } + + private function onFileError(text:String):void { + if (hasEventListener(LoaderErrorEvent.LOADER_ERROR)) { + dispatchEvent(new LoaderErrorEvent(LoaderErrorEvent.LOADER_ERROR, currentURL, text)); + } + } + + private function merge(diffuse:BitmapData, alpha:BitmapData):BitmapData { + var res:BitmapData = new BitmapData(diffuse.width, diffuse.height); + res.copyPixels(diffuse, diffuse.rect, new Point()); + if (diffuse.width != alpha.width || diffuse.height != alpha.height) { + diffuse.draw(alpha, new Matrix(diffuse.width/alpha.width, 0, 0, diffuse.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = diffuse; + } else { + diffuse.dispose(); + } + res.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + return res; + } + + private function getStub():BitmapData { + if (stub == null) { + var size:uint = 20; + stub = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stub.setPixel((i % 2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stub; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..058ab57 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,780 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.objects.Mesh; + + import flash.geom.Matrix; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + public class Parser3DS { + + private static const CHUNK_MAIN:int = 0x4D4D; + private static const CHUNK_VERSION:int = 0x0002; + private static const CHUNK_SCENE:int = 0x3D3D; + private static const CHUNK_ANIMATION:int = 0xB000; + private static const CHUNK_OBJECT:int = 0x4000; + private static const CHUNK_TRIMESH:int = 0x4100; + private static const CHUNK_VERTICES:int = 0x4110; + private static const CHUNK_FACES:int = 0x4120; + private static const CHUNK_FACESMATERIAL:int = 0x4130; + private static const CHUNK_MAPPINGCOORDS:int = 0x4140; + //private static const CHUNK_OBJECTCOLOR:int = 0x4165; + private static const CHUNK_TRANSFORMATION:int = 0x4160; + //private static const CHUNK_MESHANIMATION:int = 0xB002; + private static const CHUNK_MATERIAL:int = 0xAFFF; + + private var data:ByteArray; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Object; + + public var objects:Vector.; + public var parents:Vector.; + public var materials:Vector.; + public var textureMaterials:Vector.; + + public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1):void { + if (data.bytesAvailable < 6) return; + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(data.position, data.bytesAvailable); + objects = new Vector.(); + parents = new Vector.(); + materials = new Vector.(); + textureMaterials = new Vector.(); + buildContent(texturesBaseURL, scale); + data = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + private function readChunkInfo(dataPosition:int):ChunkInfo { + data.position = dataPosition; + var chunkInfo:ChunkInfo = new ChunkInfo(); + chunkInfo.id = data.readUnsignedShort(); + chunkInfo.size = data.readUnsignedInt(); + chunkInfo.dataSize = chunkInfo.size - 6; + chunkInfo.dataPosition = data.position; + chunkInfo.nextChunkPosition = dataPosition + chunkInfo.size; + return chunkInfo; + } + + private function parse3DSChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + data.position = dataPosition; + switch (chunkInfo.id) { + // Главный + case CHUNK_MAIN: + parseMainChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + parse3DSChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMainChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Версия + case CHUNK_VERSION: + //version = data.readUnsignedInt(); + break; + // 3D-сцена + case CHUNK_SCENE: + parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Анимация + case CHUNK_ANIMATION: + parseAnimationChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + parseMainChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parse3DChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Материал + case CHUNK_MATERIAL: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Объект + case CHUNK_OBJECT: + parseObject(chunkInfo); + break; + } + parse3DChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseObject(chunkInfo:ChunkInfo):void { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + // Получаем название объекта + object.name = getString(chunkInfo.dataPosition); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + // Парсим объект + var offset:int = object.name.length + 1; + parseObjectChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + private function parseObjectChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Меш + case CHUNK_TRIMESH: + parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + parseObjectChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMeshChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Вершины + case CHUNK_VERTICES: + parseVertices(object); + break; + // UV + case CHUNK_MAPPINGCOORDS: + parseUVs(object); + break; + // Трансформация + case CHUNK_TRANSFORMATION: + parseMatrix(object); + break; + // Грани + case CHUNK_FACES: + parseFaces(object, chunkInfo); + break; + } + parseMeshChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseVertices(object:ObjectData):void { + var num:int = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.vertices[j++] = data.readFloat(); + object.vertices[j++] = data.readFloat(); + object.vertices[j++] = data.readFloat(); + } + } + + private function parseUVs(object:ObjectData):void { + var num:int = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.uvs[j++] = data.readFloat(); + object.uvs[j++] = data.readFloat(); + } + } + + private function parseMatrix(object:ObjectData):void { + object.a = data.readFloat(); + object.e = data.readFloat(); + object.i = data.readFloat(); + object.b = data.readFloat(); + object.f = data.readFloat(); + object.j = data.readFloat(); + object.c = data.readFloat(); + object.g = data.readFloat(); + object.k = data.readFloat(); + object.d = data.readFloat(); + object.h = data.readFloat(); + object.l = data.readFloat(); + } + + private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void { + var num:int = data.readUnsignedShort(); + object.faces = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.faces[j++] = data.readUnsignedShort(); + object.faces[j++] = data.readUnsignedShort(); + object.faces[j++] = data.readUnsignedShort(); + data.position += 2; // Пропускаем флаг отрисовки рёбер + } + var offset:int = 2 + 8*num; + parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + private function parseFacesChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Поверхности + case CHUNK_FACESMATERIAL: + parseSurface(object); + break; + } + parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseSurface(object:ObjectData):void { + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Создаём данные поверхности + var surface:Vector. = new Vector.; + // Помещаем данные поверхности в список + object.surfaces[getString(data.position)] = surface; + // Получаем грани поверхности + var num:int = data.readUnsignedShort(); + for (var i:int = 0; i < num; i++) { + surface[i] = data.readUnsignedShort(); + } + } + + private function parseAnimationChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + if (animationDatas == null) { + animationDatas = new Array(); + } + var animation:AnimationData = new AnimationData(); + animationDatas.push(animation); + parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Таймлайн + case 0xB008: + break; + } + parseAnimationChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Идентификация объекта и его связь + case 0xB010: + // Имя объекта + animation.objectName = getString(data.position); + data.position += 4; + // Индекс родительского объекта в линейном списке объектов сцены + animation.parentIndex = data.readUnsignedShort(); + break; + // Имя dummy объекта + case 0xB011: + animation.objectName = getString(data.position); + break; + // Точка привязки объекта (pivot) + case 0xB013: + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Смещение объекта относительно родителя + case 0xB020: + data.position += 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + data.position += 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + data.position += 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + } + parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMaterialChunk(material:MaterialData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Имя материала + case 0xA000: + parseMaterialName(material); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = chunkInfo.dataPosition + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = chunkInfo.dataPosition + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = chunkInfo.dataPosition + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = chunkInfo.dataPosition + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Bump map + case 0xA230: + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMaterialName(material:MaterialData):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Object(); + } + // Получаем название материала + material.name = getString(data.position); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Имя файла + case 0xA300: + map.filename = getString(chunkInfo.dataPosition).toLowerCase(); + break; + case 0xA351: + // Параметры текстурирования + //trace("MAP OPTIONS", data.readShort().toString(2)); + break; + // Масштаб по U + case 0xA354: + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + map.rotation = data.readFloat(); + break; + } + parseMapChunk(materialName, map, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function buildContent(texturesBaseURL:String, scale:Number):void { + // Расчёт матриц текстурных материалов + for (var materialName:String in materialDatas) { + var materialData:MaterialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + if (mapData != null) { + var materialMatrix:Matrix = new Matrix(); + var rot:Number = mapData.rotation*Math.PI/180; + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + materialData.matrix = materialMatrix; + var textureMaterial:TextureMaterial = new TextureMaterial(); + textureMaterial.name = materialName; + textureMaterial.diffuseMapURL = texturesBaseURL + mapData.filename; + textureMaterial.opacityMapURL = (materialData.opacityMap != null) ? (texturesBaseURL + materialData.opacityMap.filename) : null; + materialData.material = textureMaterial; + textureMaterial.name = materialData.name; + textureMaterials.push(textureMaterial); + } else { + var fillMaterial:FillMaterial = new FillMaterial(materialData.color); + materialData.material = fillMaterial; + fillMaterial.name = materialData.name; + } + materials.push(materialData.material); + } + var objectName:String; + var objectData:ObjectData; + var object:Object3D; + // В сцене есть иерархически связанные оьъекты и (или) указаны данные о трансформации объектов. + if (animationDatas != null) { + if (objectDatas != null) { + var i:int; + var length:int = animationDatas.length; + var animationData:AnimationData; + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + // Проверка на инстансы + if (objectData != null) { + for (var j:int = i + 1, nameCounter:int = 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (!animationData2.isInstance && objectName == animationData2.objectName) { + // Найдено совпадение имени объекта в проверяемой секции анимации. Создаём референс. + var newObjectData:ObjectData = new ObjectData(); + var newName:String = objectName + nameCounter++; + newObjectData.name = newName; + objectDatas[newName] = newObjectData; + animationData2.objectName = newName; + newObjectData.vertices = objectData.vertices; + newObjectData.uvs = objectData.uvs; + newObjectData.faces = objectData.faces; + newObjectData.surfaces = objectData.surfaces; + newObjectData.a = objectData.a; + newObjectData.b = objectData.b; + newObjectData.c = objectData.c; + newObjectData.d = objectData.d; + newObjectData.e = objectData.e; + newObjectData.f = objectData.f; + newObjectData.g = objectData.g; + newObjectData.h = objectData.h; + newObjectData.i = objectData.i; + newObjectData.j = objectData.j; + newObjectData.k = objectData.k; + newObjectData.l = objectData.l; + } + } + } + // Если меш + if (objectData != null && objectData.vertices != null) { + // Создание полигонального объекта + object = new Mesh(); + buildMesh(object as Mesh, objectData, animationData, scale); + } else { + // Создание пустого 3д-объекта + object = new Object3D(); + } + object.name = objectName; + animationData.object = object; + if (animationData.position != null) { + object.x = animationData.position.x*scale; + object.y = animationData.position.y*scale; + object.z = animationData.position.z*scale; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + } + // Добавление объектов + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objects.push(animationData.object); + parents.push((animationData.parentIndex == 0xFFFF) ? null : AnimationData(animationDatas[animationData.parentIndex]).object); + } + } + // В сцене нет иерархически связанных объектов и не заданы трансформации для объектов. В контейнер добавляются только полигональные объекты. + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + object = new Mesh(); + object.name = objectName; + buildMesh(object as Mesh, objectData, null, scale); + objects.push(object); + parents.push(null); + } + } + } + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, scale:Number):void { + var vertices:Vector. = new Vector.(); + var faces:Vector. = new Vector.(); + var numVertices:int = 0; + var numFaces:int = 0; + var n:int; + var m:int; + var face:Face; + var vertex:Vertex; + var correct:Boolean = false; + if (animationData != null) { + var a:Number = objectData.a; + var b:Number = objectData.b; + var c:Number = objectData.c; + var d:Number = objectData.d; + var e:Number = objectData.e; + var f:Number = objectData.f; + var g:Number = objectData.g; + var h:Number = objectData.h; + var i:Number = objectData.i; + var j:Number = objectData.j; + var k:Number = objectData.k; + var l:Number = objectData.l; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + objectData.a = (-g*j + f*k)*det; + objectData.b = (c*j - b*k)*det; + objectData.c = (-c*f + b*g)*det; + objectData.d = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + objectData.e = (g*i - e*k)*det; + objectData.f = (-c*i + a*k)*det; + objectData.g = (c*e - a*g)*det; + objectData.h = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + objectData.i = (-f*i + e*j)*det; + objectData.j = (b*i - a*j)*det; + objectData.k = (-b*e + a*f)*det; + objectData.l = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + if (animationData.pivot != null) { + objectData.d -= animationData.pivot.x; + objectData.h -= animationData.pivot.y; + objectData.l -= animationData.pivot.z; + } + correct = true; + } + // Создание и корректировка вершин + for (n = 0,m = 0; n < objectData.vertices.length;) { + vertex = new Vertex(); + if (correct) { + var x:Number = objectData.vertices[n++]; + var y:Number = objectData.vertices[n++]; + var z:Number = objectData.vertices[n++]; + vertex.x = objectData.a*x + objectData.b*y + objectData.c*z + objectData.d; + vertex.y = objectData.e*x + objectData.f*y + objectData.g*z + objectData.h; + vertex.z = objectData.i*x + objectData.j*y + objectData.k*z + objectData.l; + } else { + vertex.x = objectData.vertices[n++]; + vertex.y = objectData.vertices[n++]; + vertex.z = objectData.vertices[n++]; + } + vertex.x *= scale; + vertex.y *= scale; + vertex.z *= scale; + vertex.u = objectData.uvs[m++]; + vertex.v = 1 - objectData.uvs[m++]; + vertex.transformID = -1; + vertices[numVertices++] = vertex; + vertex.next = mesh.vertexList; + mesh.vertexList = vertex; + } + // Создание граней + var last:Face; + for (n = 0; n < objectData.faces.length;) { + face = new Face(); + face.wrapper = new Wrapper(); + face.wrapper.next = new Wrapper(); + face.wrapper.next.next = new Wrapper(); + face.wrapper.vertex = vertices[objectData.faces[n++]]; + face.wrapper.next.vertex = vertices[objectData.faces[n++]]; + face.wrapper.next.next.vertex = vertices[objectData.faces[n++]]; + faces[numFaces++] = face; + if (last != null) { + last.next = face; + } else { + mesh.faceList = face; + } + last = face; + } + // Назначение материалов + if (objectData.surfaces != null) { + for (var key:String in objectData.surfaces) { + var surface:Vector. = objectData.surfaces[key]; + var materialData:MaterialData = materialDatas[key]; + var material:Material = materialData.material; + for (n = 0; n < surface.length; n++) { + face = faces[surface[n]]; + face.material = material; + // Коррекция UV-координат + if (materialData.matrix != null) { + for (var w:Wrapper = face.wrapper; w != null; w = w.next) { + vertex = w.vertex; + if (vertex.transformID < 0) { + var u:Number = vertex.u; + var v:Number = vertex.v; + vertex.u = materialData.matrix.a*u + materialData.matrix.b*v + materialData.matrix.tx; + vertex.v = materialData.matrix.c*u + materialData.matrix.d*v + materialData.matrix.ty; + vertex.transformID = 0; + } + } + } + } + } + } + // Назначение материала по-умолчанию для граней без поверхностей + var defaultMaterial:FillMaterial = new FillMaterial(0x7F7F7F); + defaultMaterial.name = "default"; + for (face = mesh.faceList; face != null; face = face.next) { + if (face.material == null) { + face.material = defaultMaterial; + } + } + // Расчёт нормалей + mesh.calculateNormals(true); + mesh.calculateBounds(); + } + + private function getString(index:int):String { + data.position = index; + var charCode:int; + var res:String = ""; + while ((charCode = data.readByte()) != 0) { + res += String.fromCharCode(charCode); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k <= -1) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.engine3d.materials.Material; + +import flash.geom.Matrix; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var color:int; + public var specular:int; + public var glossiness:int; + public var transparency:int; + public var diffuseMap:MapData; + public var opacityMap:MapData; + public var matrix:Matrix; + public var material:Material; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var faces:Vector.; + public var surfaces:Object; + public var a:Number; + public var b:Number; + public var c:Number; + public var d:Number; + public var e:Number; + public var f:Number; + public var g:Number; + public var h:Number; + public var i:Number; + public var j:Number; + public var k:Number; + public var l:Number; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:int; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; + public var isInstance:Boolean; +} + +class ChunkInfo { + public var id:int; + public var size:int; + public var dataSize:int; + public var dataPosition:int; + public var nextChunkPosition:int; +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/ParserCollada.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/ParserCollada.as new file mode 100644 index 0000000..2e0d9f4 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/ParserCollada.as @@ -0,0 +1,509 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.loaders.collada.DaeAnimatedObject; + import alternativa.engine3d.loaders.collada.DaeDocument; + import alternativa.engine3d.loaders.collada.DaeMaterial; + import alternativa.engine3d.loaders.collada.DaeNode; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + + import flash.geom.Matrix3D; + import flash.utils.Dictionary; + + /** + * Класс выполняет парсинг xml коллады. + */ + public class ParserCollada { + + /** + * Список объектов из коллады + */ + public var objects:Vector.; + /** + * Список родителей объектов из коллады. Количество и порядок расположения элементов соответствует массиву objects. + * + * @see #objects + */ + public var parents:Vector.; + /** + * Список камер из коллады + */ + public var cameras:Vector.; + /** + * Список всех материалов из коллады + * + * @see #textureMaterials + */ + public var materials:Vector.; + /** + * Список текстурных материалов из коллады. + * Можно использовать класс MaterialLoader для загрузки текстур этих материалов. + * + * @see #materials + * @see MaterialLoader + */ + public var textureMaterials:Vector.; + + // /** + // * Корневые объекты. Массив доступен после создания иерархии. + // * @see #makeHierarchyForStatic() + // * @see #makeHierarchyForAnimation() + // */ + // public var hierarchy:Vector.; + + /** + * Массив анимационных контроллеров. Количество и порядок расположения элементов соответствует массиву objects. + */ + public var animations:Vector.; + + /** + * Создает экземпляр парсера + */ + public function ParserCollada() { + } + + /** + * Зачищает все ссылки на внешние объекты. + */ + public function clean():void { + objects = null; + parents = null; + cameras = null; + materials = null; + textureMaterials = null; + // hierarchy = null; + animations = null; + } + + /** + * Создает для объектов из массивов objects и parents родительские связи, + * считает матрицу объектам в координатах родительского контейнера и заполняет массив корневых объектов. + * Значения в массивах objects, parents и animations обновляются. + * + * @param skipObject3Ds при значении true объекты типа Object3D не будут добавлены в родительский контейнер. + * + * @return массив корневых объектов (контейнеры и объекты без родителей) + */ + public function makeHierarchyForStatic(skipObject3Ds:Boolean = true):Vector. { + var hierarchy:Vector. = new Vector.(); + var i:int; + var count:int = objects.length; + for (i = 0; i < count; i++) { + var object:Object3D = objects[i]; + var parent:Object3D = parents[i]; + if (parent != null) { + if (parent is Object3DContainer) { + (parent as Object3DContainer).addChild(object); + } else { + var mat:Matrix3D = object.getMatrix(); + var pmat:Matrix3D = parent.getMatrix(); + mat.append(pmat); + object.setMatrix(mat); + if ((object as Object).constructor != Object3D || !skipObject3Ds) { + var container:Object3DContainer = findParentContainer(parent); + if (container != null) { + parents[i] = container; + container.addChild(object); + } else { + parents[i] = null; + hierarchy.push(object); + } + } else { + objects[i] = null; + } + } + } else { + if ((object as Object).constructor != Object3D || !skipObject3Ds) { + hierarchy.push(object); + } else { + objects[i] = null; + } + } + } + if (skipObject3Ds) { + // Удаляем пропущенные объекты + var j:int; + for (i = 0, j = 0; i < count; i++) { + if (objects[i] != null) { + objects[j] = objects[i]; + parents[j] = parents[i]; + animations[j] = animations[i]; + j++; + } + } + objects.length = j; + parents.length = j; + animations.length = j; + } + return hierarchy; + } + + /** + * Создает для объектов из массивов objects и parents родительские связи, + * создает контейнеры для объектов у которых родитель не является контейнером. + * Значения в массивах objects, parents и animations обновляются. + * + * @return массив корневых объектов (контейнеры и объекты без родителей) + */ + public function makeHierarchyForAnimation():Vector. { + var hierarchy:Vector. = new Vector.(); + // Словарь замены парентов на контейнеры + var parentReplace:Dictionary = new Dictionary(); + var i:int; + var count:int = objects.length; + for (i = 0; i < count; i++) { + var object:Object3D = objects[i]; + var parent:Object3D = parents[i]; + if (parent != null) { + if (parent is Object3DContainer) { + (parent as Object3DContainer).addChild(object); + } else { + var container:Object3DContainer = parentReplace[parent]; + if (container == null) { + container = new Object3DContainer(); + if (parent.name != null) { + container.name = parent.name + "-container"; + } + container.setMatrix(parent.getMatrix()); + parentReplace[parent] = container; + objects.push(container); + if (parent.parent == null) { + hierarchy.push(container); + parents.push(null); + } else { + parent.parent.addChild(container); + parents.push(parent.parent); + } + animations.push(null); + } + container.addChild(object); + parents[i] = container; + } + } else { + hierarchy.push(object); + } + } + return hierarchy; + } + + /** + * @private + */ + private function findParentContainer(object:Object3D):Object3DContainer { + var index:int = objects.indexOf(object); + var parent:Object3D; + while ((parent = parents[index]) != null) { + if (parent is Object3DContainer) { + return Object3DContainer(parent); + } + index = objects.indexOf(parent); + } + return null; + } + + /** + * Метод распарсивает xml коллады и заполняет массивы objects, parents, cameras, materials, textureMaterials, animations. + * Для загрузки текстур сцены можно использовать класс MaterialLoader. + * + * @param data xml содержимое коллады + * @param baseURL адрес относительно которого выполняется поиск текстур. Путь к файлу коллады. + * Должен соответствовать спецификации URI. Например file:///C:/test.dae или /C:/test.dae для полных путей или test.dae, ./test.dae для относительных. + * + * @example Пример загрузки файла коллады, парсинга, загрузки текстур и создания иерархии: + * package { + * + * import alternativa.engine3d.containers.AverageZContainer; + * import alternativa.engine3d.core.Object3D; + * import alternativa.engine3d.core.Object3DContainer; + * import alternativa.engine3d.loaders.MaterialLoader; + * import alternativa.engine3d.loaders.ParserCollada; + * + * import flash.display.Sprite; + * import flash.events.Event; + * import flash.geom.Matrix3D; + * import flash.net.URLLoader; + * import flash.net.URLRequest; + * + * public class ColladaExample extends Sprite { + * + * private const modelURL:String = "model.dae"; + * + * private var loader:URLLoader; + * private var materialLoader:MaterialLoader; + * + * public function ColladaExample() { + * loader = new URLLoader(); + * loader.addEventListener(Event.COMPLETE, onModelLoad); + * loader.load(new URLRequest(modelURL)); + * } + * + * private function onModelLoad(e:Event):void { + * var collada:ParserCollada = new ParserCollada(); + * collada.parse(XML(loader.data), modelURL); + * + * var container:AverageZContainer = new AverageZContainer(); + * // Создаем иерархию объектов + * var objects:Vector. = collada.makeHierarchy(); + * for (var o:int = 0, count:int = objects.length; o < count; o++) { + * container.addChild(objects[o]); + * } + * + * // Начинаем загрузку текстур материалов + * materialLoader = new MaterialLoader(); + * materialLoader.addEventListener(Event.COMPLETE, onMaterialsLoad); + * materialLoader.load(collada.textureMaterials); + * } + * + * private function onMaterialsLoad(e:Event):void { + * trace("Loading complete"); + * } + * + * }} + * + * @see MaterialLoader + * @see #objects + * @see #parents + * @see #cameras + * @see #materials + * @see #textureMaterials + */ + public function parse(data:XML, baseURL:String = null):void { + clean(); + // var t:int = getTimer(); + var document:DaeDocument = new DaeDocument(data); + // t = getTimer() - t; + // trace("TIME INIT:", t); + objects = new Vector.(); + parents = new Vector.(); + cameras = new Vector.(); + materials = new Vector.(); + animations = new Vector.(); + textureMaterials = new Vector.(); + if (document.scene != null) { + // t = getTimer(); + parseNodes(document.scene.nodes); + parseMaterials(document.materials); + // t = getTimer() - t; + // trace("TIME PARSING:", t); + } + + var material:TextureMaterial; + if (baseURL != null) { + baseURL = fixURL(baseURL); + var end:int = baseURL.lastIndexOf("/"); + var base:String = (end < 0) ? "" : baseURL.substring(0, end + 1); + for each (material in textureMaterials) { + if (material.diffuseMapURL != null) { + material.diffuseMapURL = resolveURL(fixURL(material.diffuseMapURL), base); + } + if (material.opacityMapURL != null) { + material.opacityMapURL = resolveURL(fixURL(material.opacityMapURL), base); + } + } + } else { + for each (material in textureMaterials) { + if (material.diffuseMapURL != null) { + material.diffuseMapURL = fixURL(material.diffuseMapURL); + } + if (material.opacityMapURL != null) { + material.opacityMapURL = fixURL(material.opacityMapURL); + } + } + } + + } + + private function parseNodes(nodes:Vector., parent:Object3D = null):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + var j:int; + var num:int; + node.parse(); + var animatedObjects:Vector.; + var animatedObject:DaeAnimatedObject; + var object:Object3D; + if (node.skins != null) { + // Рутовая кость скина + animatedObjects = node.skins; + num = animatedObjects.length; + for (j = 0; j < num; j++) { + animatedObject = animatedObjects[j]; + object = animatedObject.object; + this.objects.push(object); + this.parents.push(parent); + this.animations.push(animatedObject.animation); + } + } else { + if (!node.skiped) { + animatedObjects = node.objects; + num = animatedObjects.length; // >= 1 + for (j = 0; j < num; j++) { + animatedObject = animatedObjects[j]; + object = animatedObject.object; + this.objects.push(object); + this.parents.push(parent); + if (object is Camera3D) { + this.cameras.push(object as Camera3D); + } + this.animations.push(animatedObject.animation); + } + parseNodes(node.nodes, animatedObjects[0].object); + } + } + } + } + + private function parseMaterials(materials:Object):void { + for each (var material:DaeMaterial in materials) { + if (material.used) { + material.parse(); + this.materials.push(material.material); + if (material.material is TextureMaterial) { + var tmaterial:TextureMaterial = material.material as TextureMaterial; + if (tmaterial.texture == null) { + // Филлы тоже задаются текстурным материалом на данный момент + textureMaterials.push(tmaterial); + } + } + } + } + } + + /** + * @private + * Приводит урл к следующему виду: + * Обратные слеши в пути заменяет на прямые + * Три прямых слеша после схемы file: + */ + private function fixURL(url:String):String { + var pathStart:int = url.indexOf("://"); + pathStart = (pathStart < 0) ? 0 : pathStart + 3; + var pathEnd:int = url.indexOf("?", pathStart); + pathEnd = (pathEnd < 0) ? url.indexOf("#", pathStart) : pathEnd; + var path:String = url.substring(pathStart, (pathEnd < 0) ? 0x7FFFFFFF : pathEnd); + path = path.replace(/\\/g, "/"); + var fileIndex:int = url.indexOf("file://"); + if (fileIndex >= 0) { + if (url.charAt(pathStart) == "/") { + return "file://" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + return "file:///" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + return url.substring(0, pathStart) + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + + // public function resolveTest(url:String, base:String):void { + // trace('was::"' + base + '" "' + url + '"'); + // trace('now::"' + resolveURL(fixURL(url), fixURL(base)) + '"'); + // } + + /** + * @private + */ + private function mergePath(path:String, base:String, relative:Boolean = false):String { + var baseParts:Array = base.split("/"); + var parts:Array = path.split("/"); + for (var i:int = 0, count:int = parts.length; i < count; i++) { + var part:String = parts[i]; + if (part == "..") { + var basePart:String = baseParts.pop(); + while (basePart == "." || basePart == "" && basePart != null) basePart = baseParts.pop(); + if (relative) { + if (basePart == "..") { + baseParts.push("..", ".."); + } else if (basePart == null) { + baseParts.push(".."); + } + } + } else { + baseParts.push(part); + } + } + return baseParts.join("/"); + } + + /** + * @private + * Конвертирует относительные пути в полные + */ + private function resolveURL(url:String, base:String):String { + // http://labs.apache.org/webarch/uri/rfc/rfc3986.html + if (url.charAt(0) == "." && url.charAt(1) == "/") { + // Файл в той же папке + return base + url.substring(2); + } else if (url.charAt(0) == "/") { + // Полный путь + return url; + } else if (url.charAt(0) == "." && url.charAt(1) == ".") { + // Выше по уровню + var queryAndFragmentIndex:int = url.indexOf("?"); + queryAndFragmentIndex = (queryAndFragmentIndex < 0) ? url.indexOf("#") : queryAndFragmentIndex; + var path:String; + var queryAndFragment:String; + if (queryAndFragmentIndex < 0) { + queryAndFragment = ""; + path = url; + } else { + queryAndFragment = url.substring(queryAndFragmentIndex); + path = url.substring(0, queryAndFragmentIndex); + } + // Делим базовый урл на составные части + var bPath:String; + var bSlashIndex:int = base.indexOf("/"); + var bShemeIndex:int = base.indexOf(":"); + var bAuthorityIndex:int = base.indexOf("//"); + if (bAuthorityIndex < 0 || bAuthorityIndex > bSlashIndex) { + if (bShemeIndex >= 0 && bShemeIndex < bSlashIndex) { + // Присутствует схема, нет домена + var bSheme:String = base.substring(0, bShemeIndex + 1); + bPath = base.substring(bShemeIndex + 1); + if (bPath.charAt(0) == "/") { + return bSheme + "/" + mergePath(path, bPath.substring(1), false) + queryAndFragment; + } else { + return bSheme + mergePath(path, bPath, false) + queryAndFragment; + } + } else { + // Нет схемы, нет домена + if (base.charAt(0) == "/") { + return "/" + mergePath(path, base.substring(1), false) + queryAndFragment; + } else { + return mergePath(path, base, true) + queryAndFragment; + } + } + } else { + bSlashIndex = base.indexOf("/", bAuthorityIndex + 2); + var bAuthority:String; + if (bSlashIndex >= 0) { + bAuthority = base.substring(0, bSlashIndex + 1); + bPath = base.substring(bSlashIndex + 1); + return bAuthority + mergePath(path, bPath, false) + queryAndFragment; + } else { + bAuthority = base; + return bAuthority + "/" + mergePath(path, "", false); + } + } + } + var shemeIndex:int = url.indexOf(":"); + var slashIndex:int = url.indexOf("/"); + if (shemeIndex >= 0 && (shemeIndex < slashIndex || slashIndex < 0)) { + // Содержит схему + return url; + } + return base + "/" + url; + } + + public function getObjectByName(name:String):Object3D { + for (var i:int = 0, count:int = objects.length; i < count; i++) { + var object:Object3D = objects[i]; + if (object.name == name) { + return object; + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as new file mode 100644 index 0000000..042e0ec --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as @@ -0,0 +1,226 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.containers.ConflictContainer; + import alternativa.engine3d.containers.KDTree; + import alternativa.engine3d.containers.ZSortContainer; + import alternativa.engine3d.core.Clipping; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.core.Sorting; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.LOD; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.objects.Sprite3D; + + import flash.utils.getDefinitionByName; + + /** + * @private + */ + public class DaeAlternativa3DObject extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + public function DaeAlternativa3DObject(data:XML, document:DaeDocument) { + super(data, document); + } + + private function createObject(className:String):* { + try { + var ClassDef:Class = getDefinitionByName(className) as Class; + return new ClassDef(); + } catch (e:ReferenceError) { + trace("[ERROR]", e.message); + } + return null; + } + + public function parseContainer():Object3DContainer { + var container:Object3DContainer; + var classNameXML:XML = data.@className[0]; + switch (data.localName()) { + case "object3d": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new Object3DContainer(); + } + return setParams(container); + } + case "averageZ": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new ZSortContainer(); + } + return setParams(container); + } + case "conflict": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new ConflictContainer(); + } + return setParams(container); + } + case "kdTree": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new KDTree(); + } + return setParams(container); + } + } + return null; + } + + private function getClippingValue(clipping:XML):int { + switch (clipping.toString()) { + case "BOUND_CULLING": + return Clipping.BOUND_CULLING; + break; + case "FACE_CULLING": + return Clipping.FACE_CULLING; + break; + case "FACE_CLIPPING": + return Clipping.FACE_CLIPPING; + break; + } + return Clipping.BOUND_CULLING; + } + + private function getSortingValue(sorting:XML):int { + switch (sorting.toString()) { + case "STATIC_BSP": + return Sorting.STATIC_BSP; + break; + case "DYNAMIC_BSP": + return Sorting.DYNAMIC_BSP; + break; + case "NONE": + return Sorting.NONE; + break; + case "AVERAGE_Z": + return Sorting.AVERAGE_Z; + break; + } + return Sorting.NONE; + } + + public function parseSprite3D(material:Material = null):Sprite3D { + if (data.localName() == "sprite") { + var sprite:Sprite3D; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + sprite = createObject(classNameXML.toString()); + } else { + sprite = new Sprite3D(); + } + sprite.material = material; + var sortingXML:XML = data.sorting[0]; + var clippingXML:XML = data.clipping[0]; + if (sortingXML != null) { + sprite.sorting = getSortingValue(sortingXML); + } + if (clippingXML != null) { + sprite.clipping = getClippingValue(clippingXML); + } + return setParams(sprite); + } + return null; + } + + public function parseMesh(skin:Boolean = false):Mesh { + if (data.localName() == "mesh") { + var mesh:Mesh; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + mesh = createObject(classNameXML.toString()); + } else { + mesh = (skin) ? new Skin() : new Mesh(); + } + var sortingXML:XML = data.sorting[0]; + var clippingXML:XML = data.clipping[0]; + var optimizeXML:XML = data.optimizeBSP[0]; + if (clippingXML != null) { + mesh.clipping = getClippingValue(clippingXML); + } + var optimize:Boolean = (optimizeXML != null) ? optimizeXML.toString() != "false" : true; + if (sortingXML != null) { + mesh.sorting = getSortingValue(sortingXML); + if (mesh.sorting == Sorting.STATIC_BSP) { + mesh.calculateBSP(optimize); + } else if (mesh.sorting == Sorting.DYNAMIC_BSP && optimize) { + mesh.optimizeForDynamicBSP(); + } + } + return setParams(mesh); + } + return null; + } + + public function parseLOD():LOD { + if (data.localName() == "LOD" || data.localName() == "lod") { + var lod:LOD; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + lod = createObject(classNameXML.toString()); + } else { + lod = new LOD(); + } + var levels:XMLList = data.level; + var count:int = levels.length(); + var distances:Vector. = lod.lodDistances = new Vector.(count); + var objects:Vector. = lod.lodObjects = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var level:XML = levels[i]; + distances[i] = parseNumber(level.@distance[0]); + var node:DaeNode = document.findNode(level.@url[0]); + if (node != null) { + node.parse(); + objects[i] = node.objects[0].object; + } else { + document.logger.logNotFoundError(level.@url[0]); + } + } + return setParams(lod); + } + return new LOD(); + } + + private function setParams(object:*):* { + var params:XMLList = data.param; + for (var i:int = 0, count:int = params.length(); i < count; i++) { + var param:XML = params[i]; + try { + var name:String = param.@name[0].toString(); + var value:String = param.text().toString(); + if (value == "true") { + object[name] = true; + } else if (value == "false") { + object[name] = false; + } else if ((value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') || (value.charAt(0) == "'" && value.charAt(value.length - 1) == "'")) { + object[name] = value; + } else { + if (value.indexOf(".") >= 0) { + object[name] = parseFloat(value); + } else if (value.indexOf(",") >= 0) { + value = value.replace(/,/, "."); + object[name] = parseFloat(value); + } else { + object[name] = parseInt(value); + } + } + } catch (e:Error) { + trace("[ERROR]", e.message); + } + } + return object; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as new file mode 100644 index 0000000..21990fc --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.core.Object3D; + + /** + * @private + */ + public class DaeAnimatedObject { + + public var object:Object3D; + public var animation:Animation; + + public function DaeAnimatedObject(object:Object3D, animation:Animation = null) { + this.object = object; + this.animation = animation; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeArray.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeArray.as new file mode 100644 index 0000000..8c44997 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeArray.as @@ -0,0 +1,41 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeArray extends DaeElement { + + use namespace collada; + + /** + * Массив значений типа String. + * Перед использованием вызвать parse(). + */ + public var array:Array; + + public function DaeArray(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get type():String { + return String(data.localName()); + } + + override protected function parseImplementation():Boolean { + array = parseStringArray(data); + var countXML:XML = data.@count[0]; + if (countXML != null) { + var count:int = parseInt(countXML.toString(), 10); + if (array.length < count) { + document.logger.logNotEnoughDataError(data.@count[0]); + return false; + } else { + array.length = count; + return true; + } + } + return false; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeCamera.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeCamera.as new file mode 100644 index 0000000..dc3d3e2 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeCamera.as @@ -0,0 +1,55 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.core.Camera3D; + + /** + * @private + */ + public class DaeCamera extends DaeElement { + + use namespace collada; + + public function DaeCamera(data:XML, document:DaeDocument) { + super(data, document); + } + + private function setXFov(camera:Camera3D, xFov:Number):void { + //camera.fov = 2*Math.atan(0.5*Math.sqrt(camera.width*camera.width + camera.height*camera.height)/(camera.width/(2*Math.tan(0.5*xFov)))); + } + + public function parseCamera():Camera3D { + var camera:Camera3D = new Camera3D(); + var perspectiveXML:XML = data.optics.technique_common.perspective[0]; + if (perspectiveXML) { + const DEG2RAD:Number = Math.PI/180; + var xfovXML:XML = perspectiveXML.xfov[0]; + var yfovXML:XML = perspectiveXML.yfov[0]; + var ratioXML:XML = perspectiveXML.aspect_ratio[0]; + if (ratioXML == null) { + if (xfovXML != null) { + setXFov(camera, parseNumber(xfovXML)*DEG2RAD); + } else if (yfovXML != null) { + setXFov(camera, parseNumber(yfovXML)*DEG2RAD); + } + } else { + var ratio:Number = parseNumber(ratioXML); + //camera.height = camera.width/ratio; + if (xfovXML != null) { + setXFov(camera, parseNumber(xfovXML)*DEG2RAD); + } else if (yfovXML != null) { + setXFov(camera, ratio*parseNumber(yfovXML)*DEG2RAD); + } + } + var znearXML:XML = perspectiveXML.znear[0]; + var zfarXML:XML = perspectiveXML.zfar[0]; + if (znearXML != null) { + camera.nearClipping = parseNumber(znearXML); + } + if (zfarXML != null) { + camera.farClipping = parseNumber(zfarXML); + } + } + return camera; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeChannel.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeChannel.as new file mode 100644 index 0000000..eaae6c0 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeChannel.as @@ -0,0 +1,185 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.Key; + import alternativa.engine3d.animation.Track; + import alternativa.engine3d.animation.ValueKey; + + /** + * @private + */ + public class DaeChannel extends DaeElement { + + static public const PARAM_UNDEFINED:int = -1; + static public const PARAM_TRANSLATE_X:int = 0; + static public const PARAM_TRANSLATE_Y:int = 1; + static public const PARAM_TRANSLATE_Z:int = 2; + static public const PARAM_SCALE_X:int = 3; + static public const PARAM_SCALE_Y:int = 4; + static public const PARAM_SCALE_Z:int = 5; + static public const PARAM_ROTATION_X:int = 6; + static public const PARAM_ROTATION_Y:int = 7; + static public const PARAM_ROTATION_Z:int = 8; + static public const PARAM_TRANSLATE:int = 9; + static public const PARAM_SCALE:int = 10; + static public const PARAM_MATRIX:int = 11; + + /** + * Анимационный трек с ключами. + * Перед использованием вызвать parse(). + */ + public var track:Track; + + /** + * Тип анимированного параметра, принимает одно из значений DaeChannel.PARAM_*. + * Перед использованием вызвать parse(). + */ + public var animatedParam:int = PARAM_UNDEFINED; + + public function DaeChannel(data:XML, document:DaeDocument) { + super(data, document); + } + + /** + * Возвращает ноду которой предназначена анимация. + */ + public function get node():DaeNode { + var targetXML:XML = data.@target[0]; + if (targetXML != null) { + var targetParts:Array = targetXML.toString().split("/"); + // Первая часть это id элемента + var node:DaeNode = document.findNodeByID(targetParts[0]); + if (node != null) { + // Последняя часть это трансформируемый элемент + targetParts.pop(); + for (var i:int = 1, count:int = targetParts.length; i < count; i++) { + var sid:String = targetParts[i]; + node = node.getNodeBySid(sid); + if (node == null) { + return null; + } + } + return node; + } + } + return null; + } + + override protected function parseImplementation():Boolean { + parseTransformationType(); + parseSampler(); + return true; + } + + private function parseTransformationType():void { + var targetXML:XML = data.@target[0]; + if (targetXML == null) return; + + // Разбиваем путь на части + var targetParts:Array = targetXML.toString().split("/"); + var sid:String = targetParts.pop(); + var sidParts:Array = sid.split("."); + var sidPartsCount:int = sidParts.length; + + // Определяем тип свойства + var transformationXML:XML; + var children:XMLList = node.data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + var attr:XML = child.@sid[0]; + if (attr != null && attr.toString() == sidParts[0]) { + transformationXML = child; + break; + } + } + // TODO:: вариант со скобками на всякий случай + var transformationName:String = transformationXML.localName() as String; + if (sidPartsCount > 1) { + var componentName:String = sidParts[1]; + switch (transformationName) { + case "translate": + switch (componentName) { + case "X": + animatedParam = PARAM_TRANSLATE_X; + break; + case "Y": + animatedParam = PARAM_TRANSLATE_Y; + break; + case "Z": + animatedParam = PARAM_TRANSLATE_Z; + break; + } + break; + case "rotate": { + var axis:Array = parseNumbersArray(transformationXML); + // TODO:: искать максимальное значение, а не единицу + switch (axis.indexOf(1)) { + case 0: + animatedParam = PARAM_ROTATION_X; + break; + case 1: + animatedParam = PARAM_ROTATION_Y; + break; + case 2: + animatedParam = PARAM_ROTATION_Z; + break; + } + break; + } + case "scale": + switch (componentName) { + case "X": + animatedParam = PARAM_SCALE_X; + break; + case "Y": + animatedParam = PARAM_SCALE_Y; + break; + case "Z": + animatedParam = PARAM_SCALE_Z; + break; + } + break; + } + } else { + switch (transformationName) { + case "translate": + animatedParam = PARAM_TRANSLATE; + break; + case "scale": + animatedParam = PARAM_SCALE; + break; + case "matrix": + animatedParam = PARAM_MATRIX; + break; + } + } + } + + private function parseSampler():void { + var sampler:DaeSampler = document.findSampler(data.@source[0]); + if (sampler != null) { + sampler.parse(); + + if (animatedParam == PARAM_MATRIX) { + track = sampler.parseMatrixTrack(); + return; + } + if (animatedParam == PARAM_TRANSLATE || animatedParam == PARAM_SCALE) { + track = sampler.parsePointsTrack(); + return; + } + track = sampler.parseValuesTrack(); + if (animatedParam == PARAM_ROTATION_X || animatedParam == PARAM_ROTATION_Y || animatedParam == PARAM_ROTATION_Z) { + // Переводим углы в радианы + var toRad:Number = Math.PI/180; + for (var key:Key = track.keyList; key != null; key = key.next) { + var valueKey:ValueKey = ValueKey(key); + valueKey.value *= toRad; + } + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeController.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeController.as new file mode 100644 index 0000000..1395512 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeController.as @@ -0,0 +1,471 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.*; + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.animation.ComplexAnimation; + import alternativa.engine3d.animation.ObjectAnimation; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Skin; + + import flash.geom.Matrix3D; + import flash.utils.Dictionary; + + /** + * @private + */ + public class DaeController extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + private var jointsBindMatrices:Vector. >; + private var vcounts:Array; + private var indices:Array; + private var jointsInput:DaeInput; + private var weightsInput:DaeInput; + private var inputsStride:int; + + public function DaeController(data:XML, document:DaeDocument) { + super(data, document); + + // sources мы создаем внутри DaeDocument, здесь не нужно. + } + + override protected function parseImplementation():Boolean { + var vertexWeightsXML:XML = data.skin.vertex_weights[0]; + if (vertexWeightsXML == null) { + return false; + } + var vcountsXML:XML = vertexWeightsXML.vcount[0]; + if (vcountsXML == null) { + return false; + } + vcounts = parseIntsArray(vcountsXML); + var indicesXML:XML = vertexWeightsXML.v[0]; + if (indicesXML == null) { + return false; + } + indices = parseIntsArray(indicesXML); + parseInputs(); + parseJointsBindMatrices(); + return true; + } + + private function parseInputs():void { + var inputsList:XMLList = data.skin.vertex_weights.input; + var maxInputOffset:int = 0; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "JOINT" : + if (jointsInput == null) { + jointsInput = input; + } + break; + case "WEIGHT" : + if (weightsInput == null) { + weightsInput = input; + } + break; + } + } + var offset:int = input.offset; + maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset; + } + inputsStride = maxInputOffset + 1; + } + + /** + * Парсит инверсные матрицы для костей и сохраняет их в вектор. + */ + private function parseJointsBindMatrices():void { + var jointsXML:XML = data.skin.joints.input.(@semantic == "INV_BIND_MATRIX")[0]; + if (jointsXML != null) { + var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]); + if (jointsSource != null) { + if (jointsSource.parse() && jointsSource.numbers != null && jointsSource.stride >= 16) { + var stride:int = jointsSource.stride; + var count:int = jointsSource.numbers.length/stride; + jointsBindMatrices = new Vector. >(count); + for (var i:int = 0; i < count; i++) { + var index:int = stride*i; + var matrix:Vector. = new Vector.(16); + jointsBindMatrices[i] = matrix; + for (var j:int = 0; j < 16; j++) { + matrix[j] = jointsSource.numbers[int(index + j)]; + } + } + } + } else { + document.logger.logNotFoundError(jointsXML.@source[0]); + } + } + } + + private function get geometry():DaeGeometry { + var geom:DaeGeometry = document.findGeometry(data.skin.@source[0]); + if (geom == null) { + document.logger.logNotFoundError(data.@source[0]); + } + return geom; + } + + /** + * Возвращает геометрию с костями и контроллер для костей. + * Перед использованием вызвать parse(). + */ + public function parseSkin(skinNode:DaeNode, materials:Object, skeletons:Vector.):DaeAnimatedObject { + var skinXML:XML = data.skin[0]; + if (skinXML != null) { + var skin:Skin; + var geom:DaeGeometry = geometry; + if (geom != null) { + geom.parse(); + skin = geom.parseAlternativa3DObject(true) as Skin; + if (skin == null) { + skin = new Skin(); + } + var vertices:Vector. = geom.fillInMesh(skin, materials); + applyBindShapeMatrix(skin); + var joints:Vector. = addJointsToSkin(skin, skinNode, findNodes(skeletons)); + setJointsBindMatrices(joints); + linkVerticesToJoints(joints, vertices); + skin.normalizeWeights(); + geom.cleanVertices(skin); + skin.calculateNormals(true); + skin.calculateBounds(); + return new DaeAnimatedObject(skin, mergeJointsAnimations(joints)); + } else { + skin = new Skin(); + skin.calculateNormals(true); + skin.calculateBounds(); + return new DaeAnimatedObject(skin); + } + } + return null; + } + + /** + * Объединяет анимацию костей в одну анимацию, если требуется + */ + private function mergeJointsAnimations(joints:Vector.):Animation { + var animation:Animation; + var complexAnimation:ComplexAnimation; + for (var i:int = 0, count:int = joints.length; i < count; i++) { + var animatedObject:DaeAnimatedObject = joints[i]; + if (animatedObject.animation != null) { + if (animation == null) { + // Первая анимация + animation = animatedObject.animation; + } else { + if (complexAnimation == null) { + // Вторая анимация, создаем комплексную и добавляем в нее первую анимацию + complexAnimation = new ComplexAnimation(); + complexAnimation.addAnimation(animation); + animation = complexAnimation; + } + complexAnimation.addAnimation(animatedObject.animation); + } + } + } + return animation; + } + + /** + * Задает костям их инверсные матрицы. + */ + private function setJointsBindMatrices(animatedJoints:Vector.):void { + for (var i:int = 0, count:int = jointsBindMatrices.length; i < count; i++) { + var animatedJoint:DaeAnimatedObject = animatedJoints[i]; + var bindMatrix:Vector. = jointsBindMatrices[i]; + Joint(animatedJoint.object).setBindingMatrix(bindMatrix[0], bindMatrix[1], bindMatrix[2], bindMatrix[3], + bindMatrix[4], bindMatrix[5], bindMatrix[6], bindMatrix[7], + bindMatrix[8], bindMatrix[9], bindMatrix[10], bindMatrix[11]); + } + } + + /** + * Связывает вершину и все ее дубликаты с костью + */ + private function linkVertexToJoint(joint:Joint, vertex:Vertex, weight:Number):void { + joint.bindVertex(vertex, weight); + // Цепляем дубликаты + while ((vertex = vertex.value) != null) { + joint.bindVertex(vertex, weight); + } + } + + /** + * Связывает вершины с костями + */ + private function linkVerticesToJoints(animatedJoints:Vector., vertices:Vector.):void { + var jointsOffset:int = jointsInput.offset; + var weightsOffset:int = weightsInput.offset; + var weightsSource:DaeSource = weightsInput.prepareSource(1); + var weights:Vector. = weightsSource.numbers; + var weightsStride:int = weightsSource.stride; + var vertexIndex:int = 0; + for (var i:int = 0, numVertices:int = vertices.length; i < numVertices; i++) { + var vertex:Vertex = vertices[i]; + var count:int = vcounts[i]; + for (var j:int = 0; j < count; j++) { + var index:int = inputsStride*(vertexIndex + j); + var jointIndex:int = indices[int(index + jointsOffset)]; + if (jointIndex >= 0) { + var weightIndex:int = indices[int(index + weightsOffset)]; + var weight:Number = weights[int(weightsStride*weightIndex)]; + linkVertexToJoint(Joint(animatedJoints[jointIndex].object), vertex, weight); + } + } + vertexIndex += count; + } + } + + /** + * Создает иерархию костей и добавляет к скину. + * + * @return вектор добавленых к скину костей с анимацией. + * Если были добавлены вспомогательные кости, длина вектора будет отличаться от длины вектора nodes + */ + private function addJointsToSkin(skin:Skin, skinNode:DaeNode, nodes:Vector.):Vector. { + // Словарь, в котором ключ-нода, значение-позиция в векторе nodes + var nodesDictionary:Dictionary = new Dictionary(); + var count:int = nodes.length; + var i:int; + for (i = 0; i < count; i++) { + nodesDictionary[nodes[i]] = i; + } + var animatedJoints:Vector. = new Vector.(count); + for (i = 0; i < count; i++) { + var node:DaeNode = nodes[i]; + if (isRootJointNode(node, nodesDictionary)) { + var animatedJoint:DaeAnimatedObject = addRootJointToSkin(skin, skinNode, node); + if (animatedJoint != null) { + animatedJoints[i] = animatedJoint; + addJointChildren(Joint(animatedJoint.object), animatedJoints, node, nodesDictionary); + } + } + } + return animatedJoints; + } + + /** + * Возвращает true если у кости нет родительской кости + * @param node нода кости + * @param nodes словарь, в котором ключи это ноды всех костей + */ + private function isRootJointNode(node:DaeNode, nodes:Dictionary):Boolean { + for (var parent:DaeNode = node.parent; parent != null; parent = parent.parent) { + if (parent in nodes) { + return false; + } + } + return true; + } + + /** + * Добавляет рутовую кость к скину + */ + private function addRootJointToSkin(skin:Skin, skinNode:DaeNode, node:DaeNode):DaeAnimatedObject { + var joint:Joint; + if (skinNode == node) { + // Кость и является скином + joint = new Joint(); + joint.name = node.name; + skin.addJoint(joint); + return new DaeAnimatedObject(joint); + } else { + if (node.scene == skinNode.scene) { + var parent:DaeNode = node.parent; + var toSceneMatrix:Matrix3D; + if (parent != null) { + // Считаем матрицу перевода кости в сцену + toSceneMatrix = parent.getMatrix(); + while ((parent = parent.parent) != null) { + toSceneMatrix.append(parent.getMatrix()); + } + } + // Считаем матрицу перевода локального пространства скина в сцену + var skinMatrix:Matrix3D = skinNode.getMatrix(); + for (parent = skinNode.parent; parent != null; parent = parent.parent) { + skinMatrix.append(parent.getMatrix()); + } + skinMatrix.invert(); + // Считаем матрицу перевода в скин + var toSkinMatrix:Matrix3D; + if (toSceneMatrix != null) { + toSkinMatrix = toSceneMatrix; + toSkinMatrix.append(skinMatrix); + } else { + toSkinMatrix = skinMatrix; + } + var animation:ObjectAnimation = node.parseAnimation(); + if (animation != null) { + // Если кость анимирована, создаем вспомогательную кость перевода в скин + var additionalJoint:Joint = new Joint(); + additionalJoint.setMatrix(toSkinMatrix); + skin.addJoint(additionalJoint); + joint = new Joint(); + joint.name = node.name; + additionalJoint.addJoint(joint); + return node.applyAnimation(node.applyTransformations(joint), animation); + } else { + joint = new Joint(); + joint.name = node.name; + skin.addJoint(joint); + node.applyTransformations(joint, null, toSkinMatrix); + return new DaeAnimatedObject(joint); + } + } else { + // Не обрабатывается + document.logger.logJointInAnotherSceneError(node.data); + return null; + } + } + } + + /** + * Создает иерархию дочерних костей и добавляет к родительской кости. + * + * @param parent родительская кость + * @param animatedJoints вектор костей в который положить созданные кости. + * В конец вектора будут добавлены вспомогательные кости, если понадобятся. + * @param parentNode нода родительской кости + * @param nodes словарь, в котором ключ это нода кости, а значение это индекс кости в векторе animatedJoints + */ + private function addJointChildren(parent:Joint, animatedJoints:Vector., parentNode:DaeNode, nodes:Dictionary):void { + var children:Vector. = parentNode.nodes; + for (var i:int = 0, count:int = children.length; i < count; i++) { + var child:DaeNode = children[i]; + var joint:Joint; + if (child in nodes) { + joint = new Joint(); + joint.name = child.name; + animatedJoints[nodes[child]] = child.applyAnimation(child.applyTransformations(joint)); + parent.addJoint(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } else { + // Нода не является костью + if (hasJointInDescendants(child, nodes)) { + // Если среди ее потомков есть кость, нужно создать вспомогательную кость вместо этой ноды. + joint = new Joint(); + joint.name = child.name; + // Добавляем в конец новую кость + animatedJoints.push(child.applyAnimation(child.applyTransformations(joint))); + parent.addJoint(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } + } + } + } + + private function hasJointInDescendants(parentNode:DaeNode, nodes:Dictionary):Boolean { + var children:Vector. = parentNode.nodes; + for (var i:int = 0, count:int = children.length; i < count; i++) { + var child:DaeNode = children[i]; + if (child in nodes || hasJointInDescendants(child, nodes)) { + return true; + } + } + return false; + } + + /** + * Трансформирует все вершины объекта при помощи BindShapeMatrix из коллады + */ + private function applyBindShapeMatrix(skin:Skin):void { + var matrixXML:XML = data.skin.bind_shape_matrix[0]; + if (matrixXML != null) { + var matrix:Array = parseNumbersArray(matrixXML); + if (matrix.length >= 16) { + var a:Number = matrix[0]; + var b:Number = matrix[1]; + var c:Number = matrix[2]; + var d:Number = matrix[3]; + var e:Number = matrix[4]; + var f:Number = matrix[5]; + var g:Number = matrix[6]; + var h:Number = matrix[7]; + var i:Number = matrix[8]; + var j:Number = matrix[9]; + var k:Number = matrix[10]; + var l:Number = matrix[11]; + for (var vertex:Vertex = skin.vertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.x = a*x + b*y + c*z + d; + vertex.y = e*x + f*y + g*z + h; + vertex.z = i*x + j*y + k*z + l; + } + } + } + } + + public function findRootJointNodes(skeletons:Vector.):Vector. { + var nodes:Vector. = findNodes(skeletons); + var i:int = 0; + var count:int = nodes.length; + if (count > 0) { + var nodesDictionary:Dictionary = new Dictionary(); + for (i = 0; i < count; i++) { + nodesDictionary[nodes[i]] = i; + } + var rootNodes:Vector. = new Vector.(); + for (i = 0; i < count; i++) { + var node:DaeNode = nodes[i]; + if (isRootJointNode(node, nodesDictionary)) { + rootNodes.push(node); + } + } + return rootNodes; + } + return null; + } + + /** + * Находит ноду по ее сиду в векторе скелетов + */ + private function findNode(nodeName:String, skeletons:Vector.):DaeNode { + var count:int = skeletons.length; + for (var i:int = 0; i < count; i++) { + var node:DaeNode = skeletons[i].getNodeBySid(nodeName); + if (node != null) { + return node; + } + } + return null; + } + + /** + * Возвращает вектор нод костей. + */ + private function findNodes(skeletons:Vector.):Vector. { + var jointsXML:XML = data.skin.joints.input.(@semantic == "JOINT")[0]; + if (jointsXML != null) { + var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]); + if (jointsSource != null) { + if (jointsSource.parse() && jointsSource.names != null) { + var stride:int = jointsSource.stride; + var count:int = jointsSource.names.length/stride; + var nodes:Vector. = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = findNode(jointsSource.names[int(stride*i)], skeletons); + if (node == null) { + // Ошибка, нет ноды + } + nodes[i] = node; + } + return nodes; + } + } else { + document.logger.logNotFoundError(jointsXML.@source[0]); + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeDocument.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeDocument.as new file mode 100644 index 0000000..1efddce --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeDocument.as @@ -0,0 +1,294 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + + /** + * @private + */ + public class DaeDocument { + + use namespace collada; + use namespace alternativa3d; + + public var scene:DaeVisualScene; + + /** + * Файл коллады + */ + private var data:XML; + + // Словари для хранения соответствий id->DaeElement + internal var sources:Object; + internal var arrays:Object; + internal var vertices:Object; + internal var geometries:Object; + internal var nodes:Object; + internal var cameras:Object; + internal var images:Object; + internal var effects:Object; + internal var controllers:Object; + internal var samplers:Object; + internal var alternativa3DObjects:Object; + + public var materials:Object; + + internal var logger:DaeLogger; + + public var versionMajor:uint; + public var versionMinor:uint; + public var alternativa3DExtensionVersionMajor:uint = 0; + public var alternativa3DExtensionVersionMinor:uint = 0; + + public function DaeDocument(document:XML) { + this.data = document; + + var versionComponents:Array = data.@version[0].toString().split(/[.,]/); + versionMajor = parseInt(versionComponents[1], 10); + versionMinor = parseInt(versionComponents[2], 10); + + logger = new DaeLogger(); + + constructStructures(); + constructScenes(); + constructInstanceControllers(); + constructAnimations(); + constructAlternativa3DObjects(); + } + + private function isLocalURL(url:String):Boolean { + return url.charAt(0) == "#"; + } + + private function getLocalID(url:XML):String { + var path:String = url.toString(); + if (isLocalURL(path)) { + var i:int = path.lastIndexOf("#"); + return (i >= 0) ? path.substring(i + 1) : null; + } else { + logger.logExternalError(url); + return null; + } + } + + // Ищем объявления всех элементов и заполняем словари + private function constructStructures():void { + var element:XML; + + sources = new Object(); + arrays = new Object(); + for each (element in data..source) { + // Собираем все . В конструкторах заполняется словарь arrays + var source:DaeSource = new DaeSource(element, this); + if (source.id != null) { + sources[source.id] = source; + } + } + cameras = new Object(); + for each (element in data.library_cameras.camera) { + // Собираем все . + var camera:DaeCamera = new DaeCamera(element, this); + if (camera.id != null) { + cameras[camera.id] = camera; + } + } + images = new Object(); + for each (element in data.library_images.image) { + // Собираем все . + var image:DaeImage = new DaeImage(element, this); + if (image.id != null) { + images[image.id] = image; + } + } + effects = new Object(); + for each (element in data.library_effects.effect) { + // Собираем все . В конструкторах заполняется словарь images + var effect:DaeEffect = new DaeEffect(element, this); + if (effect.id != null) { + effects[effect.id] = effect; + } + } + materials = new Object(); + for each (element in data.library_materials.material) { + // Собираем все . + var material:DaeMaterial = new DaeMaterial(element, this); + if (material.id != null) { + materials[material.id] = material; + } + } + geometries = new Object(); + vertices = new Object(); + for each (element in data.library_geometries.geometry) { + // Собираем все . В конструкторах заполняется словарь vertices + var geom:DaeGeometry = new DaeGeometry(element, this); + if (geom.id != null) { + geometries[geom.id] = geom; + } + } + + controllers = new Object(); + for each (element in data.library_controllers.controller) { + // Собираем все + var controller:DaeController = new DaeController(element, this); + if (controller.id != null) { + controllers[controller.id] = controller; + } + } + + nodes = new Object(); + for each (element in data.library_nodes.node) { + // Создаем только корневые ноды, остальные создаются рекурсивно в конструкторах + var node:DaeNode = new DaeNode(element, this); + if (node.id != null) { + nodes[node.id] = node; + } + } + } + + private function constructInstanceControllers():void { + for each (var node:DaeNode in nodes) { + var instanceControllerXML:XML = node.data.instance_controller[0]; + if (instanceControllerXML != null) { + node.skiped = true; + var instanceController:DaeInstanceController = new DaeInstanceController(instanceControllerXML, this, node); + var jointNodes:Vector. = instanceController.findRootJointNodes(); + var i:int; + var count:int = jointNodes.length; + if (count > 0) { + var jointNode:DaeNode = jointNodes[0]; + jointNode.addInstanceController(instanceController); + for (i = 0; i < count; i++) { + jointNodes[i].skiped = true; + } + } + } + } + } + + private function constructScenes():void { + var vsceneURL:XML = data.scene.instance_visual_scene.@url[0]; + var vsceneID:String = getLocalID(vsceneURL); + for each (var element:XML in data.library_visual_scenes.visual_scene) { + // Создаем visual_scene, в конструкторах создаются node + var vscene:DaeVisualScene = new DaeVisualScene(element, this); + if (vscene.id == vsceneID) { + this.scene = vscene; + } + } + if (vsceneID != null && scene == null) { + logger.logNotFoundError(vsceneURL); + } + } + + private function constructAnimations():void { + var element:XML; + samplers = new Object(); + for each (element in data.library_animations..sampler) { + // Собираем все + var sampler:DaeSampler = new DaeSampler(element, this); + if (sampler.id != null) { + samplers[sampler.id] = sampler; + } + } + + for each (element in data.library_animations..channel) { + var channel:DaeChannel = new DaeChannel(element, this); + var node:DaeNode = channel.node; + if (node != null) { + node.addChannel(channel); + } + } + } + + private function constructAlternativa3DObjects():void { + alternativa3DObjects = new Object(); + var alternativa3dXML:XML = data.extra.technique.(@profile = "Alternativa3D")[0]; + if (alternativa3dXML != null) { + var versionComponents:Array = alternativa3dXML.version[0].text().toString().split(/[.,]/); + alternativa3DExtensionVersionMajor = parseInt(versionComponents[0], 10); + alternativa3DExtensionVersionMinor = parseInt(versionComponents[1], 10); + var element:XML; + var object:DaeAlternativa3DObject; + for each (element in alternativa3dXML.library_containers.children()) { + // контейнеры + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + for each (element in alternativa3dXML.library_sprites.sprite) { + // спрайты + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + for each (element in alternativa3dXML.library_lods.lod) { + // лоды + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + } else { + alternativa3DExtensionVersionMajor = alternativa3DExtensionVersionMinor = 0; + } + } + + public function findArray(url:XML):DaeArray { + return arrays[getLocalID(url)]; + } + + public function findSource(url:XML):DaeSource { + return sources[getLocalID(url)]; + } + + public function findCamera(url:XML):DaeCamera { + return cameras[getLocalID(url)]; + } + + public function findImage(url:XML):DaeImage { + return images[getLocalID(url)]; + } + + public function findImageByID(id:String):DaeImage { + return images[id]; + } + + public function findEffect(url:XML):DaeEffect { + return effects[getLocalID(url)]; + } + + public function findMaterial(url:XML):DaeMaterial { + return materials[getLocalID(url)]; + } + + public function findVertices(url:XML):DaeVertices { + return vertices[getLocalID(url)]; + } + + public function findGeometry(url:XML):DaeGeometry { + return geometries[getLocalID(url)]; + } + + public function findNode(url:XML):DaeNode { + return nodes[getLocalID(url)]; + } + + public function findNodeByID(id:String):DaeNode { + return nodes[id]; + } + + public function findController(url:XML):DaeController { + return controllers[getLocalID(url)]; + } + + public function findSampler(url:XML):DaeSampler { + return samplers[getLocalID(url)]; + } + + public function findAlternativa3DObject(url:XML):DaeAlternativa3DObject { + return alternativa3DObjects[getLocalID(url)]; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffect.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffect.as new file mode 100644 index 0000000..cc95976 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffect.as @@ -0,0 +1,157 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + + /** + * @private + */ +public class DaeEffect extends DaeElement { + + use namespace collada; + + private var effectParams:Object; + private var commonParams:Object; + private var techniqueParams:Object; + + private var diffuse:DaeEffectParam; + private var emission:DaeEffectParam; + private var transparent:DaeEffectParam; + private var transparency:DaeEffectParam; + + public function DaeEffect(data:XML, document:DaeDocument) { + super(data, document); + + // Внтри объявляются image. + constructImages(); + } + + private function constructImages():void { + var list:XMLList = data..image; + for each (var element:XML in list) { + var image:DaeImage = new DaeImage(element, document); + if (image.id != null) { + document.images[image.id] = image; + } + } + } + + override protected function parseImplementation():Boolean { + var element:XML; + var param:DaeParam; + effectParams = new Object(); + for each (element in data.newparam) { + param = new DaeParam(element, document); + effectParams[param.sid] = param; + } + commonParams = new Object(); + for each (element in data.profile_COMMON.newparam) { + param = new DaeParam(element, document); + commonParams[param.sid] = param; + } + techniqueParams = new Object(); + var technique:XML = data.profile_COMMON.technique[0]; + if (technique != null) { + for each (element in technique.newparam) { + param = new DaeParam(element, document); + techniqueParams[param.sid] = param; + } + } + var shader:XML = data.profile_COMMON.technique.*.(localName() == "constant" || localName() == "lambert" || localName() == "phong" || localName() == "blinn")[0]; + if (shader != null) { + if (shader.localName() == "constant") { + var emissionXML:XML = shader.emission[0]; + if (emissionXML != null) { + emission = new DaeEffectParam(emissionXML, this); + } + } else { + var diffuseXML:XML = shader.diffuse[0]; + if (diffuseXML != null) { + diffuse = new DaeEffectParam(diffuseXML, this); + } + } + var transparentXML:XML = shader.transparent[0]; + if (transparentXML != null) { + transparent = new DaeEffectParam(transparentXML, this); + } + var transparencyXML:XML = shader.transparency[0]; + if (transparencyXML != null) { + transparency = new DaeEffectParam(transparencyXML, this); + } + } + return true; + } + + internal function getParam(name:String, setparams:Object):DaeParam { + var param:DaeParam = setparams[name]; + if (param != null) { + return param; + } + param = techniqueParams[name]; + if (param != null) { + return param; + } + param = commonParams[name]; + if (param != null) { + return param; + } + return effectParams[name]; + } + + private function float4ToUint(value:Array, alpha:Boolean = true):uint { + var r:uint = (value[0] * 255); + var g:uint = (value[1] * 255); + var b:uint = (value[2] * 255); + if (alpha) { + var a:uint = (value[3] * 255); + return (a << 24) | (r << 16) | (g << 8) | b; + } else { + return (r << 16) | (g << 8) | b; + } + } + + /** + * Возвращает материал движка с заданными параметрами. + * Перед использованием вызвать parse(). + */ + public function getMaterial(setparams:Object):Material { + var diffuse:DaeEffectParam = (diffuse != null) ? diffuse : emission; + if (diffuse != null) { + var color:Array = diffuse.getColor(setparams); + if (color != null) { + var fillMaterial:FillMaterial = new FillMaterial(float4ToUint(color, false), color[3]); + if (transparency != null) { + var value:Number = transparency.getFloat(setparams); + if (!isNaN(value)) { + fillMaterial.alpha = value; + } + } + return fillMaterial; + } else { + var image:DaeImage = diffuse.getImage(setparams); + if (image != null) { + var sampler:DaeParam = diffuse.getSampler(setparams); + var textureMaterial:TextureMaterial = new TextureMaterial(); + textureMaterial.repeat = (sampler == null) ? true : (sampler.wrap_s == null || sampler.wrap_s == "WRAP"); + textureMaterial.diffuseMapURL = image.init_from; + var transparentImage:DaeImage = (transparent == null) ? null : transparent.getImage(setparams); + if (transparentImage != null) { + textureMaterial.opacityMapURL = transparentImage.init_from; + } + return textureMaterial; + } + } + } + return null; + } + + /** + * Имя текстурного канала для карты цвета объекта + * Перед использованием вызвать parse(). + */ + public function get diffuseTexCoords():String { + return (diffuse == null && emission == null) ? null : ((diffuse != null) ? diffuse.texCoord : emission.texCoord); + } + +} +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffectParam.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffectParam.as new file mode 100644 index 0000000..68282bb --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeEffectParam.as @@ -0,0 +1,85 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeEffectParam extends DaeElement { + + use namespace collada; + + private var effect:DaeEffect; + + public function DaeEffectParam(data:XML, effect:DaeEffect) { + super(data, effect.document); + this.effect = effect; + } + + public function getFloat(setparams:Object):Number { + var floatXML:XML = data.float[0]; + if (floatXML != null) { + return parseNumber(floatXML); + } + var paramRef:XML = data.param.@ref[0]; + if (paramRef != null) { + var param:DaeParam = effect.getParam(paramRef.toString(), setparams); + if (param != null) { + return param.getFloat(); + } + } + return NaN; + } + + public function getColor(setparams:Object):Array { + var colorXML:XML = data.color[0]; + if (colorXML != null) { + return parseNumbersArray(colorXML); + } + var paramRef:XML = data.param.@ref[0]; + if (paramRef != null) { + var param:DaeParam = effect.getParam(paramRef.toString(), setparams); + if (param != null) { + return param.getFloat4(); + } + } + return null; + } + + private function get texture():String { + var attr:XML = data.texture.@texture[0]; + return (attr == null) ? null : attr.toString(); + } + + public function getSampler(setparams:Object):DaeParam { + var sid:String = texture; + if (sid != null) { + return effect.getParam(sid, setparams); + } + return null; + } + + public function getImage(setparams:Object):DaeImage { + var sampler:DaeParam = getSampler(setparams); + if (sampler != null) { + var surfaceSID:String = sampler.surfaceSID; + if (surfaceSID != null) { + var surface:DaeParam = effect.getParam(surfaceSID, setparams); + if (surface != null) { + return surface.image; + } + } else { + return sampler.image; + } + } else { + // Возможно файл был экспортирован стандартным экспортом макса, который забивает на спецификацию и хранит ссылку прямо на image. + return document.findImageByID(texture); + } + return null; + } + + public function get texCoord():String { + var attr:XML = data.texture.@texcoord[0]; + return (attr == null) ? null : attr.toString(); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeElement.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeElement.as new file mode 100644 index 0000000..d7bd448 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeElement.as @@ -0,0 +1,94 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeElement { + + use namespace collada; + + public var document:DaeDocument; + + public var data:XML; + + /** + * -1 - not parsed, 0 - parsed with error, 1 - parsed without error. + */ + private var _parsed:int = -1; + + public function DaeElement(data:XML, document:DaeDocument) { + this.document = document; + this.data = data; + } + + /** + * Выполняет предварительную настройку объекта. + * + * @return false в случае ошибки. + */ + public function parse():Boolean { + // -1 - not parsed, 0 - parsed with error, 1 - parsed without error. + if (_parsed < 0) { + _parsed = parseImplementation() ? 1 : 0; + return _parsed != 0; + } + return _parsed != 0; + } + + /** + * Переопределяемый метод parse() + */ + protected function parseImplementation():Boolean { + return true; + } + + /** + * Возвращает массив значений типа String. + */ + protected function parseStringArray(element:XML):Array { + return element.text().toString().split(/\s+/); + } + + protected function parseNumbersArray(element:XML):Array { + var arr:Array = element.text().toString().split(/\s+/); + for (var i:int = 0, count:int = arr.length; i < count; i++) { + var value:String = arr[i]; + if (value.indexOf(",") != -1) { + value = value.replace(/,/, "."); + } + arr[i] = parseFloat(value); + } + return arr; + } + + protected function parseIntsArray(element:XML):Array { + var arr:Array = element.text().toString().split(/\s+/); + for (var i:int = 0, count:int = arr.length; i < count; i++) { + var value:String = arr[i]; + arr[i] = parseInt(value, 10); + } + return arr; + } + + protected function parseNumber(element:XML):Number { + var value:String = element.toString().replace(/,/, "."); + return parseFloat(value); + } + + public function get id():String { + var idXML:XML = data.@id[0]; + return (idXML == null) ? null : idXML.toString(); + } + + public function get sid():String { + var attr:XML = data.@sid[0]; + return (attr == null) ? null : attr.toString(); + } + + public function get name():String { + var nameXML:XML = data.@name[0]; + return (nameXML == null) ? null : nameXML.toString(); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeGeometry.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeGeometry.as new file mode 100644 index 0000000..ab68d87 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeGeometry.as @@ -0,0 +1,125 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + /** + * @private + */ + public class DaeGeometry extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + private var primitives:Vector.; + + private var vertices:DaeVertices; + + public function DaeGeometry(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются элементы: sources, vertices. + // sources мы создаем внутри DaeDocument, здесь не нужно. + constructVertices(); + } + + private function constructVertices():void { + var verticesXML:XML = data.mesh.vertices[0]; + if (verticesXML != null) { + vertices = new DaeVertices(verticesXML, document); + document.vertices[vertices.id] = vertices; + } + } + + override protected function parseImplementation():Boolean { + if (vertices != null) { + return parsePrimitives(); + } + return false; + } + + private function parsePrimitives():Boolean { + primitives = new Vector.(); + var children:XMLList = data.mesh.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case "polygons": + case "polylist": + case "triangles": + case "trifans": + case "tristrips": + primitives.push(new DaePrimitive(child, document)); + break; + } + } + return true; + } + + /** + * Создает геометрию и возвращает в виде меша. + * Перед использованием вызвать parse(). + * + * @param materials словарь материалов. + */ + public function parseMesh(materials:Object):Mesh { + if (data.mesh.length() > 0) { + var mesh:Mesh = parseAlternativa3DObject(false); + if (mesh == null) { + mesh = new Mesh(); + } + fillInMesh(mesh, materials); + cleanVertices(mesh); + mesh.calculateNormals(true); + mesh.calculateBounds(); + return mesh; + } + return null; + } + + /** + * Заполняет заданный объект геометрией и возвращает массив вершин с индексами. + * Перед использованием вызвать parse(). + * Некоторые вершины в поле value содержат ссылку на дубликат вершины. + * После использования нужно вызвать cleanVertices для зачистки вершин от временных данных. + * + * @return массив вершин с индексами. У вершины в поле value задается дубликат вершины. + */ + public function fillInMesh(mesh:Mesh, materials:Object):Vector. { + vertices.parse(); + var createdVertices:Vector. = vertices.fillInMesh(mesh); + for (var i:int = 0, count:int = primitives.length; i < count; i++) { + var primitive:DaePrimitive = primitives[i]; + primitive.parse(); + if (primitive.verticesEquals(vertices)) { + primitive.fillInMesh(mesh, createdVertices, materials[primitive.materialSymbol]); + } else { + // Ошибка, нельзя использовать вершины из другой геометрии + } + } + return createdVertices; + } + + /** + * Зачищает вершины от временных данных + */ + public function cleanVertices(mesh:Mesh):void { + for (var vertex:Vertex = mesh.vertexList; vertex != null; vertex = vertex.next) { + vertex.index = 0; + vertex.value = null; + } + } + + public function parseAlternativa3DObject(skin:Boolean = false):Mesh { + var profile:XML = data.mesh.extra.technique.(@profile == "Alternativa3D")[0]; + if (profile != null) { + var meshXML:XML = profile.mesh[0]; + if (meshXML != null) { + return (new DaeAlternativa3DObject(meshXML, document)).parseMesh(skin); + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeImage.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeImage.as new file mode 100644 index 0000000..8c403b9 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeImage.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeImage extends DaeElement { + + use namespace collada; + + public function DaeImage(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get init_from():String { + var element:XML = data.init_from[0]; + if (element != null) { + if (document.versionMajor > 4) { + var refXML:XML = element.ref[0]; + return (refXML == null) ? null : refXML.text().toString(); + } + return element.text().toString(); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInput.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInput.as new file mode 100644 index 0000000..441efee --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInput.as @@ -0,0 +1,53 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeInput extends DaeElement { + + use namespace collada; + + public function DaeInput(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get semantic():String { + var attribute:XML = data.@semantic[0]; + return (attribute == null) ? null : attribute.toString(); + } + + public function get source():XML { + return data.@source[0]; + } + + public function get offset():int { + var attr:XML = data.@offset[0]; + return (attr == null) ? 0 : parseInt(attr.toString(), 10); + } + + public function get setNum():int { + var attr:XML = data.@set[0]; + return (attr == null) ? 0 : parseInt(attr.toString(), 10); + } + + /** + * Если DaeSource по ссылке source имеет тип значений Number и + * количество компонент не меньше заданного, то этот метод его вернет. + */ + public function prepareSource(minComponents:int):DaeSource { + var source:DaeSource = document.findSource(this.source); + if (source != null) { + source.parse(); + if (source.numbers != null && source.stride >= minComponents) { + return source; + } else { + // document.logger.logNotEnoughDataError(); + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceController.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceController.as new file mode 100644 index 0000000..eb8f85d --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceController.as @@ -0,0 +1,55 @@ +package alternativa.engine3d.loaders.collada { + + + /** + * @private + */ + public class DaeInstanceController extends DaeElement { + + use namespace collada; + + public var node:DaeNode; + + public var skin:DaeAnimatedObject; + + public function DaeInstanceController(data:XML, document:DaeDocument, node:DaeNode) { + super(data, document); + this.node = node; + } + + public function get controller():DaeController { + var controller:DaeController = document.findController(data.@url[0]); + if (controller == null) { + document.logger.logNotFoundError(data.@url[0]); + } + return controller; + } + + public function get skeletons():Vector. { + var list:XMLList = data.skeleton; + if (list.length() > 0) { + var skeletons:Vector. = new Vector.(); + for (var i:int = 0, count:int = list.length(); i < count; i++) { + var skeletonXML:XML = list[i]; + var skel:DaeNode = document.findNode(skeletonXML.text()[0]); + if (skel != null) { + skeletons.push(skel); + } else { + document.logger.logNotFoundError(skeletonXML); + } + } + return skeletons; + } + return null; + } + + public function findRootJointNodes():Vector. { + var controller:DaeController = this.controller; + if (controller != null) { + return controller.findRootJointNodes(this.skeletons); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as new file mode 100644 index 0000000..fef0585 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as @@ -0,0 +1,39 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeInstanceMaterial extends DaeElement { + + use namespace collada; + + public function DaeInstanceMaterial(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get symbol():String { + var attribute:XML = data.@symbol[0]; + return (attribute == null) ? null : attribute.toString(); + } + + private function get target():XML { + return data.@target[0]; + } + + public function get material():DaeMaterial { + var mat:DaeMaterial = document.findMaterial(target); + if (mat == null) { + document.logger.logNotFoundError(target); + } + return mat; + } + + public function getBindVertexInputSetNum(semantic:String):int { + var bindVertexInputXML:XML = data.bind_vertex_input.(@semantic == semantic)[0]; + if (bindVertexInputXML == null) return 0; + var setNumXML:XML = bindVertexInputXML.@input_set[0]; + return (setNumXML == null) ? 0 : parseInt(setNumXML.toString(), 10); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeLogger.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeLogger.as new file mode 100644 index 0000000..6887544 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeLogger.as @@ -0,0 +1,53 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeLogger { + + public function DaeLogger() { + } + + private function logMessage(message:String, element:XML):void { + // var index:int = element.childIndex(); + var index:int = 0; + var name:String = (element.nodeKind() == "attribute") ? "@" + element.localName() : element.localName() + ((index > 0) ? "[" + index + "]" : ""); + var parent:* = element.parent(); + while (parent != null) { + // index = parent.childIndex(); + name = parent.localName() + ((index > 0) ? "[" + index + "]" : "") + "." + name; + parent = parent.parent(); + } + trace(message, '| "' + name + '"'); + } + + private function logError(message:String, element:XML):void { + logMessage("[ERROR] " + message, element); + } + + public function logExternalError(element:XML):void { + logError("External urls don't supported", element); + } + + public function logSkewError(element:XML):void { + logError(" don't supported", element); + } + + public function logJointInAnotherSceneError(element:XML):void { + logError("Joints in different scenes don't supported", element); + } + + public function logInstanceNodeError(element:XML):void { + logError(" don't supported", element); + } + + public function logNotFoundError(element:XML):void { + logError("Element with url \"" + element.toString() + "\" not found", element); + } + + public function logNotEnoughDataError(element:XML):void { + logError("Not enough data", element); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeMaterial.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeMaterial.as new file mode 100644 index 0000000..01f7c78 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeMaterial.as @@ -0,0 +1,61 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.materials.Material; + + /** + * @private + */ + public class DaeMaterial extends DaeElement { + + use namespace collada; + + /** + * Материал движка. + * Перед использованием вызвать parse(). + */ + public var material:Material; + + /** + * Имя текстурного канала для карты цвета объекта + * Перед использованием вызвать parse(). + */ + public var diffuseTexCoords:String; + + /** + * Материал используется. + */ + public var used:Boolean = false; + + public function DaeMaterial(data:XML, document:DaeDocument) { + super(data, document); + } + + private function parseSetParams():Object { + var params:Object = new Object(); + var list:XMLList = data.instance_effect.setparam; + for each (var element:XML in list) { + var param:DaeParam = new DaeParam(element, document); + params[param.ref] = param; + } + return params; + } + + private function get effectURL():XML { + return data.instance_effect.@url[0]; + } + + override protected function parseImplementation():Boolean { + var effect:DaeEffect = document.findEffect(effectURL); + if (effect != null) { + effect.parse(); + material = effect.getMaterial(parseSetParams()); + diffuseTexCoords = effect.diffuseTexCoords; + if (material != null) { + material.name = name; + } + return true; + } + return false; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeNode.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeNode.as new file mode 100644 index 0000000..2ceb1f3 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeNode.as @@ -0,0 +1,456 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.ComplexAnimation; + import alternativa.engine3d.animation.MatrixAnimation; + import alternativa.engine3d.animation.ObjectAnimation; + import alternativa.engine3d.animation.TransformAnimation; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + /** + * @private + */ + public class DaeNode extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + public var scene:DaeVisualScene; + public var parent:DaeNode; + + // Скин или рутовая кость + public var skiped:Boolean = false; + + /** + * Анимационные каналы этой ноды + */ + private var channels:Vector.; + + /** + * Вектор контроллеров, которые ссылаются на эту ноду + */ + private var instanceControllers:Vector.; + + /** + * Массив нод в этой ноде. + */ + public var nodes:Vector.; + + /** + * Массив объектов в этой ноде. + * Перед использованием вызвать parse(). + */ + public var objects:Vector.; + + /** + * Вектор скинов в этой ноде. + * Перед использованием вызвать parse(). + */ + public var skins:Vector.; + + /** + * Создание ноды из xml. Рекурсивно создаются дочерние ноды. + */ + public function DaeNode(data:XML, document:DaeDocument, scene:DaeVisualScene = null, parent:DaeNode = null) { + super(data, document); + + this.scene = scene; + this.parent = parent; + + // Внутри объявляются другие node. + constructNodes(); + } + + private function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = new DaeNode(nodesList[i], document, scene, this); + if (node.id != null) { + document.nodes[node.id] = node; + } + nodes[i] = node; + } + } + + public function addChannel(channel:DaeChannel):void { + if (channels == null) { + channels = new Vector.(); + } + channels.push(channel); + } + + public function addInstanceController(controller:DaeInstanceController):void { + if (instanceControllers == null) { + instanceControllers = new Vector.(); + } + instanceControllers.push(controller); + } + + override protected function parseImplementation():Boolean { + this.skins = parseSkins(); + this.objects = parseObjects(); + return true; + } + + private function parseInstanceMaterials(geometry:XML):Object { + var instances:Object = new Object(); + var list:XMLList = geometry.bind_material.technique_common.instance_material; + for (var i:int = 0, count:int = list.length(); i < count; i++) { + var instance:DaeInstanceMaterial = new DaeInstanceMaterial(list[i], document); + instances[instance.symbol] = instance; + } + return instances; + } + + /** + * Возвращает ноду по сиду. + */ + public function getNodeBySid(sid:String):DaeNode { + if (sid == this.sid) { + return this; + } + + var levelNodes:Vector. > = new Vector. >; + var levelNodes2:Vector. > = new Vector. >; + + levelNodes.push(nodes); + var len:int = levelNodes.length; + while (len > 0) { + for (var i:int = 0; i < len; i++) { + var children:Vector. = levelNodes[i]; + var count:int = children.length; + for (var j:int = 0; j < count; j++) { + var node:DaeNode = children[j]; + if (node.sid == sid) { + return node; + } + if (node.nodes.length > 0) { + levelNodes2.push(node.nodes); + } + } + } + var temp:Vector. > = levelNodes; + levelNodes = levelNodes2; + levelNodes2 = temp; + levelNodes2.length = 0; + + len = levelNodes.length; + } + return null; + } + + /** + * Парсит и возвращает массив скинов, связанных с этой нодой. + */ + public function parseSkins():Vector. { + if (instanceControllers == null) { + return null; + } + var skins:Vector. = new Vector.(); + for (var i:int = 0, count:int = instanceControllers.length; i < count; i++) { + var instanceController:DaeInstanceController = instanceControllers[i]; + var controller:DaeController = instanceController.controller; + if (controller != null) { + controller.parse(); + var animatedSkinAndJoints:DaeAnimatedObject = controller.parseSkin(this, parseInstanceMaterials(instanceController.data), instanceController.skeletons); + if (animatedSkinAndJoints != null) { + var skin:Skin = Skin(animatedSkinAndJoints.object); + // Имя берем из ноды, содержащей instance_controller + skin.name = instanceController.node.name; + var animatedSkin:DaeAnimatedObject = applyAnimation(applyTransformations(skin)); + if (animatedSkin.animation != null) { + if (animatedSkinAndJoints.animation != null) { + var complex:ComplexAnimation = new ComplexAnimation(); + complex.addAnimation(animatedSkin.animation); + complex.addAnimation(animatedSkinAndJoints.animation); + animatedSkin.animation = complex; + } + } else { + animatedSkin.animation = animatedSkinAndJoints.animation; + } + skins.push(animatedSkin); + } + } + } + return skins; + } + + private function getNewName(index:int = 0):String { + var name:String = this.name; + if (name != null) { + if (index == 0) { + return name; + } else { + return name + "-" + index; + } + } + return null; + } + + /** + * Парсит и возвращает массив объектов, связанных с этой нодой. + * Может быть Mesh или Object3D, если неизвестен тип объекта. + */ + public function parseObjects():Vector. { + var objects:Vector. = new Vector.(); + if (isAlternativa3DObject()) { + var a3dObject:Object3D = parseAlternativa3DObject(); + if (a3dObject != null) { + a3dObject.name = name; + objects.push(applyAnimation(applyTransformations(a3dObject))); + return objects; + } + } else { + var children:XMLList = data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case "instance_camera": + var cam:DaeCamera = document.findCamera(child.@url[0]); + var camera:Camera3D = cam.parseCamera(); + camera.name = getNewName(objects.length); + // Поворачиваем на 180 градусов по оси X, чтобы соответствовало движку. + var rotXMatrix:Matrix3D = new Matrix3D(); + rotXMatrix.appendRotation(180, Vector3D.X_AXIS); + objects.push(applyAnimation(applyTransformations(camera, rotXMatrix))); + break; + case "instance_geometry": + var geom:DaeGeometry = document.findGeometry(child.@url[0]); + if (geom != null) { + geom.parse(); + var mesh:Mesh = geom.parseMesh(parseInstanceMaterials(child)); + if (mesh != null) { + mesh.name = getNewName(objects.length); + objects.push(applyAnimation(applyTransformations(mesh))); + } + } else { + document.logger.logNotFoundError(child.@url[0]); + } + break; + // case "instance_controller": + // Парсится в методе parseSkins(); + // break; + case "instance_node": + document.logger.logInstanceNodeError(child); + // var instanceNode:DaeNode = document.findNode(child.@url[0]); + // instanceNode.parse(); + // if (instanceNode != null) { + // var instances:Vector. = instanceNode.parseObjects(); + // for (var j:int = 0, num:int = instances.length; j < num; j++) { + // var instance:Object3D = instances[j]; + // objects.push(applyTransformationsAndAnimation(instance, instance.getMatrix())); + // } + // } else { + // document.logger.logNotFoundError(child.@url[0]); + // } + break; + } + } + } + if (objects.length == 0) { + // Не нашлось ни одного подходящего объекта, создаем Object3D. + var object:Object3D = new Object3D(); + object.name = name; + objects.push(applyAnimation(applyTransformations(object))); + } + return objects; + } + + /** + * Возвращает трансформацию ноды в виде матрицы + * + * @param initialMatrix матрица, к которой будет добавлена трансформация ноды + */ + public function getMatrix(initialMatrix:Matrix3D = null):Matrix3D { + var matrix:Matrix3D = (initialMatrix == null) ? new Matrix3D() : initialMatrix; + var components:Array; + var children:XMLList = data.children(); + for (var i:int = children.length() - 1; i >= 0; i--) { + // Трансформации накладываются с конца в начало + var child:XML = children[i]; + var sid:XML = child.@sid[0]; + if (sid != null && sid.toString() == "post-rotationY") { + // Стандартный экспорт макса записал какой-то хлам, игнорируем + continue; + } + switch (child.localName()) { + case "scale" : { + components = parseNumbersArray(child); + matrix.appendScale(components[0], components[1], components[2]); + break; + } + case "rotate" : { + components = parseNumbersArray(child); + matrix.appendRotation(components[3], new Vector3D(components[0], components[1], components[2])); + break; + } + case "translate" : { + components = parseNumbersArray(child); + matrix.appendTranslation(components[0], components[1], components[2]); + break; + } + case "matrix" : { + components = parseNumbersArray(child); + matrix.append(new Matrix3D(Vector.([components[0], components[4], components[8], components[12], + components[1], components[5], components[9], components[13], + components[2], components[6], components[10], components[14], + components[3] ,components[7], components[11], components[15]]))); + break; + } + case "lookat" : { + // components = parseNumbersArray(child); + break; + } + case "skew" : { + document.logger.logSkewError(child); + break; + } + } + } + return matrix; + } + + /** + * Назначает контроллер анимации к объекту. + * + * @param animation анимация которую следует применить к объекту, + * если null, будет создана новая анимация из ноды. + */ + public function applyAnimation(object:Object3D, animation:ObjectAnimation = null):DaeAnimatedObject { + animation = (animation == null) ? parseAnimation() : animation; + if (animation != null) { + animation.object = object; + } + return new DaeAnimatedObject(object, animation); + } + + /** + * Применяет трансформацию к объекту. + * + * @param prepend если не равен null, трансформация добавляется к этой матрице. + */ + public function applyTransformations(object:Object3D, prepend:Matrix3D = null, append:Matrix3D = null):Object3D { + if (append != null) { + var matrix:Matrix3D = getMatrix(prepend); + matrix.append(append); + object.setMatrix(matrix); + } else { + object.setMatrix(getMatrix(prepend)); + } + return object; + } + + private function isAlternativa3DObject():Boolean { + return data.extra.technique.(@profile == "Alternativa3D")[0] != null; + } + + private function parseAlternativa3DObject():Object3D { + var profile:XML = data.extra.technique.(@profile == "Alternativa3D")[0]; + if (profile != null) { + var containerXML:XML = profile.instance_container[0]; + if (containerXML != null) { + var container:DaeAlternativa3DObject = document.findAlternativa3DObject(containerXML.@url[0]); + if (container != null) { + return container.parseContainer(); + } else { + document.logger.logNotFoundError(containerXML.@url[0]) + } + } + var spriteXML:XML = profile.instance_sprite[0]; + if (spriteXML != null) { + var sprite:DaeAlternativa3DObject = document.findAlternativa3DObject(spriteXML.@url[0]); + if (sprite != null) { + var material:DaeMaterial = document.findMaterial(spriteXML.instance_material.@target[0]); + if (material != null) { + material.parse(); + material.used = true; + return sprite.parseSprite3D(material.material); + } else { + return sprite.parseSprite3D(); + } + } else { + document.logger.logNotFoundError(spriteXML.@url[0]) + } + } + var lodXML:XML = profile.instance_lod[0]; + if (lodXML != null) { + var lod:DaeAlternativa3DObject = document.findAlternativa3DObject(lodXML.@url[0]); + if (lod != null) { + return lod.parseLOD(); + } else { + document.logger.logNotFoundError(lodXML.@url[0]); + } + } + } + return null; + } + + /** + * Возвращает анимацию ноды. + */ + public function parseAnimation():ObjectAnimation { + if (channels == null) { + return null; + } + var channel:DaeChannel = channels[0]; + channel.parse(); + if (channel.animatedParam == DaeChannel.PARAM_MATRIX) { + // Анимация матрицы + var matrixAnimation:MatrixAnimation = new MatrixAnimation(); + matrixAnimation.matrix = channel.track; + return matrixAnimation; + } + // Это не анимация матрицы, значит покомпонентная анимация + var animation:TransformAnimation = new TransformAnimation(); + var count:int = channels.length; + for (var i:int = 0; i < count; i++) { + channel = channels[i]; + channel.parse(); + switch (channel.animatedParam) { + case DaeChannel.PARAM_TRANSLATE: + animation.translation = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_X: + animation.x = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_Y: + animation.y = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_Z: + animation.z = channel.track; + break; + case DaeChannel.PARAM_ROTATION_X: + animation.rotationX = channel.track; + break; + case DaeChannel.PARAM_ROTATION_Y: + animation.rotationY = channel.track; + break; + case DaeChannel.PARAM_ROTATION_Z: + animation.rotationZ = channel.track; + break; + case DaeChannel.PARAM_SCALE: + animation.scale = channel.track; + break; + case DaeChannel.PARAM_SCALE_X: + animation.scaleX = channel.track; + break; + case DaeChannel.PARAM_SCALE_Y: + animation.scaleY = channel.track; + break; + case DaeChannel.PARAM_SCALE_Z: + animation.scaleZ = channel.track; + break; + } + } + return animation; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeParam.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeParam.as new file mode 100644 index 0000000..57b565d --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeParam.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeParam extends DaeElement { + + use namespace collada; + + public function DaeParam(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get ref():String { + var attribute:XML = data.@ref[0]; + return (attribute == null) ? null : attribute.toString(); + } + + public function getFloat():Number { + var floatXML:XML = data.float[0]; + if (floatXML != null) { + return parseNumber(floatXML); + } + return NaN; + } + + public function getFloat4():Array { + var element:XML = data.float4[0]; + var components:Array; + if (element == null) { + element = data.float3[0]; + if (element != null) { + components = parseNumbersArray(element); + components[3] = 1.0; + } + } else { + components = parseNumbersArray(element); + } + return components; + } + + /** + * Возвращает sid параметра с типом surface. Только если тип этого элемента sampler2D и версия коллады 1.4. + */ + public function get surfaceSID():String { + var element:XML = data.sampler2D.source[0]; + return (element == null) ? null : element.text().toString(); + } + + public function get wrap_s():String { + var element:XML = data.sampler2D.wrap_s[0]; + return (element == null) ? null : element.text().toString(); + } + + public function get image():DaeImage { + var surface:XML = data.surface[0]; + var image:DaeImage; + if (surface != null) { + // Collada 1.4 + var init_from:XML = surface.init_from[0]; + if (init_from == null) { + // Error + return null; + } + image = document.findImageByID(init_from.text().toString()); + } else { + // Collada 1.5 + var imageIDXML:XML = data.instance_image.@url[0]; + if (imageIDXML == null) { + // error + return null; + } + image = document.findImage(imageIDXML); + } + return image; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaePrimitive.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaePrimitive.as new file mode 100644 index 0000000..dbffd6a --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaePrimitive.as @@ -0,0 +1,266 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.Mesh; + + /** + * @private + */ + public class DaePrimitive extends DaeElement { + + use namespace collada; + + private var verticesInput:DaeInput; + private var texCoordsInputs:Vector.; + private var inputsStride:int; + + public function DaePrimitive(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + parseInputs(); + return true; + } + + private function parseInputs():void { + texCoordsInputs = new Vector.(); + var inputsList:XMLList = data.input; + var maxInputOffset:int = 0; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "VERTEX" : + if (verticesInput == null) { + verticesInput = input; + } + break; + case "TEXCOORD" : + texCoordsInputs.push(input); + break; + } + } + var offset:int = input.offset; + maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset; + } + inputsStride = maxInputOffset + 1; + } + + private function findTexCoordsInput(setNum:int):DaeInput { + for (var i:int = 0, count:int = texCoordsInputs.length; i < count; i++) { + var texCoordsInput:DaeInput = texCoordsInputs[i]; + if (texCoordsInput.setNum == setNum) { + return texCoordsInput; + } + } + return (texCoordsInputs.length > 0) ? texCoordsInputs[0] : null; + } + + private function get type():String { + return data.localName() as String; + } + + /** + * Заполняет заданный меш геометрией этого примитива, используя заданные вершины. + * На вершины накладывается uv маппинг. + * Перед использованием вызвать parse(). + */ + public function fillInMesh(mesh:Mesh, vertices:Vector., instanceMaterial:DaeInstanceMaterial = null):void { + var countXML:XML = data.@count[0]; + if (countXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + var numPrimitives:int = parseInt(countXML.toString(), 10); + var texCoordsInput:DaeInput; + var material:Material; + if (instanceMaterial != null) { + var dmat:DaeMaterial = instanceMaterial.material; + dmat.parse(); + if (dmat.diffuseTexCoords != null) { + texCoordsInput = findTexCoordsInput(instanceMaterial.getBindVertexInputSetNum(dmat.diffuseTexCoords)); + } else { + texCoordsInput = findTexCoordsInput(-1); + } + dmat.used = true; + material = dmat.material; + } else { + texCoordsInput = findTexCoordsInput(-1); + } + if (texCoordsInput != null) { + // Если у вершины index != -1, значит вершина где-то используется и ее нужно сдублировать + // Устанавливаем для такой вершины index в -2 + for each (var vertex:Vertex in vertices) { + while (vertex != null && vertex.index != -1) { + vertex.index = -2; + // Переходим к следующему дубликату + vertex = vertex.value; + } + } + } + var texCoords:Vector.; + var texCoordsStride:int = 1; + var texCoordsOffset:int = 0; + if (texCoordsInput != null) { + var texCoordsSource:DaeSource = texCoordsInput.prepareSource(2); + if (texCoordsSource != null) { + texCoords = texCoordsSource.numbers; + texCoordsStride = texCoordsSource.stride; + texCoordsOffset = texCoordsInput.offset; + } + } + var indicesXML:XML; + var indices:Array; + switch (this.type) { + case "polygons" : { + if (data.ph.length() > 0) { + // Полигоны с дырками не поддерживаются + // document.logger.lo + } + var indicesList:XMLList = data.p; + for (var i:int = 0, count:int = indicesList.length(); i < count; i++) { + indices = parseIntsArray(indicesList[i]); + fillInPolygon(mesh, material, vertices, verticesInput.offset, indices.length/inputsStride, indices, texCoords, texCoordsStride, texCoordsOffset); + } + break; + } + case "polylist" : { + indicesXML = data.p[0]; + if (indicesXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + indices = parseIntsArray(indicesXML); + var vcountsXML:XML = data.vcount[0]; + var vcounts:Array; + if (vcountsXML != null) { + vcounts = parseIntsArray(vcountsXML); + if (vcounts.length < numPrimitives) { + return; + } + fillInPolylist(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, vcounts, texCoords, texCoordsStride, texCoordsOffset); + } else { + fillInPolygon(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, texCoords, texCoordsStride, texCoordsOffset); + } + break; + } + case "triangles" : { + indicesXML = data.p[0]; + if (indicesXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + indices = parseIntsArray(indicesXML); + fillInTriangles(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, texCoords, texCoordsStride, texCoordsOffset); + break; + } + } + } + + /** + * Добавляет uv координаты вершине или создает новую вершину, если нельзя добавить в эту. + * Новая вершина добавляется в список value этой вершины. + * @return вершина с заданными uv координатами + */ + private function applyUV(mesh:Mesh, vertex:Vertex, texCoords:Vector., index:int):Vertex { + var u:Number = texCoords[index]; + var v:Number = 1 - texCoords[int(index + 1)]; + if (vertex.index == -1) { + // Была без uv координат + vertex.u = u; + vertex.v = v; + vertex.index = index; + return vertex; + } + if (vertex.index == index) { + return vertex; + } else { + // Дублируем вершину, если её index отличается от индекса заданной uv координаты + while (vertex.value != null) { + vertex = vertex.value; + if (vertex.index == index) { + return vertex; + } + } + // Последний элемент, создаем дубликат и возвращаем + vertex.value = mesh.addVertex(vertex.x, vertex.y, vertex.z, u, v); + vertex = vertex.value; + vertex.index = index; + return vertex; + } + } + + /** + * Создает один полигон. + */ + private function fillInPolygon(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numIndices:int, indices:Array, texCoords:Vector., texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + var polygon:Vector. = new Vector.(numIndices); + for (var i:int = 0; i < numIndices; i++) { + var vertex:Vertex = vertices[indices[int(inputsStride*i + verticesOffset)]]; + if (texCoords != null) { + vertex = applyUV(mesh, vertex, texCoords, texCoordsStride*indices[int(inputsStride*i + texCoordsOffset)]); + } + polygon[i] = vertex; + } + mesh.addFace(polygon, material); + } + + private function fillInPolylist(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numFaces:int, indices:Array, vcounts:Array, texCoords:Vector. = null, texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + var polygon:Vector. = new Vector.(); + var polyIndex:int = 0; + for (var i:int = 0; i < numFaces; i++) { + var count:int = vcounts[i]; + if (count >= 3) { + polygon.length = count; + for (var j:int = 0; j < count; j++) { + var vertexIndex:int = inputsStride*(polyIndex + j); + var vertex:Vertex = vertices[indices[int(vertexIndex + verticesOffset)]]; + if (texCoords != null) { + vertex = applyUV(mesh, vertex, texCoords, texCoordsStride*indices[int(vertexIndex + texCoordsOffset)]); + } + polygon[j] = vertex; + } + polyIndex += count; + mesh.addFace(polygon, material); + } + } + } + + private function fillInTriangles(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numFaces:int, indices:Array, texCoords:Vector. = null, texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + for (var i:int = 0; i < numFaces; i++) { + var index:int = 3*inputsStride*i; + var vertexIndex:int = index + verticesOffset; + var a:Vertex = vertices[indices[int(vertexIndex)]]; + var b:Vertex = vertices[indices[int(vertexIndex + inputsStride)]]; + var c:Vertex = vertices[indices[int(vertexIndex + 2*inputsStride)]]; + if (texCoords != null) { + var texIndex:int = index + texCoordsOffset; + a = applyUV(mesh, a, texCoords, texCoordsStride*indices[int(texIndex)]); + b = applyUV(mesh, b, texCoords, texCoordsStride*indices[int(texIndex + inputsStride)]); + c = applyUV(mesh, c, texCoords, texCoordsStride*indices[int(texIndex + 2*inputsStride)]); + } + mesh.addTriFace(a, b, c, material); + } + } + + /** + * Сравнивает вершины, используемые в примитиве с указанными + * Перед использованием вызвать parse(). + */ + public function verticesEquals(otherVertices:DaeVertices):Boolean { + var vertices:DaeVertices = document.findVertices(verticesInput.source); + if (vertices == null) { + document.logger.logNotFoundError(verticesInput.source); + } + return vertices == otherVertices; + } + + public function get materialSymbol():String { + var attr:XML = data.@material[0]; + return (attr == null) ? null : attr.toString(); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSampler.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSampler.as new file mode 100644 index 0000000..7376983 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSampler.as @@ -0,0 +1,104 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.MatrixKey; + import alternativa.engine3d.animation.PointKey; + import alternativa.engine3d.animation.Track; + import alternativa.engine3d.animation.ValueKey; + + import flash.geom.Matrix3D; + + /** + * @private + */ + public class DaeSampler extends DaeElement { + + use namespace collada; + + private var times:Vector.; + private var values:Vector.; + private var timesStride:int; + private var valuesStride:int; + + public function DaeSampler(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + var inputsList:XMLList = data.input; + + var inputSource:DaeSource; + var outputSource:DaeSource; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "INPUT" : + inputSource = input.prepareSource(1); + if (inputSource != null) { + times = inputSource.numbers; + timesStride = inputSource.stride; + } + break; + case "OUTPUT" : + outputSource = input.prepareSource(1); + if (outputSource != null) { + values = outputSource.numbers; + valuesStride = outputSource.stride; + } + break; + } + } + } + return true; + } + + public function parseValuesTrack():Track { + if (times != null && values != null && timesStride > 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + track.addKey(new ValueKey(times[int(timesStride*i)], values[int(valuesStride*i)])); + } + track.sortKeys(); + // TODO:: Всякие исключительные ситуации с индексами + return track; + } + return null; + } + + public function parseMatrixTrack():Track { + if (times != null && values != null && timesStride != 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + var index:int = valuesStride*i; + var matrix:Matrix3D = new Matrix3D(Vector.([values[index], values[index + 4], values[index + 8], values[index + 12], + values[index + 1], values[index + 5], values[index + 9], values[index + 13], + values[index + 2], values[index + 6], values[index + 10], values[index + 14], + values[index + 3] ,values[index + 7], values[index + 11], values[index + 15]])); + track.addKey(new MatrixKey(times[i*timesStride], matrix)); + } + track.sortKeys(); + return track; + } + return null; + } + + public function parsePointsTrack():Track { + if (times != null && values != null && timesStride != 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + var index:int = i*valuesStride; + track.addKey(new PointKey(times[i*timesStride], values[index], values[index + 1], values[index + 2])); + } + track.sortKeys(); + return track; + // TODO:: Всякие исключительные ситуации с индексами + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSource.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSource.as new file mode 100644 index 0000000..8ee6f55 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeSource.as @@ -0,0 +1,153 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeSource extends DaeElement { + + use namespace collada; + + /** + * Типы массивов + */ + private const FLOAT_ARRAY:String = "float_array"; + private const INT_ARRAY:String = "int_array"; + private const NAME_ARRAY:String = "Name_array"; + /** + * Массив элементов типа Number. + * Перед использованием вызвать parse(). + */ + public var numbers:Vector.; + /** + * Массив элементов типа int. + * Перед использованием вызвать parse(). + */ + public var ints:Vector.; + /** + * Массив элементов типа string. + * Перед использованием вызвать parse(). + */ + + public var names:Vector.; + /** + * Размерность типов в массиве numbers или ints. + * Перед использованием вызвать parse(). + */ + public var stride:int; + + public function DaeSource(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются arrays. + constructArrays(); + } + + private function constructArrays():void { + var children:XMLList = data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case FLOAT_ARRAY : + case INT_ARRAY : + case NAME_ARRAY : + var array:DaeArray = new DaeArray(child, document); + if (array.id != null) { + document.arrays[array.id] = array; + } + break; + } + } + } + + private function get accessor():XML { + return data.technique_common.accessor[0]; + } + + override protected function parseImplementation():Boolean { + var accessor:XML = this.accessor; + if (accessor != null) { + var arrayXML:XML = accessor.@source[0]; + var array:DaeArray = (arrayXML == null) ? null : document.findArray(arrayXML); + if (array != null) { + var countXML:String = accessor.@count[0]; + if (countXML != null) { + var count:int = parseInt(countXML.toString(), 10); + var offsetXML:XML = accessor.@offset[0]; + var strideXML:XML = accessor.@stride[0]; + var offset:int = (offsetXML == null) ? 0 : parseInt(offsetXML.toString(), 10); + var stride:int = (strideXML == null) ? 1 : parseInt(strideXML.toString(), 10); + array.parse(); + if (array.array.length < (offset + (count*stride))) { + document.logger.logNotEnoughDataError(accessor); + return false; + } + this.stride = parseArray(offset, count, stride, array.array, array.type); + return true; + } + } else { + document.logger.logNotFoundError(arrayXML); + } + } + return false; + } + + private function numValidParams(params:XMLList):int { + var res:int = 0; + for (var i:int = 0, count:int = params.length(); i < count; i++) { + if (params[i].@name[0] != null) { + res++; + } + } + return res; + } + + private function parseArray(offset:int, count:int, stride:int, array:Array, type:String):int { + var params:XMLList = this.accessor.param; + var arrStride:int = Math.max(numValidParams(params), stride); + switch (type) { + case FLOAT_ARRAY: + numbers = new Vector.(int(arrStride*count)); + break; + case INT_ARRAY: + ints = new Vector.(int(arrStride*count)); + break; + case NAME_ARRAY: + names = new Vector.(int(arrStride*count)); + break; + } + var curr:int = 0; + for (var i:int = 0; i < arrStride; i++) { + // Только param, у которого установлен name, должен быть считан + var param:XML = params[i]; + if (param == null || param.hasOwnProperty("@name")) { + var j:int; + switch (type) { + case FLOAT_ARRAY: + for (j = 0; j < count; j++) { + var value:String = array[int(offset + stride*j + i)]; + if (value.indexOf(",") != -1) { + value = value.replace(/,/, "."); + } + numbers[int(arrStride*j + curr)] = parseFloat(value); + } + break; + case INT_ARRAY: + for (j = 0; j < count; j++) { + ints[int(arrStride*j + curr)] = parseInt(array[int(offset + stride*j + i)], 10); + } + break; + case NAME_ARRAY: + for (j = 0; j < count; j++) { + names[int(arrStride*j + curr)] = array[int(offset + stride*j + i)]; + } + break; + + } + curr++; + } + } + return arrStride; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVertices.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVertices.as new file mode 100644 index 0000000..1d685cb --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVertices.as @@ -0,0 +1,58 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + /** + * @private + */ + public class DaeVertices extends DaeElement { + + use namespace collada; + + /** + * Источник данных координат вершин. Содержит координаты в массиве numbers. + * Свойство stride источника не меньше трех. + * Перед использованием вызвать parse(). + */ + private var positions:DaeSource; + //private var texCoords:Vector.; + + public function DaeVertices(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + // Получаем массив координат вершин + var inputXML:XML = data.input.(@semantic == "POSITION")[0]; + if (inputXML != null) { + positions = (new DaeInput(inputXML, document)).prepareSource(3); + if (positions != null) { + return true; + } + } + return false; + } + + /** + * Создает вершины в меше. У каждой вершины index устанавливается в -1. + * Перед использованием вызвать parse(). + * + * @return вектор вершин и их индексов + */ + public function fillInMesh(mesh:Mesh):Vector. { + var stride:int = positions.stride; + var coords:Vector. = positions.numbers; + var numVerts:int = positions.numbers.length/stride; + var createdVertices:Vector. = new Vector.(numVerts); + var i:int; + for (i = 0; i < numVerts; i++) { + var offset:int = stride*i; + var vertex:Vertex = mesh.addVertex(coords[offset], coords[int(offset + 1)], coords[int(offset + 2)], 0, 0); + vertex.index = -1; + createdVertices[i] = vertex; + } + return createdVertices; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVisualScene.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVisualScene.as new file mode 100644 index 0000000..c23fcdf --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/DaeVisualScene.as @@ -0,0 +1,33 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeVisualScene extends DaeElement { + + use namespace collada; + + public var nodes:Vector.; + + public function DaeVisualScene(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются node. + constructNodes(); + } + + public function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = new DaeNode(nodesList[i], document, this); + if (node.id != null) { + document.nodes[node.id] = node; + } + nodes[i] = node; + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/collada.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/collada.as new file mode 100644 index 0000000..4803d66 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/collada/collada.as @@ -0,0 +1,7 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public namespace collada = "http://www.collada.org/2005/11/COLLADASchema"; +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderErrorEvent.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderErrorEvent.as new file mode 100644 index 0000000..3719db1 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderErrorEvent.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.loaders.events { + import flash.events.ErrorEvent; + import flash.events.Event; + + public class LoaderErrorEvent extends ErrorEvent { + + public static const LOADER_ERROR:String = "loaderError"; + + private var _url:String; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param url адрес файла, при загрузке которого произошла проблема + * @param text описание ошибки + */ + public function LoaderErrorEvent(type:String, url:String, text:String) { + super(type); + this.text = text; + _url = url; + } + + /** + * Адрес файла, при загрузке которого произошла проблема + */ + public function get url():String { + return _url; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderErrorEvent(type, _url, text); + } + + /** + * Создаёт строковое представление объекта. + * @return строковое представление объекта + */ + override public function toString():String { + return "[LoaderErrorEvent url=" + _url + ", text=" + text + "]"; + } + + } +} + diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..6768155 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Событие загрузчиков ресурсов, состоящих из нескольких частей. + */ + public class LoaderEvent extends Event { + /** + * Событие начала загрузки очередной части ресурса. + */ + public static const PART_OPEN:String = "partOpen"; + /** + * Событие окончания загрузки очередной части ресурса. + */ + public static const PART_COMPLETE:String = "partComplete"; + + private var _partsTotal:int; + private var _currentPart:int; + private var _target:Object; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param partsTotal общее количество загружаемых частей + * @param currentPart номер части, к которому относится событие. Нумерация начинается с нуля + * @param target объект, к которому относится событие + */ + public function LoaderEvent(type:String, partsTotal:int, currentPart:int, target:Object = null) { + super(type); + _partsTotal = partsTotal; + _currentPart = currentPart; + _target = target; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер части, к которому относится событие. Нумерация начинается с нуля + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Объект, содержащийся в событии + */ + override public function get target():Object { + return _target; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderEvent(type, _partsTotal, _currentPart, _target); + } + + /** + * Создаёт строкове представление объекта. + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", target=" + _target + "]"; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..c66f7f8 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Событие прогресса загрузки ресурсов, состоящих из нескольких частей. + */ + public class LoaderProgressEvent extends ProgressEvent { + + /** + * Событие прогресса загрузки очередной части ресурса. + */ + public static const LOADER_PROGRESS:String = "loaderProgress"; + + private var _filesTotal:int; + private var _filesLoaded:int; + private var _totalProgress:Number = 0; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param filesTotal общее количество загружаемых файлов + * @param filesLoaded количество полностью загруженных файлов + * @param totalProgress общий прогресс загрузки, выраженный числом в интервале [0, 1] + * @param bytesLoaded количество загруженных байт загружаемого в данный момент файла + * @param bytesTotal объём загружаемого в данный момент файла + */ + public function LoaderProgressEvent(type:String, filesTotal:int, filesLoaded:int, totalProgress:Number = 0, bytesLoaded:uint = 0, bytesTotal:uint = 0) { + super(type, false, false, bytesLoaded, bytesTotal); + _filesTotal = filesTotal; + _filesLoaded = filesLoaded; + _totalProgress = totalProgress; + } + + /** + * Общее количество загружаемых файлов. + */ + public function get filesTotal():int { + return _filesTotal; + } + + /** + * Количество полностью загруженных файлов. + */ + public function get filesLoaded():int { + return _filesLoaded; + } + + /** + * Общий прогресс загрузки, выраженный числом в интервале [0, 1]. + */ + public function get totalProgress():Number { + return _totalProgress; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _filesTotal, _filesLoaded, _totalProgress, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строкове представление объекта. + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent filesTotal=" + _filesTotal + ", filesLoaded=" + _filesLoaded + ", totalProgress=" + _totalProgress.toFixed(2) + "]"; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D7/7.3/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..1bfecca --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,80 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class FillMaterial extends Material { + + public var color:int; + public var alpha:Number; + public var lineThickness:Number; + public var lineColor:int; + + public function FillMaterial(color:int = 0x7F7F7F, alpha:Number = 1, lineThickness:Number = -1, lineColor:int = 0xFFFFFF) { + this.color = color; + this.alpha = alpha; + this.lineThickness = lineThickness; + this.lineColor = lineColor; + } + + override alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var next:Face; + // Отрисовка + if (lineThickness >= 0) canvas.gfx.lineStyle(lineThickness, lineColor); + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + if (alpha > 0) canvas.gfx.beginFill(color, alpha); + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + numVertices++; + } + if (alpha <= 0) { + vertex = face.wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + override alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var next:Face; + // Отрисовка + if (lineThickness >= 0) canvas.gfx.lineStyle(lineThickness, lineColor); + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + if (alpha > 0) canvas.gfx.beginFill(color, alpha); + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + numVertices++; + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/materials/Material.as b/Alternativa3D7/7.3/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..54510a9 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/materials/Material.as @@ -0,0 +1,30 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + + use namespace alternativa3d; + + public class Material { + + public var name:String; + + alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + clearLinks(list); + } + + alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + clearLinks(list); + } + + alternativa3d function clearLinks(list:Face):void { + while (list != null) { + var next:Face = list.processNext; + list.processNext = null; + list = next; + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D7/7.3/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..162892c --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,533 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + import flash.display.BitmapData; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + public class TextureMaterial extends Material { + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + static private const matrix:Matrix = new Matrix(); + static private const rect:Rectangle = new Rectangle(); + static private const point:Point = new Point(); + + static protected var drawVertices:Vector. = new Vector.(); + static protected var drawUVTs:Vector. = new Vector.(); + static protected var drawIndices:Vector. = new Vector.(); + + public var diffuseMapURL:String; + public var opacityMapURL:String; + + public var texture:BitmapData; + + public var repeat:Boolean = false; + public var smooth:Boolean = true; + + public var mipMapping:int = 0; + + public var resolution:Number = 1; + + public var threshold:Number = 0.01; + + public var correctUV:Boolean = false; + + alternativa3d var mipMap:Vector.; + alternativa3d var numMaps:int = 0; + + public function TextureMaterial(texture:BitmapData = null, repeat:Boolean = false, smooth:Boolean = true) { + this.texture = texture; + this.repeat = repeat; + this.smooth = smooth; + } + + override alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + var face:Face; + var next:Face; + var last:Face; + var wrapper:Wrapper; + var vertex:Vertex; + var a:int; + var b:int; + var c:int; + var t:Number; + var f:Number; + var j:int; + var mu:Number; + var mv:Number; + var du:Number; + var dv:Number; + var drawTexture:BitmapData; + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var vertices:Vector. = drawVertices; + var uvts:Vector. = drawUVTs; + var indices:Vector. = drawIndices; + var numVertices:int; + var verticesLength:int; + var uvtsLength:int; + var indicesLength:int; + var numDraws:int = camera.numDraws; + var numPolygons:int = camera.numPolygons; + var numTriangles:int = camera.numTriangles; + // Если нет текстуры, нужно просто расцепить список + if (texture == null) { + clearLinks(list); + return; + } + // Мипмаппинг + if (mipMapping < 2) { + numDraws++; + numVertices = 0; + verticesLength = 0; + uvtsLength = 0; + indicesLength = 0; + for (face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + wrapper = face.wrapper; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + a = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + a = vertex.index; + } + wrapper = wrapper.next; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + b = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + b = vertex.index; + } + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + c = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + c = vertex.index; + } + drawIndices[indicesLength] = a; + indicesLength++; + drawIndices[indicesLength] = b; + indicesLength++; + drawIndices[indicesLength] = c; + indicesLength++; + b = c; + numTriangles++; + } + numPolygons++; + } + // Подрезка + vertices.length = verticesLength; + uvts.length = uvtsLength; + indices.length = indicesLength; + // Отрисовка + if (mipMapping == 0) { + // Без мипмаппинга + drawTexture = texture; + } else { + // Мипмаппинг по удалённости объекта от камеры + f = camera.focalLength*resolution; + var level:int = (distance >= f) ? (1 + Math.log(distance/f)*1.442695040888963387) : 0; + if (level >= numMaps) level = numMaps - 1; + drawTexture = mipMap[level]; + } + if (correctUV) { + du = -0.5/(drawTexture.width - 1); + dv = -0.5/(drawTexture.height - 1); + mu = 1 - du - du; + mv = 1 - dv - dv; + for (j = 0; j < uvtsLength; j++) { + uvts[j] = uvts[j]*mu + du; j++; + uvts[j] = uvts[j]*mv + dv; j++; + } + } + canvas.gfx.beginBitmapFill(drawTexture, null, repeat, smooth); + canvas.gfx.drawTriangles(vertices, indices, uvts, "none"); + } else { + // Расчёт Z-баунда + var z:Number; + var min:Number = 1e+22; + var max:Number = -1; + for (face = list; face != null; face = face.processNext) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + z = wrapper.vertex.cameraZ; + if (z < min) min = z; + if (z > max) max = z; + } + } + // Расстояние нулевого мипа + f = camera.focalLength*resolution; + // Минимальный и максимальный уровень + var minLevel:int = (min >= f) ? (1 + Math.log(min/f)*1.442695040888963387) : 0; + if (minLevel >= numMaps) minLevel = numMaps - 1; + var maxLevel:int = (max >= f) ? (1 + Math.log(max/f)*1.442695040888963387) : 0; + if (maxLevel >= numMaps) maxLevel = numMaps - 1; + // Рассечение Z-плоскостями начиная с дальних и отрисовка + z = f*Math.pow(2, maxLevel - 1); + var temporaryWrapper:Wrapper; + for (var i:int = maxLevel; i >= minLevel; i--) { + numDraws++; + numVertices = 0; + verticesLength = 0; + uvtsLength = 0; + indicesLength = 0; + var zMin:Number = z - threshold; + var zMax:Number = z + threshold; + for (face = list,list = null,last = null; face != null; face = next) { + next = face.processNext; + face.processNext = null; + wrapper = null; + if (i == minLevel) { + wrapper = face.wrapper; + } else { + var w:Wrapper = face.wrapper; + var az:Number = w.vertex.cameraZ; + w = w.next; + var bz:Number = w.vertex.cameraZ; + w = w.next; + var cz:Number = w.vertex.cameraZ; + w = w.next; + var behind:Boolean = az < zMin || bz < zMin || cz < zMin; + var infront:Boolean = az > zMax || bz > zMax || cz > zMax; + for (; w != null; w = w.next) { + var vz:Number = w.vertex.cameraZ; + if (vz < zMin) { + behind = true; + } else if (vz > zMax) { + infront = true; + } + } + if (!behind) { + wrapper = face.wrapper; + } else if (!infront) { + if (list != null) { + last.processNext = face; + } else { + list = face; + } + last = face; + } else { + var negative:Face = face.create(); + camera.lastFace.next = negative; + camera.lastFace = negative; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + var va:Vertex = w.vertex; + az = va.cameraZ; + for (w = face.wrapper; w != null; w = w.next) { + var vb:Vertex = w.vertex; + bz = vb.cameraZ; + if (az < zMin && bz > zMax || az > zMax && bz < zMin) { + t = (z - az)/(bz - az); + var v:Vertex = vb.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*t; + v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*t; + v.cameraZ = z; + v.u = va.u + (vb.u - va.u)*t; + v.v = va.v + (vb.v - va.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + wrapper = wNew; + } + wPositive = wNew; + } + if (bz <= zMax) { + wNew = w.create(); + wNew.vertex = vb; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bz >= zMin) { + wNew = w.create(); + wNew.vertex = vb; + if (wPositive != null) { + wPositive.next = wNew; + } else { + wrapper = wNew; + } + wPositive = wNew; + } + va = vb; + az = bz; + } + if (list != null) { + last.processNext = negative; + } else { + list = negative; + } + last = negative; + temporaryWrapper = wrapper; + } + } + if (wrapper != null) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + a = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + a = vertex.index; + } + wrapper = wrapper.next; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + b = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + b = vertex.index; + } + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + c = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + c = vertex.index; + } + drawIndices[indicesLength] = a; + indicesLength++; + drawIndices[indicesLength] = b; + indicesLength++; + drawIndices[indicesLength] = c; + indicesLength++; + b = c; + numTriangles++; + } + numPolygons++; + if (temporaryWrapper != null) { + //for (wrapper = temporaryWrapper; wrapper != null; wrapper.vertex = null, wrapper = wrapper.next); + wrapper = temporaryWrapper; + while (wrapper != null) { + wrapper.vertex = null; + wrapper = wrapper.next; + } + camera.lastWrapper.next = temporaryWrapper; + camera.lastWrapper = wPositive; + temporaryWrapper = null; + } + } + } + // Следующая плоскость + z *= 0.5; + // Подрезка + vertices.length = verticesLength; + uvts.length = uvtsLength; + indices.length = indicesLength; + // Отрисовка + drawTexture = mipMap[i]; + if (correctUV) { + du = -0.5/(drawTexture.width - 1); + dv = -0.5/(drawTexture.height - 1); + mu = 1 - du - du; + mv = 1 - dv - dv; + for (j = 0; j < uvtsLength; j++) { + uvts[j] = uvts[j]*mu + du; j++; + uvts[j] = uvts[j]*mv + dv; j++; + } + } + canvas.gfx.beginBitmapFill(drawTexture, null, repeat, smooth); + canvas.gfx.drawTriangles(vertices, indices, uvts, "none"); + } + } + camera.numDraws = numDraws; + camera.numPolygons = numPolygons; + camera.numTriangles = numTriangles; + } + + override alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var face:Face; + var next:Face; + // Если нет текстуры, нужно просто расцепить список + if (texture == null) { + clearLinks(list); + return; + } + var drawTexure:BitmapData; + if (mipMapping == 0) { + // Без мипмаппинга + drawTexure = texture; + } else { + // Мипмаппинг по удалённости объекта от камеры + var f:Number = camera.focalLength*resolution; + var level:int = (distance >= f) ? (1 + Math.log(distance/f)*1.442695040888963387) : 0; + if (level >= numMaps) level = numMaps - 1; + drawTexure = mipMap[level]; + } + // Коррекция матрицы + var tw:Number = drawTexure.width; + var th:Number = drawTexure.height; + matrix.a = a/tw; + matrix.b = b/tw; + matrix.c = c/th; + matrix.d = d/th; + matrix.tx = tx; + matrix.ty = ty; + // Отрисовка + canvas.gfx.beginBitmapFill(drawTexure, matrix, repeat, smooth); + for (face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + numVertices++; + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + public function disposeMipMap():void { + if (numMaps > 0) { + for (numMaps--; numMaps > 0; numMaps--) { + (mipMap[numMaps] as BitmapData).dispose(); + } + mipMap = null; + } + } + + public function calculateMipMaps(maxLevel:int = 12):void { + if (numMaps > 0) { + for (numMaps--; numMaps > 0; numMaps--) (mipMap[numMaps] as BitmapData).dispose(); + } else { + mipMap = new Vector.(); + } + matrix.identity(); + mipMap[numMaps] = texture; + numMaps++; + filter.preserveAlpha = !texture.transparent; + var bmp:BitmapData = (texture.width*texture.height > 16777215) ? texture.clone() : new BitmapData(texture.width, texture.height, texture.transparent); + var current:BitmapData = texture; + var w:Number = rect.width = texture.width; + var h:Number = rect.height = texture.height; + while (numMaps <= maxLevel && w > 1 && h > 1 && rect.width > 1 && rect.height > 1) { + bmp.applyFilter(current, rect, point, filter); + rect.width = w >> 1; + rect.height = h >> 1; + matrix.a = rect.width/w; + matrix.d = rect.height/h; + w *= 0.5; + h *= 0.5; + current = new BitmapData(rect.width, rect.height, texture.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + mipMap[numMaps] = current; + numMaps++; + } + bmp.dispose(); + mipMap.length = numMaps; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/AnimSprite.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..c80ae52 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,46 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.Material; + + use namespace alternativa3d; + + /** + * Анимированный спрайт. + * Анимация осуществляется путём переключения изображений, + * хранящихся в списке textures + */ + public class AnimSprite extends Sprite3D { + + /** + * Список кадров изображений + */ + public var materials:Vector.; + /** + * Устанавливаемый кадр + */ + public var frame:uint = 0; + + public function AnimSprite(width:Number = 100, height:Number = 100, materials:Vector. = null) { + super(width, height); + this.materials = materials; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (materials == null) return; + material = materials[frame]; + super.draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (materials == null) return null; + material = materials[frame]; + return super.getGeometry(camera, object); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Axes.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Axes.as new file mode 100644 index 0000000..ef24dff --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Axes.as @@ -0,0 +1,116 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, иллюстрирующий систему координат + */ + public class Axes extends Object3D { + + public var axisLength:Number; + public var lineThickness:Number; + public var dotRadius:Number; + public var textSize:Number; + + public function Axes(axisLength:Number = 30, lineThickness:Number = 0, dotRadius:Number = 2, textSize:Number = 10):void { + this.axisLength = axisLength; + this.lineThickness = lineThickness; + this.dotRadius = dotRadius; + this.textSize = textSize; + boundMinX = -dotRadius; + boundMinY = -dotRadius; + boundMinZ = -dotRadius; + boundMaxX = dotRadius; + boundMaxY = dotRadius; + boundMaxZ = dotRadius; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + /* + var p:Vector. = Vector.([0, 0, 0, axisLength, 0, 0, 0, axisLength, 0, 0, 0, axisLength]); + var d:Vector. = new Vector.(8); + object.transformation.transformVectors(p, p); + + // Центр за камерой + if (p[2] < camera.nearClipping) return; + + Utils3D.projectVectors(camera.projectionMatrix, p, d, new Vector.()); + var size:Number = camera.viewSize/p[2]; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + var gfx:Graphics = canvas.gfx; + var text:TextField; + + // Ось X + if (p[5] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0xFF0000); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[2], d[3]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[2]; + text.y = d[3]; + text.text = "X"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0xFF0000)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Y + if (p[8] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x00FF00); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[4], d[5]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[4]; + text.y = d[5]; + text.text = "Y"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x00FF00)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Z + if (p[11] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x0000FF); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[6], d[7]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[6]; + text.y = d[7]; + text.text = "Z"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x0000FF)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Начало координат + gfx.lineStyle(); + gfx.beginFill(0xFFFFFF); + gfx.drawCircle(d[0], d[1], dotRadius*size); + */ + //debugDrawBoundRaduis(camera, object, canvas); + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Bone.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Bone.as new file mode 100644 index 0000000..6981b3d --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Bone.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + + use namespace alternativa3d; + + public class Bone extends Joint { + + public var length:Number; + public var distance:Number; + + // Длина кости + private var lx:Number; + private var ly:Number; + private var lz:Number; + private var ldot:Number; + + public function Bone(length:Number, distance:Number) { + this.length = length; + this.distance = distance; + } + + override alternativa3d function calculateBindingMatrix(parent:Object3D):void { + super.calculateBindingMatrix(parent); + lx = mc*length; + ly = mg*length; + lz = mk*length; + ldot = lx*lx + ly*ly + lz*lz; + } + + public function bindVerticesByDistance(skin:Skin):void { + for (var vertex:Vertex = skin.vertexList; vertex != null; vertex = vertex.next) bindVertexByDistance(vertex); + } + + public function bindVertexByDistance(vertex:Vertex):void { + var vx:Number = vertex.x - md; + var vy:Number = vertex.y - mh; + var vz:Number = vertex.z - ml; + var dot:Number = vx*lx + vy*ly + vz*lz; + if (dot > 0) { + if (ldot > dot) { + dot /= ldot; + vx = vertex.x - md - dot*lx; + vy = vertex.y - mh - dot*ly; + vz = vertex.z - ml - dot*lz; + } else { + vx -= lx; + vy -= ly; + vz -= lz; + } + } + bindVertex(vertex, 1 - Math.sqrt(vx*vx + vy*vy + vz*vz)/distance); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Joint.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Joint.as new file mode 100644 index 0000000..58a3ac0 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Joint.as @@ -0,0 +1,215 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Geometry; + + use namespace alternativa3d; + + public class Joint extends Object3D { + + private var joints:Vector. = new Vector.(); + private var _numJoints:uint = 0; + + alternativa3d var vertexBindingList:VertexBinding; + + // Матрица привязки + private var ba:Number; + private var bb:Number; + private var bc:Number; + private var bd:Number; + private var be:Number; + private var bf:Number; + private var bg:Number; + private var bh:Number; + private var bi:Number; + private var bj:Number; + private var bk:Number; + private var bl:Number; + + alternativa3d function calculateBindingMatrix(parent:Object3D):void { + composeAndAppend(parent); + calculateInverseMatrix(this); + ba = ima; + bb = imb; + bc = imc; + bd = imd; + be = ime; + bf = imf; + bg = img; + bh = imh; + bi = imi; + bj = imj; + bk = imk; + bl = iml; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.calculateBindingMatrix(this); + } + } + + /** + * @private + * Задает матрицу перевода из кости в скин. + */ + alternativa3d function setBindingMatrix(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + ba = a; + bb = b; + bc = c; + bd = d; + be = e; + bf = f; + bg = g; + bh = h; + bi = i; + bj = j; + bk = k; + bl = l; + } + + alternativa3d function addWeights():void { + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) vertexBinding.vertex.offset += vertexBinding.weight; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.addWeights(); + } + } + + alternativa3d function normalizeWeights():void { + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) vertexBinding.weight /= vertexBinding.vertex.offset; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.normalizeWeights(); + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + composeAndAppend(object); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.draw(camera, this, parentCanvas); + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BONES) drawBone(camera, canvas, 0xFFFFFF); + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + composeAndAppend(object); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.getGeometry(camera, this); + } + return null; + } + + private function drawBone(camera:Camera3D, canvas:Canvas, color:int):void { + for (var i:int = 0; i < _numJoints; i++) { + var x1:Number = 0; + var y1:Number = 0; + var z1:Number = 0; + var bone:Joint = joints[i]; + var x2:Number = bone.x; + var y2:Number = bone.y; + var z2:Number = bone.z; + var cx1:Number = ma*x1 + mb*y1 + mc*z1 + md; + var cy1:Number = me*x1 + mf*y1 + mg*z1 + mh; + var cz1:Number = mi*x1 + mj*y1 + mk*z1 + ml; + var cx2:Number = ma*x2 + mb*y2 + mc*z2 + md; + var cy2:Number = me*x2 + mf*y2 + mg*z2 + mh; + var cz2:Number = mi*x2 + mj*y2 + mk*z2 + ml; + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var t1:Number = 1/cz1; + var t2:Number = 1/cz2; + var px1:Number = cx1*viewSizeX*t1; + var py1:Number = cy1*viewSizeY*t1; + var px2:Number = cx2*viewSizeX*t2; + var py2:Number = cy2*viewSizeY*t2; + canvas.gfx.lineStyle(0, color); + canvas.gfx.moveTo(px1, py1); + canvas.gfx.lineTo(px2, py2); + } + } + + alternativa3d function calculateVertices():void { + // Матрица изменений координат в соответствии с изменением положения кости относительно слепка + ima = ma*ba + mb*be + mc*bi; + imb = ma*bb + mb*bf + mc*bj; + imc = ma*bc + mb*bg + mc*bk; + imd = ma*bd + mb*bh + mc*bl + md; + ime = me*ba + mf*be + mg*bi; + imf = me*bb + mf*bf + mg*bj; + img = me*bc + mf*bg + mg*bk; + imh = me*bd + mf*bh + mg*bl + mh; + imi = mi*ba + mj*be + mk*bi; + imj = mi*bb + mj*bf + mk*bj; + imk = mi*bc + mj*bg + mk*bk; + iml = mi*bd + mj*bh + mk*bl + ml; + // Расчёт координат + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) { + var vertex:Vertex = vertexBinding.vertex; + vertex.cameraX += (ima*vertex.x + imb*vertex.y + imc*vertex.z + imd)*vertexBinding.weight; + vertex.cameraY += (ime*vertex.x + imf*vertex.y + img*vertex.z + imh)*vertexBinding.weight; + vertex.cameraZ += (imi*vertex.x + imj*vertex.y + imk*vertex.z + iml)*vertexBinding.weight; + } + } + + public function bindVertex(vertex:Vertex, weight:Number = 1):void { + if (weight > 0) { + var vertexBinding:VertexBinding = new VertexBinding(); + vertexBinding.next = vertexBindingList; + vertexBindingList = vertexBinding; + vertexBinding.vertex = vertex; + vertexBinding.weight = weight; + } + } + + public function addJoint(joint:Joint):void { + joints[_numJoints] = joint; + _numJoints++; + } + + public function removeJoint(joint:Joint):void { + var i:int = joints.indexOf(joint); + if (i < 0) throw new ArgumentError("Joint not found"); + _numJoints--; + var j:int = i + 1; + while (i < _numJoints) { + joints[i] = joints[j]; + i++; + j++; + } + joints.length = _numJoints; + } + + public function get numJoints():uint { + return _numJoints; + } + + public function getJointAt(index:uint):Joint { + return joints[index]; + } + + alternativa3d override function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + composeAndAppend(transformation); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.updateBounds(bounds, this); + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/LOD.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..a0a2092 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,71 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Объект, имеющий набор объектов с разной детализацией. + * При отрисовке, он выбирает в зависимости от расстояния от камеры + * объект с нужной детализацией и отрисовывает его вместо себя. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class LOD extends Object3D { + + /** + * Объекты с разной детализацией + */ + public var lodObjects:Vector.; + /** + * Расстояния до камеры соответствующие объектам с разной детализацией + */ + public var lodDistances:Vector.; + + /** + * @private + */ + private function getLODObject(object:Object3D):Object3D { + //var cameraDistance:Number = object.cameraMatrix.position.length; + var cameraDistance:Number = object.ml; + // Поиск ближайшего лода + var min:Number = Infinity; + var length:uint = lodObjects.length; + var lod:Object3D; + for (var i:int = 0; i < length; i++) { + var d:Number = Math.abs(cameraDistance - lodDistances[i]); + if (d < min) { + min = d; + lod = lodObjects[i]; + } + } + return lod; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return getLODObject(object).getGeometry(camera, object); + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + var length:uint = lodObjects.length; + for (var i:int = 0; i < length; i++) { + (lodObjects[i] as Object3D).updateBounds(bounds, transformation); + } + } + + override alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + object.culling = getLODObject(object).cullingInCamera(camera, object, culling); + return object.culling; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Mesh.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..af6008c --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,3103 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.Material; + + import flash.utils.Dictionary; + + use namespace alternativa3d; + + /** + * Полигональный объект + */ + public class Mesh extends Object3D { + + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 1 - по граням + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 2; + /** + * Режим сортировки полигонов + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - построение динамического BSP при отрисовке + * 3 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + */ + public var sorting:int = 1; + /** + * Геометрическая погрешность при расчёте BSP-дерева + */ + public var threshold:Number = 0.01; + + public var faceList:Face; + public var vertexList:Vertex; + + public var faceTree:Face; + public var bspVertexList:Vertex; + + protected var transformID:int = 0; + + public function addVertex(x:Number, y:Number, z:Number, u:Number, v:Number):Vertex { + var vertex:Vertex = new Vertex(); + vertex.x = x; + vertex.y = y; + vertex.z = z; + vertex.u = u; + vertex.v = v; + vertex.next = vertexList; + vertexList = vertex; + return vertex; + } + + public function removeVertex(vertex:Vertex):void { + if (vertexList == vertex) vertexList = vertex.next; + for (var v:Vertex = vertexList; v != null; v = v.next) { + if (v.next == vertex) { + v.next = v.next.next; + return; + } + } + } + + public function addFace(vertices:Vector., material:Material = null):Face { + var face:Face = new Face(); + face.next = faceList; + faceList = face; + face.material = material; + var wrapper:Wrapper = new Wrapper(); + face.wrapper = wrapper; + wrapper.vertex = vertices[0]; + var length:int = vertices.length; + for (var i:int = 1; i < length; i++) { + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = vertices[i]; + } + return face; + } + + public function addTriFace(v1:Vertex, v2:Vertex, v3:Vertex, material:Material = null):Face { + var face:Face = new Face(); + face.next = faceList; + faceList = face; + face.material = material; + var wrapper:Wrapper = new Wrapper(); + face.wrapper = wrapper; + wrapper.vertex = v1; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = v2; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = v3; + return face; + } + + public function addQuadFace(v1:Vertex, v2:Vertex, v3:Vertex, v4:Vertex, material:Material = null):Face { + var face:Face = new Face(); + face.next = faceList; + faceList = face; + face.material = material; + var wrapper:Wrapper = new Wrapper(); + face.wrapper = wrapper; + wrapper.vertex = v1; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = v2; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = v3; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + wrapper.vertex = v4; + return face; + } + + public function addGeometryByIndices(vertices:Vector., uvs:Vector., indices:Vector., material:Material = null):void { + var i:int, j:int, k:int; + var length:int = vertices.length/3; + var verts:Vector. = new Vector.(length); + var vertex:Vertex; + for (i = 0,j = 0,k = 0; i < length; i++) { + vertex = new Vertex(); + vertex.x = vertices[j]; + j++; + vertex.y = vertices[j]; + j++; + vertex.z = vertices[j]; + j++; + vertex.u = uvs[k]; + k++; + vertex.v = uvs[k]; + k++; + verts[i] = vertex; + vertex.next = vertexList; + vertexList = vertex; + } + length = indices.length/3; + var face:Face; + for (i = 0,j = 0; i < length; i++) { + face = new Face(); + face.next = faceList; + faceList = face; + face.material = material; + var wrapper:Wrapper = new Wrapper(); + face.wrapper = wrapper; + k = indices[j]; + wrapper.vertex = verts[k]; + j++; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + k = indices[j]; + wrapper.vertex = verts[k]; + j++; + wrapper.next = new Wrapper(); + wrapper = wrapper.next; + k = indices[j]; + wrapper.vertex = verts[k]; + j++; + } + } + + public function addGeometryByVectors(vertices:Vector., faces:Vector.):void { + var i:int; + var length:int = vertices.length; + var vertex:Vertex; + for (i = 0; i < length; i++) { + vertex = vertices[i]; + vertex.next = vertexList; + vertexList = vertex; + } + length = faces.length; + var face:Face; + for (i = 0; i < length; i++) { + face = faces[i]; + face.next = faceList; + faceList = face; + } + } + + public function setMaterialToAllFaces(material:Material = null):void { + for (var face:Face = faceList; face != null; face = face.next) { + face.material = material; + } + if (faceTree != null) { + setMaterialToTree(faceTree, material); + } + } + + private function setMaterialToTree(tree:Face, material:Material):void { + for (var face:Face = tree; face != null; face = face.next) { + face.material = material; + } + if (tree.negative != null) { + setMaterialToTree(tree.negative, material); + } + if (tree.positive != null) { + setMaterialToTree(tree.positive, material); + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + var list:Face; + var vertex:Vertex; + // Сброс итератора трансформаций + if (transformID > 500000000) { + transformID = 0; + //for (vertex = vertexList; vertex != null; vertex.transformID = 0, vertex = vertex.next); + vertex = vertexList; + while (vertex != null) { + vertex.transformID = 0; + vertex = vertex.next; + } + //for (vertex = bspVertexList; vertex != null; vertex.transformID = 0, vertex = vertex.next); + vertex = bspVertexList; + while (vertex != null) { + vertex.transformID = 0; + vertex = vertex.next; + } + } + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Отсечение по нормалям + if (sorting == 3) { + if (faceTree == null) return; + list = collectNode(faceTree); + } else { + if (faceList == null) return; + list = backfaceCull(faceList); + } + if (list == null) return; + // Трансформация в камеру + transformID++; + transform(list, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + list = cull(list, culling, camera); + } else { + list = clip(list, culling, camera); + } + if (list == null) return; + } + // Сортировка + if (list.processNext != null) { + if (sorting == 1) { + list = sortByAverageZ(list); + } else if (sorting == 2) { + list = sortByDynamicBSP(list, camera, threshold); + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + for (var face:Face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, object.ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + + protected function transform(list:Face, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):void { + // Трансформация вершин граней + for (var face:Face = list; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = ma*x + mb*y + mc*z + md; + vertex.cameraY = me*x + mf*y + mg*z + mh; + vertex.cameraZ = mi*x + mj*y + mk*z + ml; + vertex.transformID = transformID; + vertex.drawID = 0; + } + } + } + } + + private function collectNode(tree:Face, readyList:Face = null):Face { + if (tree.normalX*imd + tree.normalY*imh + tree.normalZ*iml > tree.offset) { + if (tree.positive != null) readyList = collectNode(tree.positive, readyList); + for (var face:Face = tree; face != null; face = face.next) { + face.processNext = readyList; + readyList = face; + } + if (tree.negative != null) readyList = collectNode(tree.negative, readyList); + } else { + if (tree.negative != null) readyList = collectNode(tree.negative, readyList); + if (tree.positive != null) readyList = collectNode(tree.positive, readyList); + } + return readyList; + } + + private function backfaceCull(list:Face):Face { + var first:Face; + var last:Face; + for (var face:Face = list; face != null; face = face.next) { + if (face.normalX*imd + face.normalY*imh + face.normalZ*iml > face.offset) { + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function cull(list:Face, culling:int, camera:Camera3D):Face { + var first:Face; + var last:Face; + var next:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + if (c1) { + if (az <= near || bz <= near || cz <= near) { + face.processNext = null; + continue; + } + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) { + face.processNext = null; + continue; + } + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function clip(list:Face, culling:int, camera:Camera3D):Face { + var first:Face; + var last:Face; + var next:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var faceCulling:int; + var t:Number; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + faceCulling = 0; + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + face.processNext = null; + var newFace:Face = face.create(); + newFace.material = face.material; + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.wrapper = wFirst; + face = newFace; + } + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + protected function sortByDynamicBSP(list:Face, camera:Camera3D, threshold:Number, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var splitter:Face = list; + list = splitter.processNext; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var normalX:Number = 0; + var normalY:Number = 0; + var normalZ:Number = 1; + var offset:Number = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + splitterLast.processNext = face; + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + if (positiveFirst != null) { + positiveLast.processNext = null; + if (positiveFirst.processNext != null) { + result = sortByDynamicBSP(positiveFirst, camera, threshold, result); + } else { + positiveFirst.processNext = result; + result = positiveFirst; + } + } + splitterLast.processNext = result; + result = splitter; + if (negativeFirst != null) { + negativeLast.processNext = null; + if (negativeFirst.processNext != null) { + result = sortByDynamicBSP(negativeFirst, camera, threshold, result); + } else { + negativeFirst.processNext = result; + result = negativeFirst; + } + } + return result; + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var vertex:Vertex; + // Сброс итератора трансформаций + if (transformID > 500000000) { + transformID = 0; + //for (vertex = vertexList; vertex != null; vertex.transformID = 0, vertex = vertex.next); + vertex = vertexList; + while (vertex != null) { + vertex.transformID = 0; + vertex = vertex.next; + } + //for (vertex = bspVertexList; vertex != null; vertex.transformID = 0, vertex = vertex.next); + vertex = bspVertexList; + while (vertex != null) { + vertex.transformID = 0; + vertex = vertex.next; + } + } + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return null; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Инкркмент итератора трансформаций + transformID++; + // Получение клона видимой геометрии + var struct:Face; + if (sorting == 3) { + if (faceTree == null) return null; + struct = calculateFaces(faceTree, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } else { + if (faceList == null) return null; + struct = calculateFaces(faceList, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } + // Зачистка после ремапа + for (vertex = (sorting == 3) ? bspVertexList : vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + // Создание геометрии + if (struct != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = struct; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.ima = ima; + geometry.imb = imb; + geometry.imc = imc; + geometry.imd = imd; + geometry.ime = ime; + geometry.imf = imf; + geometry.img = img; + geometry.imh = imh; + geometry.imi = imi; + geometry.imj = imj; + geometry.imk = imk; + geometry.iml = iml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + protected function calculateFaces(struct:Face, culling:int, camera:Camera3D, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):Face { + var first:Face; + var last:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var t:Number; + // Если не BSP или нода видна + if (sorting != 3 || imd*struct.normalX + imh*struct.normalY + iml*struct.normalZ > struct.offset) { + // Перебор оригинальных граней + for (var face:Face = struct; face != null; face = face.next) { + // Отсечение по нормали + if (sorting != 3 && imd*face.normalX + imh*face.normalY + iml*face.normalZ <= face.offset) continue; + // Трансформация + for (w = face.wrapper; w != null; w = w.next) { + v = w.vertex; + if (v.transformID != transformID) { + ax = v.x; + ay = v.y; + az = v.z; + v.cameraX = ma*ax + mb*ay + mc*az + md; + v.cameraY = me*ax + mf*ay + mg*az + mh; + v.cameraZ = mi*ax + mj*ay + mk*az + ml; + v.transformID = transformID; + } + } + var faceCulling:int = 0; + // Отсечение по пирамиде видимости + if (culling > 0) { + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + // Куллинг + if (clipping == 1) { + if (c1) { + if (az <= near || bz <= near || cz <= near) continue; + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) continue; + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) continue; + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + // Клиппинг + } else { + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) continue; + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) continue; + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) continue; + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) continue; + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) continue; + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) continue; + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + } + } + } + var newFace:Face = face.create(); + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.material = face.material; + var newVertex:Vertex; + if (faceCulling > 0) { + for (w = wFirst; w != null; w = w.next) { + v = w.vertex; + if (v.value != null) { + w.vertex = v.value; + } else if (v.transformID > 0) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + w.vertex = newVertex; + } + } + } else { + wFirst = null; + for (w = face.wrapper; w != null; w = w.next) { + wNew = w.create(); + v = w.vertex; + if (v.value == null) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + } + wNew.vertex = v.value; + if (wFirst != null) { + wLast.next = wNew; + } else { + wFirst = wNew; + } + wLast = wNew; + } + } + newFace.wrapper = wFirst; + if (first != null) { + last.processNext = newFace; + } else { + first = newFace; + } + last = newFace; + } + } + // Если BSP + if (sorting == 3) { + var negative:Face = (struct.negative != null) ? calculateFaces(struct.negative, culling, camera, ma, mb, mc, md, me, mf, mg, mh, mi, mj, mk, ml) : null; + var positive:Face = (struct.positive != null) ? calculateFaces(struct.positive, culling, camera, ma, mb, mc, md, me, mf, mg, mh, mi, mj, mk, ml) : null; + // Если нода видна или есть видимые дочерние ноды + if (first != null || negative != null && positive != null) { + if (first == null) { + // Создание пустой ноды + first = struct.create(); + camera.lastFace.next = first; + camera.lastFace = first; + } + // Расчёт нормали + w = struct.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + if (a.transformID != transformID) { + ax = a.x; + ay = a.y; + az = a.z; + a.cameraX = ma*ax + mb*ay + mc*az + md; + a.cameraY = me*ax + mf*ay + mg*az + mh; + a.cameraZ = mi*ax + mj*ay + mk*az + ml; + a.transformID = transformID; + } + if (b.transformID != transformID) { + ax = b.x; + ay = b.y; + az = b.z; + b.cameraX = ma*ax + mb*ay + mc*az + md; + b.cameraY = me*ax + mf*ay + mg*az + mh; + b.cameraZ = mi*ax + mj*ay + mk*az + ml; + b.transformID = transformID; + } + if (c.transformID != transformID) { + ax = c.x; + ay = c.y; + az = c.z; + c.cameraX = ma*ax + mb*ay + mc*az + md; + c.cameraY = me*ax + mf*ay + mg*az + mh; + c.cameraZ = mi*ax + mj*ay + mk*az + ml; + c.transformID = transformID; + } + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var acx:Number = c.cameraX - ax; + var acy:Number = c.cameraY - ay; + var acz:Number = c.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > 0) { + nl = 1/Math.sqrt(length); + nx *= nl; + ny *= nl; + nz *= nl; + } + first.normalX = nx; + first.normalY = ny; + first.normalZ = nz; + first.offset = ax*nx + ay*ny + az*nz; + first.negative = negative; + first.positive = positive; + } else { + first = (negative != null) ? negative : positive; + } + } + return first; + } + + /** + * Расчёт нормалей + * @param normalize Флаг нормализации + */ + public function calculateNormals(normalize:Boolean = false):void { + for (var face:Face = faceList; face != null; face = face.next) { + var w:Wrapper = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + if (normalize) { + var length:Number = nx*nx + ny*ny + nz*nz; + if (length > 0.001) { + length = 1/Math.sqrt(length); + nx *= length; + ny *= length; + nz *= length; + } + } + face.normalX = nx; + face.normalY = ny; + face.normalZ = nz; + face.offset = a.x*nx + a.y*ny + a.z*nz; + } + } + + public function optimizeForDynamicBSP(iterations:int = 1):void { + var list:Face = faceList; + var last:Face; + for (var i:int = 0; i < iterations; i++) { + var prev:Face = null; + for (var face:Face = list; face != null; face = face.next) { + var normalX:Number = face.normalX; + var normalY:Number = face.normalY; + var normalZ:Number = face.normalZ; + var offset:Number = face.offset; + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var splits:int = 0; + for (var f:Face = list; f != null; f = f.next) { + if (f != face) { + var w:Wrapper = f.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + var v:Vertex = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + if (infront) break; + } else if (vo > offsetMax) { + infront = true; + if (behind) break; + } + } + if (infront && behind) { + splits++; + if (splits > i) break; + } + } + } + if (f == null) { + if (prev != null) { + prev.next = face.next; + } else { + list = face.next; + } + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + } else { + prev = face; + } + } + if (list == null) break; + } + if (last != null) { + last.next = list; + } + } + + /** + * Расчёт локального BSP-дерева + * @param splitAnalysis Флаг сплит-анализа. + * Если он включен, дерево построится с наименьшим количеством распилов, но построение будет медленнее + */ + public function calculateBSP(splitAnalysis:Boolean = false):void { + var first:Face; + var last:Face; + var face:Face; + var wrapper:Wrapper; + for (face = faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.material = face.material; + var lastWrapper:Wrapper = null; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + var vertex:Vertex = wrapper.vertex; + if (vertex.value == null) { + var newVertex:Vertex = new Vertex(); + newVertex.next = bspVertexList; + bspVertexList = newVertex; + newVertex.x = vertex.x; + newVertex.y = vertex.y; + newVertex.z = vertex.z; + newVertex.u = vertex.u; + newVertex.v = vertex.v; + vertex.value = newVertex; + } + newWrapper.vertex = vertex.value; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + newFace.calculateBestSequenceAndNormal(); + if (first != null) { + last.next = newFace; + } else { + first = newFace; + } + last = newFace; + } + // Зануление мапы + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + wrapper.vertex.value = null; + } + } + // Построение дерева + faceTree = (first != null) ? ((first.next != null) ? createNode(first, splitAnalysis) : first) : null; + } + + private function createNode(list:Face, splitAnalysis:Boolean):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var behind:Boolean; + var infront:Boolean; + var ao:Number; + var bo:Number; + var co:Number; + var vo:Number; + var normalX:Number; + var normalY:Number; + var normalZ:Number; + var offset:Number; + var offsetMin:Number; + var offsetMax:Number; + var splitter:Face = list; + if (splitAnalysis) { + var bestSplits:int = int.MAX_VALUE; + for (var face:Face = list; face != null; face = face.next) { + normalX = face.normalX; + normalY = face.normalY; + normalZ = face.normalZ; + offset = face.offset; + offsetMin = offset - threshold; + offsetMax = offset + threshold; + var splits:int = 0; + for (var f:Face = list; f != null; f = f.next) { + if (f != face) { + w = f.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + ao = a.x*normalX + a.y*normalY + a.z*normalZ; + bo = b.x*normalX + b.y*normalY + b.z*normalZ; + co = c.x*normalX + c.y*normalY + c.z*normalZ; + behind = ao < offsetMin || bo < offsetMin || co < offsetMin; + infront = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + vo = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + if (infront) break; + } else if (vo > offsetMax) { + infront = true; + if (behind) break; + } + } + if (infront && behind) { + splits++; + if (splits >= bestSplits) break; + } + } + } + if (splits < bestSplits) { + splitter = face; + bestSplits = splits; + if (bestSplits == 0) break; + } + } + } + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var splitterNext:Face = splitter.next; + var positiveFirst:Face; + var positiveLast:Face; + normalX = splitter.normalX; + normalY = splitter.normalY; + normalZ = splitter.normalZ; + offset = splitter.offset; + offsetMin = offset - threshold; + offsetMax = offset + threshold; + while (list != null) { + if (list != splitter) { + var next:Face = list.next; + w = list.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + ao = a.x*normalX + a.y*normalY + a.z*normalZ; + bo = b.x*normalX + b.y*normalY + b.z*normalZ; + co = c.x*normalX + c.y*normalY + c.z*normalZ; + behind = ao < offsetMin || bo < offsetMin || co < offsetMin; + infront = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + vo = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + if (list.normalX*normalX + list.normalY*normalY + list.normalZ*normalZ > 0) { + splitterLast.next = list; + splitterLast = list; + } else { + if (negativeFirst != null) { + negativeLast.next = list; + } else { + negativeFirst = list; + } + negativeLast = list; + } + } else { + if (positiveFirst != null) { + positiveLast.next = list; + } else { + positiveFirst = list; + } + positiveLast = list; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.next = list; + } else { + negativeFirst = list; + } + negativeLast = list; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = new Face(); + var positive:Face = new Face(); + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + w = list.wrapper.next.next; + while (w.next != null) { + w = w.next; + } + a = w.vertex; + ao = a.offset; + for (w = list.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = new Vertex(); + v.next = bspVertexList; + bspVertexList = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = new Wrapper(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = new Wrapper(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = new Wrapper(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = new Wrapper(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + negative.material = list.material; + negative.calculateBestSequenceAndNormal(); + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + positive.material = list.material; + positive.calculateBestSequenceAndNormal(); + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + } + list = next; + } else { + list = splitterNext; + } + } + if (negativeFirst != null) { + negativeLast.next = null; + splitter.negative = (negativeFirst.next != null) ? createNode(negativeFirst, splitAnalysis) : negativeFirst; + } else { + splitter.negative = null; + } + if (positiveFirst != null) { + positiveLast.next = null; + splitter.positive = (positiveFirst.next != null) ? createNode(positiveFirst, splitAnalysis) : positiveFirst; + } else { + splitter.positive = null; + } + splitterLast.next = null; + return splitter; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (transformation != null) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + } else { + vertex.cameraX = vertex.x; + vertex.cameraY = vertex.y; + vertex.cameraZ = vertex.z; + } + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + /** + * Копирование свойств другого меша. Осторожно, свойства будут иметь прямые ссылки на свойства копируемого меша. + * @param source Объект копирования + */ + public function copyFrom(source:Mesh):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + colorTransform = source.colorTransform; + filters = source.filters; + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + clipping = source.clipping; + sorting = source.sorting; + + faceList = source.faceList; + vertexList = source.vertexList; + faceTree = source.faceTree; + bspVertexList = source.bspVertexList; + } + + override public function clone():Object3D { + var mesh:Mesh = new Mesh(); + mesh.cloneBaseProperties(this); + mesh.clipping = clipping; + mesh.sorting = sorting; + var vertex:Vertex; + // Клонирование вершин + var lastVertex:Vertex; + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + var newVertex:Vertex = new Vertex(); + newVertex.x = vertex.x; + newVertex.y = vertex.y; + newVertex.z = vertex.z; + newVertex.u = vertex.u; + newVertex.v = vertex.v; + vertex.value = newVertex; + if (lastVertex != null) { + lastVertex.next = newVertex; + } else { + mesh.vertexList = newVertex; + } + lastVertex = newVertex; + } + // Клонирование граней + var lastFace:Face; + for (var face:Face = faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.material = face.material; + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + // Клонирование обёрток + var lastWrapper:Wrapper = null; + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + newWrapper.vertex = wrapper.vertex.value; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + if (lastFace != null) { + lastFace.next = newFace; + } else { + mesh.faceList = newFace; + } + lastFace = newFace; + } + // Сброс после ремапа + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + return mesh; + } + + public function generateClass(className:String = "GeneratedMesh", packageName:String = "", textureName:String = null):String { + + /*var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["__AS3__.vec.Vector"] = true; + importSet["alternativa.engine3d.core.Mesh"] = true; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + + constructor += "\t\t\tnumVertices = " + numVertices +";\r"; + constructor += "\t\t\tnumFaces = " + numFaces +";\r"; + constructor += "\t\t\tvertices = Vector.(["; + var length:uint = numVertices*3; + var n:int = 0; + + var i:int; + + for (i = 0; i < length; i++) { + constructor += vertices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tindices = Vector.(["; + length = numFaces*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += indices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + + constructor += "\t\t\tuvts = Vector.(["; + length = numVertices*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvts[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + var embeds:String = ""; + if (textureName != null) { + importSet["flash.display.BitmapData"] = true; + var bmpName:String = textureName.charAt(0).toUpperCase() + textureName.substr(1); + embeds += "\t\t[Embed(source=\"" + textureName + "\")] private static const bmp" + bmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + textureName + ":BitmapData = new bmp" + bmpName + "().bitmapData;\r\r"; + constructor += "\t\t\ttexture = " + textureName + ";\r\r"; + } + + constructor += "\t\t\tclipping = " + clipping +";\r"; + constructor += "\t\t\trepeatTexture = " + (repeatTexture ? "true" : "false") +";\r\r"; + + constructor += "\t\t\tmatrix.rawData = Vector.([" + matrix.rawData + "]);\r"; + if (_boundBox != null) { + importSet["alternativa.engine3d.bounds.BoundBox"] = true; + constructor += "\t\t\t_boundBox = new BoundBox(" + _boundBox.minX + ", " + _boundBox.minY + ", " + _boundBox.minZ + ", " + _boundBox.maxX + ", " + _boundBox.maxY + ", " + _boundBox.maxZ + ");\r"; + } + + var imports:String = ""; + + var importArray:Array = new Array(); + for (var key:* in importSet) { + importArray.push(key); + } + importArray.sort(); + + var newLine:Boolean = false; + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer;*/ + return "Method is not realized"; + } + + /** + * Объединение вершин с одинаковыми координатами и uv + * @param distanceThreshold Погрешность, в пределах которой координаты считаются одинаковыми + * @param uvThreshold Погрешность, в пределах которой UV-координаты считаются одинаковыми + */ + public function weldVertices(distanceThreshold:Number = 0, uvThreshold:Number = 0):void { + // Заполнение массива вершин + transformID++; + var face:Face; + var wrapper:Wrapper; + var vertex:Vertex; + var vertices:Vector. = new Vector.(); + var verticesLength:int = 0; + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + vertices[verticesLength] = vertex; + verticesLength++; + vertex.transformID = transformID; + } + } + } + // Группировка + var stack:Vector. = new Vector.(); + + function group(begin:int, end:int, depth:int):void { + var i:int; + var j:int; + var threshold:Number; + switch (depth) { + case 0: // x + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.x; + } + threshold = distanceThreshold; + break; + case 1: // y + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.y; + } + threshold = distanceThreshold; + break; + case 2: // z + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.z; + } + threshold = distanceThreshold; + break; + case 3: // u + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.u; + } + threshold = uvThreshold; + break; + case 4: // v + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.v; + } + threshold = uvThreshold; + break; + } + // Сортировка + stack[0] = begin; + stack[1] = end - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + vertex = vertices[(r + l) >> 1]; + var median:Number = vertex.offset; + while (i <= j) { + var left:Vertex = vertices[i]; + while (left.offset > median) { + i++; + left = vertices[i]; + } + var right:Vertex = vertices[j]; + while (right.offset < median) { + j--; + right = vertices[j]; + } + if (i <= j) { + vertices[i] = right; + vertices[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Разбиение на группы дальше + i = begin; + vertex = vertices[i]; + for (j = i + 1; j < end; j++) { + var compared:Vertex = vertices[j]; + if (vertex.offset - compared.offset > threshold) { + if (depth < 4 && j - i > 1) { + group(i, j, depth + 1); + } + i = j; + vertex = vertices[i]; + } else if (depth == 4) { + compared.value = vertex; + } + } + if (depth < 4 && j - i > 1) { + group(i, j, depth + 1); + } + } + + group(0, verticesLength, 0); + // Замена вершин + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.value != null) { + wrapper.vertex = vertex.value; + } + } + } + // Создание нового списка вершин + vertexList = null; + transformID++; + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + vertex.next = vertexList; + vertexList = vertex; + vertex.transformID = transformID; + } + } + } + // Здесь может быть удаление дубликатов из меша + } + + /** + * Объединение соседних граней, находящихся в одной плоскости + * @param angleThreshold Допустимый угол в радианах между нормалями, чтобы считать, что объединяемые грани в одной плоскости + * @param uvThreshold Допустимая разница uv-координат, чтобы считать, что объединяемые грани состыковываются по UV + * @param convexThreshold Величина, уменьшающая допустимый угол между смежными рёбрами объединяемых граней + */ + public function weldFaces(angleThreshold:Number = 0, uvThreshold:Number = 0, convexThreshold:Number = 0, pairWeld:Boolean = false):void { + var i:int; + var j:int; + var key:*; + var sibling:Face; + var face:Face; + var next:Face; + var wp:Wrapper; + var sp:Wrapper; + var w:Wrapper; + var s:Wrapper; + var wn:Wrapper; + var sn:Wrapper; + var wm:Wrapper; + var sm:Wrapper; + var vertex:Vertex; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var abx:Number; + var aby:Number; + var abz:Number; + var abu:Number; + var abv:Number; + var acx:Number; + var acy:Number; + var acz:Number; + var acu:Number; + var acv:Number; + var nx:Number; + var ny:Number; + var nz:Number; + var nl:Number; + var dictionary:Dictionary; + // Последняя грань в результирующем списке + var last:Face; + // Погрешность + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + uvThreshold += digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + // Грани + var faces:Dictionary = new Dictionary(); + // Карта соответствий vertex:faces(dictionary) + var map:Dictionary = new Dictionary(); + for (face = faceList,faceList = null; face != null; face = next) { + next = face.next; + face.next = null; + // Расчёт нормали + a = face.wrapper.vertex; + b = face.wrapper.next.vertex; + c = face.wrapper.next.next.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = nx*nx + ny*ny + nz*nz; + if (nl > digitThreshold) { + nl = 1/Math.sqrt(nl); + nx *= nl; + ny *= nl; + nz *= nl; + face.normalX = nx; + face.normalY = ny; + face.normalZ = nz; + face.offset = a.x*nx + a.y*ny + a.z*nz; + faces[face] = true; + for (wn = face.wrapper; wn != null; wn = wn.next) { + vertex = wn.vertex; + dictionary = map[vertex]; + if (dictionary == null) { + dictionary = new Dictionary(); + map[vertex] = dictionary; + } + dictionary[face] = true; + } + } + } + // Остров + var island:Vector. = new Vector.(); + // Соседи текущей грани + var siblings:Dictionary = new Dictionary(); + // Грани, которые точно не входят в текущий остров + var unfit:Dictionary = new Dictionary(); + while (true) { + // Получение первой попавшейся грани + face = null; + for (key in faces) { + face = key; + delete faces[key]; + break; + } + if (face == null) break; + // Создани острова + var num:int = 0; + island[num] = face; + num++; + a = face.wrapper.vertex; + b = face.wrapper.next.vertex; + c = face.wrapper.next.next.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + abu = b.u - a.u; + abv = b.v - a.v; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + acu = c.u - a.u; + acv = c.v - a.v; + nx = face.normalX; + ny = face.normalY; + nz = face.normalZ; + // Нахождение матрицы uv-трансформации + var det:Number = -nx*acy*abz + acx*ny*abz + nx*aby*acz - abx*ny*acz - acx*aby*nz + abx*acy*nz; + var ima:Number = (-ny*acz + acy*nz)/det; + var imb:Number = (nx*acz - acx*nz)/det; + var imc:Number = (-nx*acy + acx*ny)/det; + var imd:Number = (a.x*ny*acz - nx*a.y*acz - a.x*acy*nz + acx*a.y*nz + nx*acy*a.z - acx*ny*a.z)/det; + var ime:Number = (ny*abz - aby*nz)/det; + var imf:Number = (-nx*abz + abx*nz)/det; + var img:Number = (nx*aby - abx*ny)/det; + var imh:Number = (nx*a.y*abz - a.x*ny*abz + a.x*aby*nz - abx*a.y*nz - nx*aby*a.z + abx*ny*a.z)/det; + var ma:Number = abu*ima + acu*ime; + var mb:Number = abu*imb + acu*imf; + var mc:Number = abu*imc + acu*img; + var md:Number = abu*imd + acu*imh + a.u; + var me:Number = abv*ima + acv*ime; + var mf:Number = abv*imb + acv*imf; + var mg:Number = abv*imc + acv*img; + var mh:Number = abv*imd + acv*imh + a.v; + for (key in unfit) { + delete unfit[key]; + } + for (i = 0; i < num; i++) { + face = island[i]; + for (key in siblings) { + delete siblings[key]; + } + // Сбор потенциальных соседей грани + for (w = face.wrapper; w != null; w = w.next) { + for (key in map[w.vertex]) { + if (faces[key] && !unfit[key]) { + siblings[key] = true; + } + } + } + for (key in siblings) { + sibling = key; + // Если совпадают по нормалям + if (nx*sibling.normalX + ny*sibling.normalY + nz*sibling.normalZ >= angleThreshold) { + for (s = sibling.wrapper; s != null; s = s.next) { + vertex = s.vertex; + var du:Number = ma*vertex.x + mb*vertex.y + mc*vertex.z + md - vertex.u; + var dv:Number = me*vertex.x + mf*vertex.y + mg*vertex.z + mh - vertex.v; + if (du > uvThreshold || du < -uvThreshold || dv > uvThreshold || dv < -uvThreshold) break; + } + // Если совпадают по UV + if (s == null) { + // Проверка на соседство + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // Добавление в остров + if (w != null) { + island[num] = sibling; + num++; + delete faces[sibling]; + } + } else { + unfit[sibling] = true; + } + } else { + unfit[sibling] = true; + } + } + } + // Если в острове только одна грань + if (num == 1) { + face = island[0]; + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + // Объединение острова + } else { + while (true) { + var weld:Boolean = false; + // Перебор граней острова + for (i = 0; i < num - 1; i++) { + face = island[i]; + if (face != null) { + // Попытки объединить текущую грань с остальными + for (j = 1; j < num; j++) { + sibling = island[j]; + if (sibling != null) { + // Поиск общего ребра + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // Если ребро найдено + if (w != null) { + // Расширение граней объединеия + while (true) { + wm = (wn.next != null) ? wn.next : face.wrapper; + //for (sp = sibling.wrapper; sp.next != s && sp.next != null; sp = sp.next); + sp = sibling.wrapper; + while (sp.next != s && sp.next != null) sp = sp.next; + if (wm.vertex == sp.vertex) { + wn = wm; + s = sp; + } else break; + } + while (true) { + //for (wp = face.wrapper; wp.next != w && wp.next != null; wp = wp.next); + wp = face.wrapper; + while (wp.next != w && wp.next != null) wp = wp.next; + sm = (sn.next != null) ? sn.next : sibling.wrapper; + if (wp.vertex == sm.vertex) { + w = wp; + sn = sm; + } else break; + } + // Первый перегиб + a = w.vertex; + b = sm.vertex; + c = wp.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Второй перегиб + a = s.vertex; + b = wm.vertex; + c = sp.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Объединение + weld = true; + var newFace:Face = new Face(); + newFace.material = face.material; + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + // Здесь может быть удаление промежуточных вершин из меша + wm = null; + for (; wn != w; wn = (wn.next != null) ? wn.next : face.wrapper) { + sm = new Wrapper(); + sm.vertex = wn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + for (; sn != s; sn = (sn.next != null) ? sn.next : sibling.wrapper) { + sm = new Wrapper(); + sm.vertex = sn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + island[i] = newFace; + island[j] = null; + face = newFace; + // Если, то собираться будет парами, иначе к одной прицепляется максимально (это чуть бустрее) + if (pairWeld) break; + } + } + } + } + } + if (!weld) break; + } + // Сбор объединённых граней + for (i = 0; i < num; i++) { + face = island[i]; + if (face != null) { + // Определение лучшей последовательности вершин + face.calculateBestSequenceAndNormal(); + // Добавление + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + } + } + } + } + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Occluder.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Occluder.as new file mode 100644 index 0000000..09e3feb --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Occluder.as @@ -0,0 +1,499 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + /** + * Полигональный объект-перекрытие. + * Объекты, которые он перекрывает от видимости камеры, исключаются из отрисовки. + * Сам окклюдер не отрисовывается. + * Должен быть конвексным + */ + public class Occluder extends Object3D { + + public var faceList:Face; + public var edgeList:Edge; + public var vertexList:Vertex; + + /** + * Минимальное отношение площади перекрытия окклюдером вьюпорта к площади вьюпорта (от 0 до 1) + * Если окклюдер перекрывает больше, он помещается в очередь и учитывается + * при дальнейшей отрисовке в пределах кадра, иначе игнорируется + */ + public var minSize:Number = 0; + + /** + * Копирование геометрии меша + * @param source Объект копирования + * Меш, геометрия которого копируется, обязан быть конвексным, иначе окклюдер будет некорректно работать + */ + public function copyFrom(source:Mesh):void { + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + faceList = source.faceList; + vertexList = source.vertexList; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (transformation != null) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + } else { + vertex.cameraX = vertex.x; + vertex.cameraY = vertex.y; + vertex.cameraZ = vertex.z; + } + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + /** + * Расчёт рёбер по имеющимся вершинам и граням + */ + public function calculateEdges():void { + var face:Face; + var wrapper:Wrapper; + var edge:Edge; + // Построение рёбер + edgeList = null; + for (face = faceList; face != null; face = face.next) { + // Расчёт нормали + face.calculateBestSequenceAndNormal(); + // Перебор отрезков грани + var a:Vertex; + var b:Vertex; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next,a = b) { + a = wrapper.vertex; + b = (wrapper.next != null) ? wrapper.next.vertex : face.wrapper.vertex; + // Перебор созданных рёбер + for (edge = edgeList; edge != null; edge = edge.next) { + // Если некорректная геометрия + if (edge.a == a && edge.b == b) { + trace("Incorrect face joint"); + } + // Если найдено созданное ребро с такими вершинами + if (edge.a == b && edge.b == a) break; + } + if (edge != null) { + edge.right = face; + } else { + edge = new Edge(); + edge.a = a; + edge.b = b; + edge.left = face; + edge.next = edgeList; + edgeList = edge; + } + } + } + // Проверка на валидность + for (edge = edgeList; edge != null; edge = edge.next) { + var abx:Number = edge.b.x - edge.a.x; + var aby:Number = edge.b.y - edge.a.y; + var abz:Number = edge.b.z - edge.a.z; + var crx:Number = edge.right.normalZ*edge.left.normalY - edge.right.normalY*edge.left.normalZ; + var cry:Number = edge.right.normalX*edge.left.normalZ - edge.right.normalZ*edge.left.normalX; + var crz:Number = edge.right.normalY*edge.left.normalX - edge.right.normalX*edge.left.normalY; + // Если перегиб внутрь + if (abx*crx + aby*cry + abz*crz < 0) { + trace("Geometry is non convex"); + } + // Если ребро с одной гранью + if (edge.left == null || edge.right == null) { + trace("Geometry is non whole"); + } + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (faceList == null || edgeList == null) return; + var canvas:Canvas; + var debug:int; + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Определение видимости граней + var cameraInside:Boolean = true; + for (var face:Face = faceList; face != null; face = face.next) { + if (face.normalX*imd + face.normalY*imh + face.normalZ*iml > face.offset) { + face.distance = 1; + cameraInside = false; + } else { + face.distance = 0; + } + } + if (cameraInside) return; + // Подготовка окклюдера в камере + var occluder:Vertex; + var num:int = 0; + var occludeAll:Boolean = true; + var culling:int = object.culling; + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var a:Vertex; + var b:Vertex; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var t:Number; + // Расчёт контура + for (var edge:Edge = edgeList; edge != null; edge = edge.next) { + // Если ребро в описывающем контуре + if (edge.left.distance != edge.right.distance) { + // Определение направления (против часовой) + if (edge.left.distance > 0) { + a = edge.a; + b = edge.b; + } else { + a = edge.b; + b = edge.a; + } + // Трансформация в камеру + ax = object.ma*a.x + object.mb*a.y + object.mc*a.z + object.md; + ay = object.me*a.x + object.mf*a.y + object.mg*a.z + object.mh; + az = object.mi*a.x + object.mj*a.y + object.mk*a.z + object.ml; + bx = object.ma*b.x + object.mb*b.y + object.mc*b.z + object.md; + by = object.me*b.x + object.mf*b.y + object.mg*b.z + object.mh; + bz = object.mi*b.x + object.mj*b.y + object.mk*b.z + object.ml; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + occludeAll = false; + } + // Создание новой плоскости + a = a.create(); + a.next = occluder; + num++; + occluder = a; + // Плоскость + occluder.cameraX = bz*ay - by*az; + occluder.cameraY = bx*az - bz*ax; + occluder.cameraZ = by*ax - bx*ay; + // Ребро (для перевода в систему контейнера) + occluder.x = ax; + occluder.y = ay; + occluder.z = az; + occluder.u = bx; + occluder.v = by; + occluder.offset = bz; + } + } + // Если контур не нулевой + if (occluder != null) { + // Проверка размера на экране + if (minSize > 0) { + // Проецирование рёбер контура + var projected:Vertex = Vertex.createList(num); + for (a = occluder,b = projected; a != null; a = a.next,b = b.next) { + // Проецтрование + b.x = a.x*viewSizeX/a.z; + b.y = a.y*viewSizeY/a.z; + b.u = a.u*viewSizeX/a.offset; + b.v = a.v*viewSizeY/a.offset; + // Расчёт левой нормали + b.cameraX = b.y - b.v; + b.cameraY = b.u - b.x; + b.offset = b.cameraX*b.x + b.cameraY*b.y; + } + // Клиппинг рамки вьюпорта по рёбрам контура + var frame:Vertex; + if (culling > 0) { + if (culling & 4) { + ax = -camera.viewSizeX; + ay = -camera.viewSizeY; + bx = -camera.viewSizeX; + by = camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 8) { + ax = camera.viewSizeX; + ay = camera.viewSizeY; + bx = camera.viewSizeX; + by = -camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 16) { + ax = camera.viewSizeX; + ay = -camera.viewSizeY; + bx = -camera.viewSizeX; + by = -camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 32) { + ax = -camera.viewSizeX; + ay = camera.viewSizeY; + bx = camera.viewSizeX; + by = camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + } + // Нахождение площади перекрытия + var square:Number = 0; + az = projected.x; + bz = projected.y; + a = projected; + while (a.next != null) a = a.next; + for (a.next = frame,a = projected; a != null; a = a.next) { + square += (a.u - az)*(a.y - bz) - (a.v - bz)*(a.x - az); + if (a.next == null) break; + } + // Зачистка + a.next = Vertex.collector; + Vertex.collector = projected; + // Если площадь меньше заданной + if (square/(camera.viewSizeX*camera.viewSizeY*8) < minSize) { + // Зачистка + a = occluder; + while (a.next != null) a = a.next; + a.next = Vertex.collector; + Vertex.collector = occluder; + return; + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) { + for (a = occluder; a != null; a = a.next) { + ax = a.x*viewSizeX/a.z; + ay = a.y*viewSizeY/a.z; + bx = a.u*viewSizeX/a.offset; + by = a.v*viewSizeY/a.offset; + canvas.gfx.moveTo(ax, ay); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(ax + (bx - ax)*0.8, ay + (by - ay)*0.8); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(bx, by); + } + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Добавление окклюдера в камеру + camera.occluders[camera.numOccluders] = occluder; + camera.numOccluders++; + // Если окклюдер перекрывает весь экран + } else if (occludeAll) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) { + t = 1.5; + canvas.gfx.moveTo(-viewSizeX + t, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(-viewSizeX + t, viewSizeY*0.6); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(-viewSizeX + t, viewSizeY - t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(viewSizeX*0.6, viewSizeY - t); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(viewSizeX - t, viewSizeY - t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(viewSizeX - t, -viewSizeY*0.6); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(viewSizeX - t, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(-viewSizeX*0.6, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(-viewSizeX + t, -viewSizeY + t); + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + camera.clearOccluders(); + camera.occludedAll = true; + } + } + + } +} + +import alternativa.engine3d.core.Face; +import alternativa.engine3d.core.Vertex; + +class Edge { + + public var next:Edge; + + public var a:Vertex; + public var b:Vertex; + + public var left:Face; + public var right:Face; + +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Reference.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Reference.as new file mode 100644 index 0000000..6ca5856 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Reference.as @@ -0,0 +1,45 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Объект-ссылка. + * Может ссылаться на любой трёхмерный объект, в том числе контейнер с любой вложенностью или Reference. + * При отрисовке он отрисовывает вместо себя объект, + * на который ссылается, подставляя только свою трансформацию, alpha, blendMode, colorTransform и filters. + */ + public class Reference extends Object3D { + + /** + * Объект, который подставляется при отрисовке вместо себя + */ + public var referenceObject:Object3D; + + public function Reference(referenceObject:Object3D = null) { + this.referenceObject = referenceObject; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return referenceObject.getGeometry(camera, object); + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + referenceObject.updateBounds(bounds, transformation); + } + + override alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + object.culling = referenceObject.cullingInCamera(camera, object, culling); + return object.culling; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Skin.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Skin.as new file mode 100644 index 0000000..bf4b519 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Skin.as @@ -0,0 +1,835 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class Skin extends Mesh { + + private var joints:Vector. = new Vector.(); + private var _numJoints:uint = 0; + + public function calculateBindingMatrices():void { + ma = 1; + mb = 0; + mc = 0; + md = 0; + me = 0; + mf = 1; + mg = 0; + mh = 0; + mi = 0; + mj = 0; + mk = 1; + ml = 0; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.calculateBindingMatrix(this); + } + } + + public function normalizeWeights():void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) vertex.offset = 0; + var joint:Joint; + for (var i:int = 0; i < _numJoints; i++) { + joint = joints[i]; + joint.addWeights(); + } + for (i = 0; i < _numJoints; i++) { + joint = joints[i]; + joint.normalizeWeights(); + } + } + + public function addJoint(joint:Joint):void { + joints[_numJoints++] = joint; + } + + public function removeJoint(joint:Joint):void { + var i:int = joints.indexOf(joint); + if (i < 0) throw new ArgumentError("Joint not found"); + _numJoints--; + var j:int = i + 1; + while (i < _numJoints) { + joints[i] = joints[j]; + i++; + j++; + } + joints.length = _numJoints; + } + + public function get numJoints():uint { + return _numJoints; + } + + public function getJointAt(index:uint):Joint { + return joints[index]; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + var list:Face; + var vertex:Vertex; + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return; + culling = 0; + } + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + vertex.drawID = 0; + } + // Расчёт координат вершин + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.draw(camera, object, parentCanvas); + } + // Отсечение по нормалям + var last:Face; + for (var face:Face = faceList; face != null; face = face.next) { + var a:Vertex = face.wrapper.vertex; + var b:Vertex = face.wrapper.next.vertex; + var c:Vertex = face.wrapper.next.next.vertex; + var abx:Number = b.cameraX - a.cameraX; + var aby:Number = b.cameraY - a.cameraY; + var abz:Number = b.cameraZ - a.cameraZ; + var acx:Number = c.cameraX - a.cameraX; + var acy:Number = c.cameraY - a.cameraY; + var acz:Number = c.cameraZ - a.cameraZ; + if ((acz*aby - acy*abz)*a.cameraX + (acx*abz - acz*abx)*a.cameraY + (acy*abx - acx*aby)*a.cameraZ < 0) { + if (list != null) { + last.processNext = face; + } else { + list = face; + } + last = face; + } + } + if (last != null) { + last.processNext = null; + } + if (list == null) return; + + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + list = cull(list, culling, camera); + } else { + list = clip(list, culling, camera); + } + if (list == null) return; + } + // Сортировка + if (list.processNext != null) { + if (sorting == 1) { + list = sortByAverageZ(list); + } else if (sorting == 2) { + list = sortByDynamicBSP(list, camera, threshold); + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + for (face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, object.ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + } + // Расчёт координат вершин + if (transformation == null) { + ma = 1; + mb = 0; + mc = 0; + md = 0; + me = 0; + mf = 1; + mg = 0; + mh = 0; + mi = 0; + mj = 0; + mk = 1; + ml = 0; + transformation = this; + } + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.updateBounds(bounds, transformation); + } + // Расширение баунда + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var vertex:Vertex; + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return null; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + vertex.transformID = 1; + //trace(vertex.transformID); + vertex.drawID = 0; + } + // Расчёт координат вершин + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.getGeometry(camera, object); + } + // Получение клона видимой геометрии + var struct:Face; + if (sorting == 3) { + return null; + } else { + if (faceList == null) return null; + struct = calculateFaces(faceList, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } + // Зачистка после ремапа + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + // Создание геометрии + if (struct != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = struct; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.ima = ima; + geometry.imb = imb; + geometry.imc = imc; + geometry.imd = imd; + geometry.ime = ime; + geometry.imf = imf; + geometry.img = img; + geometry.imh = imh; + geometry.imi = imi; + geometry.imj = imj; + geometry.imk = imk; + geometry.iml = iml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + override protected function calculateFaces(struct:Face, culling:int, camera:Camera3D, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):Face { + var first:Face; + var last:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var t:Number; + // Перебор оригинальных граней + for (var face:Face = struct; face != null; face = face.next) { + // Отсечение по нормали + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + var abx:Number = b.cameraX - a.cameraX; + var aby:Number = b.cameraY - a.cameraY; + var abz:Number = b.cameraZ - a.cameraZ; + var acx:Number = c.cameraX - a.cameraX; + var acy:Number = c.cameraY - a.cameraY; + var acz:Number = c.cameraZ - a.cameraZ; + if ((acz*aby - acy*abz)*a.cameraX + (acx*abz - acz*abx)*a.cameraY + (acy*abx - acx*aby)*a.cameraZ >= 0) continue; + var faceCulling:int = 0; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + // Куллинг + if (clipping == 1) { + if (c1) { + if (az <= near || bz <= near || cz <= near) continue; + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) continue; + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) continue; + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + // Клиппинг + } else { + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) continue; + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) continue; + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) continue; + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) continue; + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) continue; + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) continue; + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + } + } + } + var newFace:Face = face.create(); + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.material = face.material; + var newVertex:Vertex; + if (faceCulling > 0) { + for (w = wFirst; w != null; w = w.next) { + v = w.vertex; + if (v.value != null) { + w.vertex = v.value; + } else if (v.transformID > 0) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + w.vertex = newVertex; + } + } + } else { + wFirst = null; + for (w = face.wrapper; w != null; w = w.next) { + wNew = w.create(); + v = w.vertex; + if (v.value == null) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + } + wNew.vertex = v.value; + if (wFirst != null) { + wLast.next = wNew; + } else { + wFirst = wNew; + } + wLast = wNew; + } + } + newFace.wrapper = wFirst; + if (first != null) { + last.processNext = newFace; + } else { + first = newFace; + } + last = newFace; + } + return first; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/Sprite3D.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..2bfc7c8 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,547 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.Material; + + use namespace alternativa3d; + + /** + * Плоский, всегда развёрнутый к камере трёхмерный объект + */ + public class Sprite3D extends Object3D { + + /** + * Материал + */ + public var material:Material; + /** + * Режим сортировки на случай конфликта + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - построение динамического BSP при отрисовке + * 3 - проход по предрасчитанному BSP + */ + public var sorting:int = 0; + /** + * X точки привязки + */ + public var originX:Number = 0.5; + /** + * Y точки привязки + */ + public var originY:Number = 0.5; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 2; + /** + * Угол поворота в радианах в плоскости экрана + */ + public var rotation:Number = 0; + /** + * Ширина + */ + public var width:Number; + /** + * Высота + */ + public var height:Number; + /** + * Зависимость размера на экране от удалённости от камеры + */ + public var perspectiveScale:Boolean = true; + + // Текстурная матрица + static private var tma:Number; + static private var tmb:Number; + static private var tmc:Number; + static private var tmd:Number; + static private var tmtx:Number; + static private var tmty:Number; + + public function Sprite3D(width:Number = 100, height:Number = 100, material:Material = null) { + this.width = width; + this.height = height; + this.material = material; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (material == null) return; + var canvas:Canvas; + var debug:int; + var face:Face = calculateFace(camera, object); + if (face != null) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, face, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + material.drawViewAligned(camera, canvas, face, object.ml, tma, tmb, tmc, tmd, tmtx, tmty); + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (material == null) return null; + var face:Face = calculateFace(camera, object); + if (face != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = face; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.viewAligned = true; + geometry.tma = tma; + geometry.tmb = tmb; + geometry.tmc = tmc; + geometry.tmd = tmd; + geometry.tmtx = tmtx; + geometry.tmty = tmty; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + private function calculateFace(camera:Camera3D, object:Object3D):Face { + var culling:int = object.culling & 60; + var z:Number = object.ml; + var size:Number; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var cx:Number; + var cy:Number; + var dx:Number; + var dy:Number; + var first:Vertex; + var last:Vertex; + // Выход по ближнему или дальнему расстоянию отсечения + if (z <= camera.nearClipping || z >= camera.farClipping) return null; + // Проекция + var projectionX:Number = camera.viewSizeX/z; + var projectionY:Number = camera.viewSizeY/z; + var projectionZ:Number = camera.focalLength/z; + // Учёт искажения матрицы камеры под 90 градусов + var perspectiveScaleX:Number = camera.focalLength/camera.viewSizeX; + var perspectiveScaleY:Number = camera.focalLength/camera.viewSizeY; + // Нахождение среднего размера спрайта + ax = object.ma/perspectiveScaleX; + ay = object.me/perspectiveScaleY; + az = object.mi; + size = Math.sqrt(ax*ax + ay*ay + az*az); + ax = object.mb/perspectiveScaleX; + ay = object.mf/perspectiveScaleY; + az = object.mj; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = object.mc/perspectiveScaleX; + ay = object.mg/perspectiveScaleY; + az = object.mk; + size += Math.sqrt(ax*ax + ay*ay + az*az); + size /= 3; + // Учёт флага масштабирования + if (!perspectiveScale) size /= projectionZ; + // Если не задано вращение + if (rotation == 0) { + // Размеры спрайта в матрице камеры + var cameraWidth:Number = size*width*perspectiveScaleX; + var cameraHeight:Number = size*height*perspectiveScaleY; + ax = object.md - originX*cameraWidth; + ay = object.mh - originY*cameraHeight; + cx = ax + cameraWidth; + cy = ay + cameraHeight; + // Подготовка смещения матрицы отрисовки + tmtx = ax*projectionX; + tmty = ay*projectionY; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (ax > z || ay > z || cx < -z || cy < -z) return null; + if (clipping == 2) { + if (ax < -z) ax = -z; + if (ay < -z) ay = -z; + if (cx > z) cx = z; + if (cy > z) cy = z; + } + } + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax; + last.cameraY = cy; + last.cameraZ = z; + last = last.next; + last.cameraX = cx; + last.cameraY = cy; + last.cameraZ = z; + last = last.next; + last.cameraX = cx; + last.cameraY = ay; + last.cameraZ = z; + // Подготовка матрицы отрисовки + tma = size*projectionZ*width; + tmb = 0; + tmc = 0; + tmd = size*projectionZ*height; + } else { + // Расчёт векторов ширины и высоты + var sin:Number = -Math.sin(rotation)*size; + var cos:Number = Math.cos(rotation)*size; + var cameraWidthX:Number = cos*width*perspectiveScaleX; + var cameraWidthY:Number = -sin*width*perspectiveScaleY; + var cameraHeightX:Number = sin*height*perspectiveScaleX; + var cameraHeightY:Number = cos*height*perspectiveScaleY; + ax = object.md - originX*cameraWidthX - originY*cameraHeightX; + ay = object.mh - originX*cameraWidthY - originY*cameraHeightY; + bx = ax + cameraHeightX; + by = ay + cameraHeightY; + cx = ax + cameraWidthX + cameraHeightX; + cy = ay + cameraWidthY + cameraHeightY; + dx = ax + cameraWidthX; + dy = ay + cameraWidthY; + // Подготовка смещения матрицы отрисовки + tmtx = ax*projectionX; + tmty = ay*projectionY; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + if ((culling & 4) && z <= -ax && z <= -bx && z <= -cx && z <= -dx) return null; + if ((culling & 8) && z <= ax && z <= bx && z <= cx && z <= dx) return null; + if ((culling & 16) && z <= -ay && z <= -by && z <= -cy && z <= -dy) return null; + if ((culling & 32) && z <= ay && z <= by && z <= cy && z <= dy) return null; + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + } else { + if (culling & 4) { + if (z <= -ax && z <= -bx && z <= -cx && z <= -dx) { + return null; + } else if (z > -ax && z > -bx && z > -cx && z > -dx) { + culling &= 59; + } + } + if (culling & 8) { + if (z <= ax && z <= bx && z <= cx && z <= dx) { + return null; + } else if (z > ax && z > bx && z > cx && z > dx) { + culling &= 55; + } + } + if (culling & 16) { + if (z <= -ay && z <= -by && z <= -cy && z <= -dy) { + return null; + } else if (z > -ay && z > -by && z > -cy && z > -dy) { + culling &= 47; + } + } + if (culling & 32) { + if (z <= ay && z <= by && z <= cy && z <= dy) { + return null; + } else if (z > ay && z > by && z > cy && z > dy) { + culling &= 31; + } + } + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + if (culling > 0) { + var t:Number; + var a:Vertex; + var b:Vertex; + var v:Vertex; + var next:Vertex; + // Клиппинг по левой стороне + if (culling & 4) { + a = last; + ax = a.cameraX; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + bx = b.cameraX; + if (z > -bx && z <= -ax || z <= -bx && z > -ax) { + t = (ax + z)/(ax - bx); + v = b.create(); + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > -bx) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ax = bx; + } + if (first == null) return null; + } + // Клиппинг по правой стороне + if (culling & 8) { + a = last; + ax = a.cameraX; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + bx = b.cameraX; + if (z > bx && z <= ax || z <= bx && z > ax) { + t = (z - ax)/(bx - ax); + v = b.create(); + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > bx) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ax = bx; + } + if (first == null) return null; + } + // Клиппинг по верхней стороне + if (culling & 16) { + a = last; + ay = a.cameraY; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + by = b.cameraY; + if (z > -by && z <= -ay || z <= -by && z > -ay) { + t = (ay + z)/(ay - by); + v = b.create(); + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > -by) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ay = by; + } + if (first == null) return null; + } + // Клиппинг по нижней стороне + if (culling & 32) { + a = last; + ay = a.cameraY; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + by = b.cameraY; + if (z > by && z <= ay || z <= by && z > ay) { + t = (z - ay)/(by - ay); + v = b.create(); + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > by) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ay = by; + } + if (first == null) return null; + } + } + } + } else { + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + } + // Подготовка матрицы отрисовки + tma = cos*projectionZ*width; + tmb = -sin*projectionZ*width; + tmc = sin*projectionZ*height; + tmd = cos*projectionZ*height; + } + // Отправка на отложенное удаление + camera.lastVertex.next = first; + camera.lastVertex = last; + // Создание грани + var face:Face = Face.create(); + face.material = material; + camera.lastFace.next = face; + camera.lastFace = face; + var wrapper:Wrapper = Wrapper.create(); + face.wrapper = wrapper; + wrapper.vertex = first; + for (first = first.next; first != null; first = first.next) { + wrapper.next = wrapper.create(); + wrapper = wrapper.next; + wrapper.vertex = first; + } + return face; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + // Расчёт локального радиуса + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*width; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*height; + var radius:Number = Math.sqrt(w*w + h*h); + var cx:Number = 0; + var cy:Number = 0; + var cz:Number = 0; + if (transformation != null) { + // Нахождение среднего размера спрайта + var ax:Number = transformation.ma; + var ay:Number = transformation.me; + var az:Number = transformation.mi; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = transformation.mb; + ay = transformation.mf; + az = transformation.mj; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = transformation.mc; + ay = transformation.mg; + az = transformation.mk; + size += Math.sqrt(ax*ax + ay*ay + az*az); + radius *= size/3; + cx = transformation.md; + cy = transformation.mh; + cz = transformation.ml; + } + if (cx - radius < bounds.boundMinX) bounds.boundMinX = cx - radius; + if (cx + radius > bounds.boundMaxX) bounds.boundMaxX = cx + radius; + if (cy - radius < bounds.boundMinY) bounds.boundMinY = cy - radius; + if (cy + radius > bounds.boundMaxY) bounds.boundMaxY = cy + radius; + if (cz - radius < bounds.boundMinZ) bounds.boundMinZ = cz - radius; + if (cz + radius > bounds.boundMaxZ) bounds.boundMaxZ = cz + radius; + } + + public function copyFrom(source:Sprite3D):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + colorTransform = source.colorTransform; + filters = source.filters; + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + clipping = source.clipping; + sorting = source.sorting; + + material = source.material; + originX = source.originX; + originY = source.originY; + rotation = source.rotation; + perspectiveScale = source.perspectiveScale; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/objects/VertexBinding.as b/Alternativa3D7/7.3/alternativa/engine3d/objects/VertexBinding.as new file mode 100644 index 0000000..6917181 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/objects/VertexBinding.as @@ -0,0 +1,12 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.core.Vertex; + + public class VertexBinding { + + public var next:VertexBinding; + + public var vertex:Vertex; + public var weight:Number = 0; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.3/alternativa/engine3d/primitives/Box.as b/Alternativa3D7/7.3/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..c2cf9a3 --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,194 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class Box extends Mesh { + + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false) { + + var wp:int = widthSegments + 1; + var lp:int = lengthSegments + 1; + var hp:int = heightSegments + 1; + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var hh:Number = height*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + var v:int = 0; + + var vertices:Vector. = new Vector.(); + var face:Face; + + // Нижняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v++] = addVertex(x*ws - wh, y*ls - lh, -hh, (widthSegments - x)*wd, (lengthSegments - y)*ld); + } + } + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + face = addQuadFace(vertices[(x + 1)*lp + y + 1], vertices[x*lp + y + 1], vertices[x*lp + y], vertices[(x + 1)*lp + y]); + face.normalZ = 1; + face.offset = -hh; + } else { + face = addQuadFace(vertices[(x + 1)*lp + y + 1], vertices[(x + 1)*lp + y], vertices[x*lp + y], vertices[x*lp + y + 1]); + face.normalZ = -1; + face.offset = hh; + } + face.normalX = 0; + face.normalY = 0; + } + } + } + var o:uint = wp*lp; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v++] = addVertex(x*ws - wh, y*ls - lh, hh, x*wd, (lengthSegments - y)*ld); + } + } + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + face = addQuadFace(vertices[o + x*lp + y + 1], vertices[o + (x + 1)*lp + y + 1], vertices[o + (x + 1)*lp + y], vertices[o + x*lp + y]); + face.normalZ = -1; + face.offset = -hh; + } else { + face = addQuadFace(vertices[o + x*lp + y], vertices[o + (x + 1)*lp + y], vertices[o + (x + 1)*lp + y + 1], vertices[o + x*lp + y + 1]); + face.normalZ = 1; + face.offset = hh; + } + face.normalX = 0; + face.normalY = 0; + } + } + } + o += wp*lp; + + // Передняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(x*ws - wh, -lh, z*hs - hh, x*wd, (heightSegments - z)*hd); + } + } + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + x*hp + z + 1], vertices[o + (x + 1)*hp + z + 1], vertices[o + (x + 1)*hp + z], vertices[o + x*hp + z]); + face.normalY = 1; + face.offset = -lh; + } else { + face = addQuadFace(vertices[o + x*hp + z], vertices[o + (x + 1)*hp + z], vertices[o + (x + 1)*hp + z + 1], vertices[o + x*hp + z + 1]); + face.normalY = -1; + face.offset = lh; + } + face.normalX = 0; + face.normalZ = 0; + } + } + } + o += wp*hp; + + // Задняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(x*ws - wh, lh, z*hs - hh, (widthSegments - x)*wd, (heightSegments - z)*hd); + } + } + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + (x + 1)*hp + z], vertices[o + (x + 1)*hp + z + 1], vertices[o + x*hp + z + 1], vertices[o + x*hp + z]); + face.normalY = -1; + face.offset = -lh; + } else { + face = addQuadFace(vertices[o + x*hp + z], vertices[o + x*hp + z + 1], vertices[o + (x + 1)*hp + z + 1], vertices[o + (x + 1)*hp + z]); + face.normalY = 1; + face.offset = lh; + } + face.normalX = 0; + face.normalZ = 0; + } + } + } + o += wp*hp; + + // Левая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(-wh, y*ls - lh, z*hs - hh, (lengthSegments - y)*ld, (heightSegments - z)*hd); + } + } + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + (y + 1)*hp + z], vertices[o + (y + 1)*hp + z + 1], vertices[o + y*hp + z + 1], vertices[o + y*hp + z]); + face.normalX = 1; + face.offset = -wh; + } else { + face = addQuadFace(vertices[o + y*hp + z], vertices[o + y*hp + z + 1], vertices[o + (y + 1)*hp + z + 1], vertices[o + (y + 1)*hp + z]); + face.normalX = -1; + face.offset = wh; + } + face.normalY = 0; + face.normalZ = 0; + } + } + } + o += lp*hp; + + // Правая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(wh, y*ls - lh, z*hs - hh, y*ld, (heightSegments - z)*hd); + } + } + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + y*hp + z + 1], vertices[o + (y + 1)*hp + z + 1], vertices[o + (y + 1)*hp + z], vertices[o + y*hp + z]); + face.normalX = -1; + face.offset = -wh; + } else { + face = addQuadFace(vertices[o + y*hp + z], vertices[o + (y + 1)*hp + z], vertices[o + (y + 1)*hp + z + 1], vertices[o + y*hp + z + 1]); + face.normalX = 1; + face.offset = wh; + } + face.normalY = 0; + face.normalZ = 0; + } + } + } + + // Установка границ + boundMinX = -wh; + boundMinY = -lh; + boundMinZ = -hh; + boundMaxX = wh; + boundMaxY = lh; + boundMaxZ = hh; + } + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D7/7.3/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..ace371a --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,251 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class GeoSphere extends Mesh { + + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + + /* + const sections:uint = 20; + + var i:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + + var v:uint = 0; + + var f:uint = sections*segments*segments; + + addVertex(0, 0, radius, 0, 0); + // vertices[v++] = 0; + // vertices[v++] = 0; + // vertices[v++] = radius; + + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*2*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + addVertex(subrad*cos, subrad*sin, subz, 0, 0); + // vertices[v++] = subrad*cos; + // vertices[v++] = subrad*sin; + // vertices[v++] = subz; + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + addVertex(subrad*cos, subrad*sin, -subz, 0, 0); + // vertices[v++] = subrad*cos; + // vertices[v++] = subrad*sin; + // vertices[v++] = -subz; + } + + addVertex(0, 0, -radius, 0, 0); + // vertices[v++] = 0; + // vertices[v++] = 0; + // vertices[v++] = -radius; + + if (segments > 1) { + for (i = 1; i < 6; i++) { + v = interpolate(0, i, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i % 5 + 1, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i + 5, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, (i + 3) % 5 + 6, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i + 5, i % 5 + 6, segments, v); + } + for (i = 6; i < 11; i++) { + v = interpolate(11, i, segments, v); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, v); + } + } + } + + // for (i = 0; i < numVertices; i++) { + // var j:uint = i*3; + // uvts[j] = Math.atan2(vertices[j + 1], vertices[j])/(Math.PI*2); + // uvts[j] = 0.5 + (reverse ? -uvts[j] : uvts[j]); + // uvts[j + 1] = 0.5 + Math.asin(vertices[j + 2]/radius)/Math.PI; + // } + + var num:uint = 0; + for (f = 0; f <= sections - 1; f++) { + for (var row:uint = 0; row <= segments - 1; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + + if (reverse) { + indices[num++] = a; + indices[num++] = c; + indices[num++] = b; + } else { + indices[num++] = a; + indices[num++] = b; + indices[num++] = c; + } + + if (column < row) { + var d:uint = findVertices(segments, f, row, column + 1); + if (reverse) { + indices[num++] = a; + indices[num++] = d; + indices[num++] = c; + } else { + indices[num++] = a; + indices[num++] = c; + indices[num++] = d; + } + } + } + } + } + + boundMinX = boundMinY = boundMinZ = -radius; + boundMaxX = boundMaxY = boundMaxZ = radius; + } + + private function interpolate(a:uint, b:uint, num:uint, v:uint):uint { + a *= 3; + b *= 3; + var ax:Number = vertices[a]; + var ay:Number = vertices[a + 1]; + var az:Number = vertices[a + 2]; + var bx:Number = vertices[b]; + var by:Number = vertices[b + 1]; + var bz:Number = vertices[b + 2]; + var cos:Number = (ax*bx + ay*by + az*bz)/(ax*ax + ay*ay + az*az); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + vertices[v++] = (ax*st2 + bx*st1)/sin; + vertices[v++] = (ay*st2 + by*st1)/sin; + vertices[v++] = (az*st2 + bz*st1)/sin; + } + return v; + //*/ + } + + /* + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + //*/ + + } +} diff --git a/Alternativa3D7/7.3/alternativa/engine3d/primitives/Plane.as b/Alternativa3D7/7.3/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..bb34daf --- /dev/null +++ b/Alternativa3D7/7.3/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,66 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class Plane extends Mesh { + + /** + * + * @param width + * @param length + * @param widthSegments + * @param lengthSegments + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1) { + /* + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + + createEmptyGeometry(wp*lp, (widthSegments*lengthSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = 0; + + if (x < widthSegments && y < lengthSegments) { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + } + } + } + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, 0, wh, lh, 0); + */ + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/Alternativa3D.as b/Alternativa3D7/7.4/alternativa/Alternativa3D.as new file mode 100644 index 0000000..b476703 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/Alternativa3D.as @@ -0,0 +1,14 @@ +package alternativa { + + /** + * Класс содержит информацию о версии библиотеки. + * Также используется для интеграции библиотеки в среду разработки Adobe Flash. + */ + public class Alternativa3D { + + /** + * Версия библиотеки в формате: поколение.feature-версия.fix-версия + */ + public static const version:String = "7.0.0"; + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/alternativa3d.as b/Alternativa3D7/7.4/alternativa/engine3d/alternativa3d.as new file mode 100644 index 0000000..8bce40e --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/alternativa3d.as @@ -0,0 +1,3 @@ +package alternativa.engine3d { + public namespace alternativa3d = "http://alternativaplatform.com/en/alternativa3d"; +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/Animation.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/Animation.as new file mode 100644 index 0000000..c353f0c --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/Animation.as @@ -0,0 +1,165 @@ +package alternativa.engine3d.animation { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + + /** + * Анимация объекта. + */ + public class Animation { + + use namespace alternativa3d; + + protected static const WEIGHTS_X:uint = 0; + protected static const WEIGHTS_Y:uint = 1; + protected static const WEIGHTS_Z:uint = 2; + protected static const WEIGHTS_ROT_X:uint = 3; + protected static const WEIGHTS_ROT_Y:uint = 4; + protected static const WEIGHTS_ROT_Z:uint = 5; + protected static const WEIGHTS_SCALE_X:uint = 6; + protected static const WEIGHTS_SCALE_Y:uint = 7; + protected static const WEIGHTS_SCALE_Z:uint = 8; + + /** + * Анимируемый объект. + */ + public var object:Object3D = null; + + /** + * Вес анимации по отношению к другим анимациям этого параметра. + * Анимация с более высоким весом оказывает большее влияние на конечное значение параметра. + * Вес наследуется на дочерние анимации. + */ + public var weight:Number = 1.0; + + /** + * Скорость проигрывания анимации. Скорость наследуется на дочерние анимации. + */ + public var speed:Number = 1.0; + + /** + * Длина анимации, включая дочерние анимации. + * После изменения длины треков и длины дочерних анимаций необходимо вызвать updateLength(). + * @see #updateLength() + */ + public var length:Number = 0.0; + + /** + * Создает анимацию + * + * @param object анимируемый объект + * @param weight вес анимации + * @param speed скорость проигрывания анимации + */ + public function Animation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) { + this.object = object; + this.weight = weight; + this.speed = speed; + } + + /** + * Пересчитывает длину анимации. + */ + public function updateLength():void { + length = 0; + } + + /** + * Расчет кадра анимации. + * + * @param position время кадра + */ + public function sample(position:Number):void { + prepareBlending(); + blend(position, 1.0); + } + + /** + * Подготавливает объект к выполнению смешения анимаций. + * Для смешения нескольких анимаций, необходимо на каждой из них вызвать prepareBlending() и затем blend(). + * Для смешения и проигрывания нескольких анимаций может быть удобнее использовать класс AnimationController. + * + * @see #blend() + * @see AnimationController + */ + public function prepareBlending():void { + if (object != null) { + if (object.weightsSum != null) { + object.weightsSum[0] = 0; object.weightsSum[1] = 0; object.weightsSum[2] = 0; + object.weightsSum[3] = 0; object.weightsSum[4] = 0; object.weightsSum[5] = 0; + object.weightsSum[6] = 0; object.weightsSum[7] = 0; object.weightsSum[8] = 0; + } else { + object.weightsSum = new Vector.(9); + } + } + } + + /** + * Выполняет смешение анимации с другими анимациями этого параметра. + * Перед вызовом этого метода, на всех анимациях которые требуется смешать, нужно вызвать метод prepareBlending(). + * Не вызывать метод, если вес меньше нуля. + * Для смешения и проигрывания нескольких анимаций может быть удобнее использовать класс AnimationController. + * + * @param position время анимации + * @param weight вес анимации + * + * @see #prepareBlending() + * @see AnimationController + */ + public function blend(position:Number, weight:Number):void { + position = (position < 0) ? 0 : (position > length) ? length : position; + control(position, weight); + } + + /** + * Расчитывает значение интерполяции параметра для текущей анимации и заданного веса. + * @param param индекс параметра + * @param weight вес анимации + */ + protected function calculateBlendInterpolation(param:int, weight:Number):Number { + var sum:Number = object.weightsSum[param]; + sum += weight; + object.weightsSum[param] = sum; + return weight/sum; + } + + /** + * Реализация расчета кадра анимации. + * @param position время кадра анимации + * @param weight вес анимации + */ + protected function control(position:Number, weight:Number):void { + } + + /** + * Переносит анимацию на указанный объект. + */ + public function reassign(object:Object3D):void { + this.object = object; + } + + /** + * Возвращает копию анимации. Копия анимации использует общие ключевые кадры с исходной анимацией. + */ + public function clone():Animation { + var cloned:Animation = new Animation(object, weight, speed); + cloned.length = length; + return cloned; + } + + /** + * Интерполяция между двумя ненормализованными углами. + */ + protected function interpolateAngle(angle1:Number, angle2:Number, weight1:Number):Number { + const PI2:Number = 2*Math.PI; + angle1 = (angle1 > Math.PI) ? angle1%PI2 - PI2 : (angle1 <= -Math.PI) ? (angle1%PI2) + PI2 : angle1; + angle2 = (angle2 > Math.PI) ? angle2%PI2 - PI2 : (angle2 <= -Math.PI) ? (angle2%PI2) + PI2 : angle2; + var delta:Number = angle2 - angle1; + delta = (delta > Math.PI) ? delta - PI2 : (delta < -Math.PI) ? delta + PI2 : delta; + return angle1 + weight1 * delta; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationController.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationController.as new file mode 100644 index 0000000..b3afda8 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationController.as @@ -0,0 +1,176 @@ +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + /** + * Управляет проигрыванием и смешением анимаций. + */ + public class AnimationController { + + use namespace alternativa3d; + + /** + * Включение/выключение контроллера. + */ + public var enabled:Boolean = true; + + private var _animations:Object = new Object(); + + /** + * Создает экземпляр контроллера. + */ + public function AnimationController() { + } + + /** + * Проиграть анимацию сначала. + * + * @param name имя анимации для проигрывания + * @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения. + */ + public function replay(name:String, fade:Number = 0):void { + var state:AnimationState = _animations[name]; + if (state == null) { + throw new ArgumentError('Animation with name "' + name + '" not found'); + } + state.replay(fade); + } + + /** + * Продолжить проигрывание анимации с текущей позиции. + * + * @param name имя анимации для проигрывания + * @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения. + */ + public function play(name:String, fade:Number = 0):void { + var state:AnimationState = _animations[name]; + if (state == null) { + throw new ArgumentError('Animation with name "' + name + '" not found'); + } + state.play(fade); + } + + /** + * Остановить анимацию. + * + * @param name имя анимации для останова + * @param fade время в течение которого вес анимации должен уменьшиться с максимального значения до 0. + */ + public function stop(name:String, fade:Number = 0):void { + var state:AnimationState = _animations[name]; + if (state == null) { + throw new ArgumentError('Animation with name "' + name + '" not found'); + } + state.stop(fade); + } + + /** + * Проиграть все анимации сначала. + * + * @param fade время в течение которого вес каждой анимации должен увеличиться с нуля до максимального значения. + */ + public function replayAll(fade:Number = 0):void { + for each (var state:AnimationState in _animations) { + state.replay(fade); + } + } + + /** + * Продолжить проигрывание всех анимаций с текущей позиции. + * + * @param fade время в течение которого вес каждой анимации должен увеличиться с нуля до максимального значения. + */ + public function playAll(fade:Number = 0):void { + for each (var state:AnimationState in _animations) { + state.play(fade); + } + } + + /** + * Остановить все анимации. + * + * @param fade время в течение которого вес каждой анимации должен уменьшиться с максимального значения до 0. + */ + public function stopAll(fade:Number = 0):void { + for each (var state:AnimationState in _animations) { + state.stop(fade); + } + } + + /** + * Проиграть анимации за прошедшее время и выполнить их смешение. + * Для автоматического ежекадрового обновления можно использовать класс AnimationTimer. + * + * @param interval прошедшее время. + * + * @see AnimationTimer + */ + public function update(interval:Number):void { + var state:AnimationState; + for each (state in _animations) { + state.prepareBlending(); + } + for each (state in _animations) { + state.update(interval); + } + } + + /** + * Добавляет анимацию в контроллер и возвращает объект состояния проигрывания анимации. + * + * @param name имя анимации + * @param animation добавляемая анимация + * @param loop проиграть анимацию сначала после достижения конца + * @return экземляр класса AnimationState через который выполняется управление проигрыванием анимации. + * + * @see AnimationState + */ + public function addAnimation(name:String, animation:Animation, loop:Boolean = true):AnimationState { + var state:AnimationState = _animations[name]; + if (state != null) { + throw new ArgumentError('Animation with this name "' + name + '" already exist'); + } + state = new AnimationState(this, animation, name, loop); + _animations[name] = state; + return state; + } + + /** + * Убирает анимацию из контроллера. + * + * @param name имя анимации для удаления. + */ + public function removeAnimation(name:String):void { + var state:AnimationState = _animations[state]; + if (state == null) { + throw new ArgumentError('Animation with name"' + name + '" dont exists'); + } + delete _animations[name]; + } + + /** + * Возвращает объект состояния проигрывания анимации по имени. + * + * @param name имя анимации. + * + * @see AnimationState + */ + public function getAnimation(name:String):AnimationState { + return _animations[name]; + } + + /** + * Возвращает словарь со всеми анимациями. Свойство - имя анимации, значение - экземпляр класса AnimationState. + * + * @see AnimationState + */ + public function get animations():Object { + var result:Object = new Object(); + for (var name:String in _animations) { + result[name] = _animations[name]; + } + return result; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationGroup.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationGroup.as new file mode 100644 index 0000000..021fa56 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationGroup.as @@ -0,0 +1,266 @@ +package alternativa.engine3d.animation { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Skin; + + /** + * Группа анимаций. Предназначена для объединения и синхронизации анимаций. + */ + public class AnimationGroup extends Animation { + + private var _numAnimations:int = 0; + private var _animations:Vector. = new Vector.(); + + /** + * Создает группу анимаций. + * + * @param object анимируемый объект + * @param weight вес анимации + * @param speed скорость проигрывания анимации + */ + public function AnimationGroup(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) { + super(object, weight, speed); + } + + /** + * @inheritDoc + */ + override public function updateLength():void { + super.updateLength(); + for (var i:int = 0; i < _numAnimations; i++) { + var animation:Animation = _animations[i]; + animation.updateLength(); + var len:Number = animation.length; + if (len > length) { + length = len; + } + } + } + + /** + * @inheritDoc + */ + override public function prepareBlending():void { + super.prepareBlending(); + for (var i:int = 0; i < _numAnimations; i++) { + _animations[i].prepareBlending(); + } + } + + /** + * @inheritDoc + */ + override public function blend(position:Number, weight:Number):void { + super.blend(position, weight); + for (var i:int = 0; i < _numAnimations; i++) { + var animation:Animation = _animations[i]; + if (animation.weight != 0) { + animation.blend(position*animation.speed, weight*animation.weight); + } + } + } + + private function getObjectNumChildren(object:Object3D):int { + if (object is Skin) { + return Skin(object).numJoints; + } else if (object is Joint) { + return Joint(object).numJoints; + } else if (object is Object3DContainer) { + return Object3DContainer(object).numChildren; + } + return 0; + } + + private function getObjectChildAt(object:Object3D, index:int):Object3D { + if (object is Skin) { + return Skin(object).getJointAt(index); + } else if (object is Joint) { + return Joint(object).getJointAt(index); + } else if (object is Object3DContainer) { + return Object3DContainer(object).getChildAt(index); + } + return null; + } + + /** + * @private + * Возвращает путь к объекту относительно некоторого родительского объекта. + * + * @param target объект для поиска, должен иметь имя + * @param base родительский объект. Может быть экземпляром классов Skin, Joint. + * + * @return путь до объекта в виде вектора имен проиндексированого в порядке увеличения + */ + private function getPathByTarget(target:Object3D, base:Object3D):Vector. { + var i:int, count:int; + for (i = 0, count = getObjectNumChildren(base); i < count; i++) { + var child:Object3D = getObjectChildAt(base, i); + if (child == target) { + return Vector.([target.name]); + } else { + var founded:Vector. = getPathByTarget(target, child); + if (founded != null) { + if (child.name != null && child.name.length > 0) { + founded.push(child.name); + } + return founded; + } + } + } + return null; + } + + private function getTargetByPath(path:Vector., base:Object3D):Object3D { + var i:int, count:int; + var name:String = path[path.length - 1]; + for (i = 0, count = getObjectNumChildren(base); i < count; i++) { + var child:Object3D = getObjectChildAt(base, i); + if (child.name == null || child.name.length == 0) { + // Ищем внутри + var founded:Object3D = getTargetByPath(path, child); + if (founded != null) { + return founded; + } + } else { + if (child.name == name) { + if (path.length == 1) { + // Нашли объект + return child; + } + path.pop(); + // Ищем внутри + return getTargetByPath(path, child); + } + } + } + return null; + } + + private function formatPath(path:Vector.):String { + path.reverse(); + return path.join("/"); + } + + /** + * Переносит анимацию с одной иерархии на другую с такой же структурой. + * Соответствие объектов определяется по их именам. Объекты без имен пропускаются при поиске соответствия. + * + * @param object экземпляр любого из классов Object3DContainer, Skin, Joint на который производится перенос анимации. + * + * @throw ArgumentError переменная object не может быть равна null + * @throw ArgumentError анимация не ссылается ни на один объект + * @throw Error аналогичный объект не найден в другой иерархии + * @throw Error один из объектов в дочерней анимации не является потомком объекта анимации + * @throw Error один из объектов в дочерней анимации не имеет имени + */ + override public function reassign(object:Object3D):void { + if (object == null) { + throw new ArgumentError("Object must be not null"); + } + var base:Object3D = this.object; + if (base == null) { + throw new ArgumentError("Base animation object must be not null"); + } + var i:int; + var animation:Animation; + for (i = 0; i < _numAnimations; i++) { + animation = _animations[i]; + if (animation.object == base) { + // Если дочерняя анимация ссылается на объект родительской анимации, просто переназначаем на новый объект + animation.reassign(object); + } else { + var child:Object3D = animation.object; + if (child != null && child.name != null && child.name.length > 0) { + // Собираем путь до объекта + var path:Vector. = getPathByTarget(child, base); + if (path != null) { + var target:Object3D = getTargetByPath(path, object); + if (target != null) { + animation.reassign(target); + } else { + throw new Error('Similiar object of animation at index ' + i + ' not found in target by path:"' + formatPath(path) + '"'); + } + } else { + throw new Error("Object of animation at index " + i + " is not child of object from base animation"); + } + } else { + if (child != null) { + throw new Error("Object of animation at index " + i + " dont have name"); + } + } + } + } + super.reassign(object); + } + + /** + * @inheritDoc + */ + override public function clone():Animation { + var cloned:AnimationGroup = new AnimationGroup(object, weight, speed); + for (var i:int = 0; i < _numAnimations; i++) { + cloned.addAnimation(_animations[i].clone()); + } + cloned.length = length; + return cloned; + } + + /** + * Добавляет дочернюю анимацию и обновляет длину анимации. + */ + public function addAnimation(animation:Animation):Animation { + if (animation == null) { + throw new Error("Animation cannot be null"); + } + _animations[_numAnimations++] = animation; + if (animation.length > length) { + length = animation.length; + } + return animation; + } + + /** + * Убирает дочернюю анимацию и обновляет длину анимации. + */ + public function removeAnimation(animation:Animation):Animation { + var index:int = _animations.indexOf(animation); + if (index < 0) throw new ArgumentError("Animation not found"); + _numAnimations--; + var j:int = index + 1; + while (index < _numAnimations) { + _animations[index] = _animations[j]; + index++; + j++; + } + _animations.length = _numAnimations; + // Пересчитываем длину + length = 0; + for (var i:int = 0; i < _numAnimations; i++) { + var anim:Animation = _animations[i]; + if (anim.length > length) { + length = anim.length; + } + } + return animation; + } + + /** + * Количество дочерних анимаций. + */ + public function get numAnimations():int { + return _numAnimations; + } + + /** + * Возвращает анимацию по индексу. + */ + public function getAnimationAt(index:int):Animation { + return _animations[index]; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationState.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationState.as new file mode 100644 index 0000000..251d282 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationState.as @@ -0,0 +1,282 @@ +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + + /** + * Cостояние проигрывания анимации в контроллере. + */ + public final class AnimationState { + + use namespace alternativa3d; + + /** + * Проигрываемая анимация. + */ + public var animation:Animation; + + /** + * Зацикленность анимации. Зацикленная анимация будет проигрываться сначала после достижения конца. + */ + public var loop:Boolean; + + /** + * Для незацикленной анимации задает время с отсчетом от конца анимации после которого начнется затухание анимации. + * 0 - без затухания, 1 - затухание с начала анимации. + */ + public var endingFadeOut:Number = 0; + + private var fadeInTime:Number; + private var fadedIn:Boolean; + private var fadeInPosition:Number; + private var fadeOutTime:Number; + private var fadedOut:Boolean; + private var fadeOutPosition:Number; + + private var manualControl:Boolean = false; + + private var _controller:AnimationController; + + private var _name:String; + private var _played:Boolean = false; + private var _position:Number = 0; + + /** + * Конструктор состояния анимации, вызывается в AnimationController. + * + * @see AnimationController + */ + public function AnimationState(controller:AnimationController, animation:Animation, name:String, loop:Boolean) { + this.animation = animation; + this._controller = controller; + this._name = name; + this.loop = loop; + } + + /** + * Проиграть анимацию сначала. + * + * @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения. + */ + public function replay(fade:Number = 0):void { + if (!_played) { + play(fade); + _position = 0; + } + } + + /** + * Продолжить проигрывание анимации с текущей позиции. + * + * @param fade время в течение которого вес анимации должен увеличиться с нуля до максимального значения. + */ + public function play(fade:Number = 0):void { + if (!_played) { + _played = true; + fadeInTime = fade; + fadedIn = true; + if (fadedOut) { + fadeInPosition = fadeInTime*(1 - fadeOutPosition/fadeOutTime); + fadedOut = false; + } else { + fadeInPosition = 0; + } + manualControl = false; + } + } + + /** + * Остановить проигрывание анимации. + * + * @param fade время в течение которого вес анимации должен уменьшиться с максимального значения до 0. + */ + public function stop(fade:Number = 0):void { + if (_played) { + _played = false; + fadeOutTime = fade; + if (fadedIn) { + fadeOutPosition = fadeOutTime*(1 - fadeInPosition/fadeInTime); + fadedIn = false; + fadedOut = true; + } else { + if (!fadedOut) { + fadeOutPosition = 0; + fadedOut = true; + } + } + manualControl = false; + } + } + + /** + * @private + */ + alternativa3d function prepareBlending():void { + if (animation != null) { + animation.prepareBlending(); + } + } + + private function loopPosition():void { + if (_position < 0) { +// _position = (length <= 0) ? 0 : _position % length; + _position = 0; + } else { + if (_position >= animation.length) { + _position = (animation.length <= 0) ? 0 : _position % animation.length; + } + } + } + + private function fading(position:Number):Number { + if (position > 1) { + return 1; + } + if (position < 0) { + return 0; + } + return position; + } + + /** + * @private + */ + alternativa3d function update(interval:Number):void { + if (animation == null) { + return; + } + var weight:Number = animation.weight; + if (_played) { + _position += interval*animation.speed; + if (loop) { + loopPosition(); + if (fadedIn) { + fadeInPosition += interval; + if (fadeInPosition < fadeInTime) { + weight *= fading(fadeInPosition/fadeInTime); + } else { + fadedIn = false; + } + } + } else { + if (_position < 0) { + _position = 0; + if (interval < 0) { + _played = false; + } + weight = 0; + } else { + if (_position > animation.length) { + if (interval > 0) { + _position = 0; + _played = false; + } else { + _position = animation.length; + } + weight = 0; + } else { + if ((_position/animation.length + endingFadeOut) > 1) { + fadedOut = true; + fadeOutTime = endingFadeOut; + fadeOutPosition = _position/animation.length + endingFadeOut - 1; + } else { + fadedOut = false; + } + if (fadedIn) { + fadeInPosition += interval; + } + if ((fadedIn && (fadeInPosition < fadeInTime)) && fadedOut) { + var w1:Number = fading(fadeInPosition/fadeInTime); + var w2:Number = fading(1 - fadeOutPosition/fadeOutTime); + if (w1 < w2) { + weight *= w1; + } else { + weight *= w2; + fadedIn = false; + } + } else { + if (fadedIn) { + if (fadeInPosition < fadeInTime) { + weight *= fading(fadeInPosition/fadeInTime); + } else { + fadedIn = false; + } + } else if (fadedOut) { + weight *= fading(1 - fadeOutPosition/fadeOutTime); + } + } + } + } + } + } else { + if (!manualControl) { + if (fadedOut) { + _position += interval*animation.speed; + if (loop) { + loopPosition(); + } else { + if (_position < 0) { + _position = 0; + } else { + if (_position >= animation.length) { + _position = animation.length; + } + } + } + fadeOutPosition += interval; + if (fadeOutPosition < fadeOutTime) { + weight *= fading(1 - fadeOutPosition/fadeOutTime); + } else { + fadedOut = false; + weight = 0; + } + } else { + weight = 0; + } + } + } + if (weight != 0) { + animation.blend(_position, weight); + } + } + + /** + * Контроллер, управляющий воспроизведением анимации. + */ + public function get controller():AnimationController { + return _controller; + } + + /** + * Имя анимации в контроллере. + */ + public function get name():String { + return _name; + } + + /** + * Проигрывается анимация в данный момент или нет. + */ + public function get played():Boolean { + return _played; + } + + /** + * Позиция проигрывания анимации. + */ + public function get position():Number { + return _position; + } + + /** + * @private + */ + public function set position(value:Number):void { + _position = value; + manualControl = true; + _played = false; + fadedIn = false; + fadedOut = false; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationTimer.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationTimer.as new file mode 100644 index 0000000..e9e23a6 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/AnimationTimer.as @@ -0,0 +1,111 @@ +package alternativa.engine3d.animation { + + import flash.utils.getTimer; + + /** + * Выполняет ежекадровое обновление контроллеров. + */ + public class AnimationTimer { + + /** + * Соотношение виртуального времени реальному + */ + public var timeScale:Number = 1.0; + + private var _numControllers:int; + private var _controllers:Vector. = new Vector.(); + + private var lastTime:int = -1; + + /** + * Создает экземпляр контроллера. + */ + public function AnimationTimer() { + } + + /** + * Начинает отсчет времени. + */ + public function start():void { + lastTime = getTimer(); + } + + /** + * Обновляет контроллеры с времени последнего вызова start() или update(). + * + * @see #start() + */ + public function update():void { + if (lastTime >= 0) { + var time:int = getTimer(); + var interval:Number = 0.001*timeScale*(time - lastTime); + for (var i:int = 0; i < _numControllers; i++) { + var controller:AnimationController = _controllers[i]; + if (controller.enabled) { + controller.update(interval); + } + } + lastTime = time; + } + } + + /** + * Приостанавливает отсчет времени. + */ + public function stop():void { + lastTime = -1; + } + + /** + * Возвращает true если таймер в данный момент остановлен. + */ + public function get stoped():Boolean { + return lastTime == -1; + } + + /** + * Добавляет контроллер. + */ + public function addController(controller:AnimationController):AnimationController { + if (controller == null) { + throw new Error("Controller cannot be null"); + } + _controllers[_numControllers++] = controller; + return controller; + } + + /** + * Убирает контроллер. + */ + public function removeController(controller:AnimationController):AnimationController { + var index:int = _controllers.indexOf(controller); + if (index < 0) throw new ArgumentError("Controller not found"); + _numControllers--; + var j:int = index + 1; + while (index < _numControllers) { + _controllers[index] = _controllers[j]; + index++; + j++; + } + _controllers.length = _numControllers; + return controller; + } + + /** + * Возвращает количество контроллеров. + */ + public function get numControllers():int { + return _numControllers; + } + + /** + * Возвращает контроллер по индексу. + * + * @param index индекс контроллера. + */ + public function getControllerAt(index:int):AnimationController { + return _controllers[index]; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/MatrixAnimation.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/MatrixAnimation.as new file mode 100644 index 0000000..c38b8c1 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/MatrixAnimation.as @@ -0,0 +1,89 @@ +package alternativa.engine3d.animation { + + import __AS3__.vec.Vector; + + import alternativa.engine3d.animation.keys.MatrixKey; + import alternativa.engine3d.core.Object3D; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + /** + * Анимация матрицы объекта. + */ + public class MatrixAnimation extends Animation { + + /** + * Временная шкала с ключевыми кадрами анимации матрицы. + */ + public var matrix:Track; + + private var matrixKey:MatrixKey = new MatrixKey(0, null); + + /** + * Создает новый экземпляр объекта. + * + * @param object объект, матрица которого анимируется. + * @param weight вес анимации. + * @param speed скорость проигрывания анимации. + */ + public function MatrixAnimation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) { + super(object, weight, speed); + } + + /** + * @inheritDoc + */ + override protected function control(position:Number, weight:Number):void { + if (matrix != null && object != null) { + matrix.getKey(position, matrixKey); + var mat:Matrix3D = matrixKey.matrix; + var components:Vector. = mat.decompose(); + var t:Vector3D = components[0]; + var r:Vector3D = components[1]; + var s:Vector3D = components[2]; + var c:Number; + c = calculateBlendInterpolation(WEIGHTS_X, weight); + object.x = (1 - c)*object.x + c*t.x; + c = calculateBlendInterpolation(WEIGHTS_Y, weight); + object.y = (1 - c)*object.y + c*t.y; + c = calculateBlendInterpolation(WEIGHTS_Z, weight); + object.z = (1 - c)*object.z + c*t.z; + c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight); + object.rotationX = interpolateAngle(object.rotationX, r.x, c); + c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight); + object.rotationY = interpolateAngle(object.rotationY, r.y, c); + c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight); + object.rotationZ = interpolateAngle(object.rotationZ, r.z, c); + c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight); + object.scaleX = (1 - c)*object.scaleX + c*s.x; + c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight); + object.scaleY = (1 - c)*object.scaleY + c*s.y; + c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight); + object.scaleZ = (1 - c)*object.scaleZ + c*s.z; + } + } + + /** + * @inheritDoc + */ + override public function clone():Animation { + var cloned:MatrixAnimation = new MatrixAnimation(object, weight, speed); + cloned.matrix = matrix; + cloned.length = length; + return cloned; + } + + /** + * @inheritDoc + */ + override public function updateLength():void { + super.updateLength(); + if (matrix != null) { + var len:Number = matrix.length; + length = (len > length) ? len : length; + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/Track.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/Track.as new file mode 100644 index 0000000..5eca3c4 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/Track.as @@ -0,0 +1,124 @@ +package alternativa.engine3d.animation { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.keys.Key; + + use namespace alternativa3d; + + /** + * Временная шкала с ключевыми кадрами. + */ + public class Track { + + /** + * Ключевые кадры. + */ + public var keyList:Key; + + /** + * Добавляет ключевой кадр. + */ + public function addKey(key:Key):void { + key.next = keyList; + keyList = key; + } + + /** + * Сортирует ключевые кадры по времени. + */ + public function sortKeys():void { + keyList = sortKeysByTime(keyList); + } + + /** + * Возвращает кадр соответствующий заданному времени. + * + * @param time время кадра. + * @param key если не null, результат будет записан в этот объект. + */ + public function getKey(time:Number, key:Key = null):Key { + var prev:Key; + var next:Key = keyList; + while (next != null && next.time < time) { + prev = next; + next = next.next; + } + if (prev != null) return prev.interpolate(time, next, key); + if (next != null) return next.interpolate(time, null, key); + return null; + } + + private function sortKeysByTime(list:Key):Key { + var left:Key = list; + var right:Key = list.next; + while (right != null && right.next != null) { + list = list.next; + right = right.next.next; + } + right = list.next; + list.next = null; + if (left.next != null) { + left = sortKeysByTime(left); + } + if (right.next != null) { + right = sortKeysByTime(right); + } + var flag:Boolean = left.time < right.time; + if (flag) { + list = left; + left = left.next; + } else { + list = right; + right = right.next; + } + var last:Key = list; + while (true) { + if (left == null) { + last.next = right; + return list; + } else if (right == null) { + last.next = left; + return list; + } + if (flag) { + if (left.time < right.time) { + last = left; + left = left.next; + } else { + last.next = right; + last = right; + right = right.next; + flag = false; + } + } else { + if (right.time < left.time) { + last = right; + right = right.next; + } else { + last.next = left; + last = left; + left = left.next; + flag = true; + } + } + } + return null; + } + + /** + * Возвращает время последнего ключевого кадра. + */ + public function get length():Number { + if (keyList != null) { + var key:Key = keyList; + while (key.next != null) { + key = key.next; + } + return key.time; + } else { + return 0; + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/TransformAnimation.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/TransformAnimation.as new file mode 100644 index 0000000..040ef99 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/TransformAnimation.as @@ -0,0 +1,241 @@ +package alternativa.engine3d.animation { + + import alternativa.engine3d.animation.keys.PointKey; + import alternativa.engine3d.animation.keys.ValueKey; + import alternativa.engine3d.core.Object3D; + + /** + * Анимация компонентов положения и ориентации объекта. + */ + public class TransformAnimation extends Animation { + + /** + * Временная шкала с ключевыми кадрами положения объекта. + */ + public var translation:Track; + /** + * Временная шкала с ключевыми кадрами вращения объекта. + */ + public var rotation:Track; + /** + * Временная шкала с ключевыми кадрами масштаба объекта. + */ + public var scale:Track; + + /** + * Временная шкала с ключевыми кадрами перемещения объекта по оси X. + */ + public var x:Track; + /** + * Временная шкала с ключевыми кадрами перемещения объекта по оси Y. + */ + public var y:Track; + /** + * Временная шкала с ключевыми кадрами перемещения объекта по оси Z. + */ + public var z:Track; + + /** + * Временная шкала с ключевыми кадрами вращения объекта по оси X. + */ + public var rotationX:Track; + /** + * Временная шкала с ключевыми кадрами вращения объекта по оси Y. + */ + public var rotationY:Track; + /** + * Временная шкала с ключевыми кадрами вращения объекта по оси Z. + */ + public var rotationZ:Track; + + /** + * Временная шкала с ключевыми кадрами масштаба объекта по оси X. + */ + public var scaleX:Track; + /** + * Временная шкала с ключевыми кадрами масштаба объекта по оси Y. + */ + public var scaleY:Track; + /** + * Временная шкала с ключевыми кадрами масштаба объекта по оси Z. + */ + public var scaleZ:Track; + + private var valueKey:ValueKey = new ValueKey(0, 0); + private var pointKey:PointKey = new PointKey(0, 0, 0, 0); + + /** + * Конструктор анимации. + * + * @param object анимируемый объект. + * @param weight вес анимации. + * @param speed скорость проигрывания анимации. + */ + public function TransformAnimation(object:Object3D = null, weight:Number = 1.0, speed:Number = 1.0) { + super(object, weight, speed); + } + + /** + * @inheritDoc + */ + override protected function control(position:Number, weight:Number):void { + if (object == null) { + return; + } + var c:Number; + if (translation != null) { + translation.getKey(position, pointKey); + c = calculateBlendInterpolation(WEIGHTS_X, weight); + object.x = (1 - c)*object.x + c*pointKey.x; + c = calculateBlendInterpolation(WEIGHTS_Y, weight); + object.y = (1 - c)*object.y + c*pointKey.y; + c = calculateBlendInterpolation(WEIGHTS_Z, weight); + object.z = (1 - c)*object.z + c*pointKey.z; + } else { + if (x != null) { + x.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_X, weight); + object.x = (1 - c)*object.x + c*valueKey.value; + } + if (y != null) { + y.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_Y, weight); + object.y = (1 - c)*object.y + c*valueKey.value; + } + if (z != null) { + z.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_Z, weight); + object.z = (1 - c)*object.z + c*valueKey.value; + } + } + if (rotation != null) { + rotation.getKey(position, pointKey); + c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight); + object.rotationX = interpolateAngle(object.rotationX, pointKey.x, c); + c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight); + object.rotationY = interpolateAngle(object.rotationY, pointKey.y, c); + c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight); + object.rotationZ = interpolateAngle(object.rotationZ, pointKey.z, c); + } else { + if (rotationX != null) { + rotationX.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_ROT_X, weight); + object.rotationX = interpolateAngle(object.rotationX, valueKey.value, c); + } + if (rotationY != null) { + rotationY.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_ROT_Y, weight); + object.rotationY = interpolateAngle(object.rotationY, valueKey.value, c); + } + if (rotationZ != null) { + rotationZ.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_ROT_Z, weight); + object.rotationZ = interpolateAngle(object.rotationZ, valueKey.value, c); + } + } + if (scale != null) { + scale.getKey(position, pointKey); + c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight); + object.scaleX = (1 - c)*object.scaleX + c*pointKey.x; + c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight); + object.scaleY = (1 - c)*object.scaleY + c*pointKey.y; + c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight); + object.scaleZ = (1 - c)*object.scaleZ + c*pointKey.z; + } else { + if (scaleX != null) { + scaleX.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_SCALE_X, weight); + object.scaleX = (1 - c)*object.scaleX + c*valueKey.value; + } + if (scaleY != null) { + scaleY.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_SCALE_Y, weight); + object.scaleY = (1 - c)*object.scaleY + c*valueKey.value; + } + if (scaleZ != null) { + scaleZ.getKey(position, valueKey); + c = calculateBlendInterpolation(WEIGHTS_SCALE_Z, weight); + object.scaleZ = (1 - c)*object.scaleZ + c*valueKey.value; + } + } + } + + /** + * @inheritDoc + */ + override public function updateLength():void { + super.updateLength(); + var len:Number; + if (translation != null) { + len = translation.length; + length = (len > length) ? len : length; + } + if (rotation != null) { + len = rotation.length; + length = (len > length) ? len : length; + } + if (scale != null) { + len = scale.length; + length = (len > length) ? len : length; + } + if (x != null) { + len = x.length; + length = (len > length) ? len : length; + } + if (y != null) { + len = y.length; + length = (len > length) ? len : length; + } + if (z != null) { + len = z.length; + length = (len > length) ? len : length; + } + if (rotationX != null) { + len = rotationX.length; + length = (len > length) ? len : length; + } + if (rotationY != null) { + len = rotationY.length; + length = (len > length) ? len : length; + } + if (rotationZ != null) { + len = rotationZ.length; + length = (len > length) ? len : length; + } + if (scaleX != null) { + len = scaleX.length; + length = (len > length) ? len : length; + } + if (scaleY != null) { + len = scaleY.length; + length = (len > length) ? len : length; + } + if (scaleZ != null) { + len = scaleZ.length; + length = (len > length) ? len : length; + } + } + + /** + * @inheritDoc + */ + override public function clone():Animation { + var cloned:TransformAnimation = new TransformAnimation(object, weight, speed); + cloned.translation = translation; + cloned.rotation = rotation; + cloned.scale = scale; + cloned.x = x; + cloned.y = y; + cloned.z = z; + cloned.rotationX = rotationX; + cloned.rotationY = rotationY; + cloned.rotationZ = rotationZ; + cloned.scaleX = scaleX; + cloned.scaleY = scaleY; + cloned.scaleZ = scaleZ; + cloned.length = length; + return cloned; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/Key.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/Key.as new file mode 100644 index 0000000..9eded98 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/Key.as @@ -0,0 +1,40 @@ +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Ключевой кадр. + */ + public class Key { + + /** + * Время кадра. + */ + public var time:Number; + /** + * Ссылка на следующий ключевой кадр на верменной шкале. + * + * @see alternativa.engine3d.animation.Track + */ + public var next:Key; + + /** + * Создает экземпляр ключевого кадра. + * + * @param time время кадра. + */ + public function Key(time:Number) { + this.time = time; + } + + /** + * @private + */ + alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + return key; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/MatrixKey.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/MatrixKey.as new file mode 100644 index 0000000..c2a6573 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/MatrixKey.as @@ -0,0 +1,62 @@ +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Ключевой кадр матричного типа. + */ + public class MatrixKey extends Key { + + private static const tempMatrix:Matrix3D = new Matrix3D(); + + /** + * Значение ключа. + */ + public var matrix:Matrix3D; + + /** + * Создает ключевой кадр матричного типа. + * + * @param time время кадра. + * @param matrix значение кадра. + */ + public function MatrixKey(time:Number, matrix:Matrix3D) { + super(time); + this.matrix = matrix; + } + + /** + * @private + */ + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + var mat:Matrix3D; + if (next != null) { + mat = tempMatrix; + mat.identity(); + mat.append(matrix); + mat.interpolateTo((next as MatrixKey).matrix, (time - this.time)/(next.time - this.time)); + } else { + mat = matrix; + } + if (key != null) { + key.time = time; + MatrixKey(key).matrix = mat; + return key; + } else { + return new MatrixKey(time, mat); + } + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[MatrixKey " + time + ":" + matrix.rawData + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/PointKey.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/PointKey.as new file mode 100644 index 0000000..89b6fa7 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/PointKey.as @@ -0,0 +1,76 @@ +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Ключевой кадр точечного типа. + */ + public class PointKey extends Key { + + /** + * Координата по оси X. + */ + public var x:Number; + /** + * Координата по оси Y. + */ + public var y:Number; + /** + * Координата по оси Z. + */ + public var z:Number; + + /** + * Создает экземпляр ключевого кадра. + * + * @param time время кадра. + * @param x координата по оси X + * @param y координата по оси Y + * @param z координата по оси Z + */ + public function PointKey(time:Number, x:Number, y:Number, z:Number) { + super(time); + this.x = x; + this.y = y; + this.z = z; + } + + /** + * @private + */ + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + var x:Number; + var y:Number; + var z:Number; + if (next != null) { + var t:Number = (time - this.time)/(next.time - this.time); + x = this.x + (PointKey(next).x - this.x)*t; + y = this.y + (PointKey(next).y - this.y)*t; + z = this.z + (PointKey(next).z - this.z)*t; + } else { + x = this.x; + y = this.y; + z = this.z; + } + if (key != null) { + key.time = time; + PointKey(key).x = x; + PointKey(key).y = y; + PointKey(key).z = z; + return key; + } else { + return new PointKey(time, x, y, z); + } + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[PointKey " + time.toFixed(3) + ":" + x + "," + y + "," + z + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/ValueKey.as b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/ValueKey.as new file mode 100644 index 0000000..ae4f9bd --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/animation/keys/ValueKey.as @@ -0,0 +1,55 @@ +package alternativa.engine3d.animation.keys { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + /** + * Ключевой кадр вещественного типа. + */ + public class ValueKey extends Key { + + /** + * Значение ключевого кадра. + */ + public var value:Number; + + /** + * Создает ключевой кадр. + * + * @param time время кадра. + * @param value значение кадра. + */ + public function ValueKey(time:Number, value:Number) { + super(time); + this.value = value; + } + + /** + * @private + */ + override alternativa3d function interpolate(time:Number, next:Key, key:Key = null):Key { + var value:Number; + if (next != null) { + value = this.value + (ValueKey(next).value - this.value)*(time - this.time)/(next.time - this.time); + } else { + value = this.value; + } + if (key != null) { + key.time = time; + ValueKey(key).value = value; + return key; + } else { + return new ValueKey(time, value); + } + } + + /** + * Строковое представление объекта. + */ + public function toString():String { + return "[ValueKey " + time + ":" + value + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/containers/ConflictContainer.as b/Alternativa3D7/7.4/alternativa/engine3d/containers/ConflictContainer.as new file mode 100644 index 0000000..a93561e --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/containers/ConflictContainer.as @@ -0,0 +1,1079 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class ConflictContainer extends Object3DContainer { + + public var resolveByAABB:Boolean = true; + public var resolveByOOBB:Boolean = true; + + //public var isolateAABBConflicts:Boolean = false; + //public var isolateOOBBConflicts:Boolean = false; + + public var threshold:Number = 0.1; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + // Если есть видимая геометрия + if (geometry != null) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Если объектов несколько + if (geometry.next != null) { + var current:Geometry; + // Расчёт инверсной матрицы камеры и позиции камеры в контейнере + calculateInverseMatrix(object); + // AABB + if (resolveByAABB) { + for (current = geometry; current != null; current = current.next) { + current.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + } + drawAABBGeometry(camera, object, canvas, geometry); + // OOBB + } else if (resolveByOOBB) { + for (current = geometry; current != null; current = current.next) { + current.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } else { + geometry.draw(camera, object, canvas, threshold); + geometry.destroy(); + } + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawAABBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var coord:Number; + var coordMin:Number; + var coordMax:Number; + var axisX:Boolean; + var axisY:Boolean; + var current:Geometry = geometry; + var compared:Geometry; + // Поиск сплита + while (current != null) { + // Сплиты по оси X + coord = current.boundMinX; + coordMin = coord - threshold; + coordMax = coord + threshold; + var outside:Boolean = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxX <= coordMax) { + outside = true; + } else if (compared.boundMinX < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + coord = current.boundMaxX; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinX >= coordMin) { + outside = true; + } else if (compared.boundMaxX > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = true; + axisY = false; + break; + } + // Сплиты по оси Y + coord = current.boundMinY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxY <= coordMax) { + outside = true; + } else if (compared.boundMinY < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + coord = current.boundMaxY; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinY >= coordMin) { + outside = true; + } else if (compared.boundMaxY > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = true; + break; + } + // Сплиты по оси Z + coord = current.boundMinZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMaxZ <= coordMax) { + outside = true; + } else if (compared.boundMinZ < coordMin) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + coord = current.boundMaxZ; + coordMin = coord - threshold; + coordMax = coord + threshold; + outside = false; + compared = geometry; + while (compared != null) { + if (current != compared) { + if (compared.boundMinZ >= coordMin) { + outside = true; + } else if (compared.boundMaxZ > coordMax) { + break; + } + } + compared = compared.next; + } + if (compared == null && outside) { + axisX = false; + axisY = false; + break; + } + current = current.next; + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + while (geometry != null) { + next = geometry.next; + var min:Number = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + var max:Number = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max > coordMax) { + geometry.next = positive; + positive = geometry; + } else if (min < coordMin) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Определение положения камеры + if (axisX && imd > coord || axisY && imh > coord || !axisX && !axisY && iml > coord) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawAABBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawAABBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else if (resolveByOOBB) { + for (current = geometry; current != null; current = current.next) { + current.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, geometry); + // Конфликт + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawOOBBGeometry(camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var vertex:Vertex; + var plane:Vertex; + var wrapper:Wrapper; + var o:Number; + var planeX:Number; + var planeY:Number; + var planeZ:Number; + var planeOffset:Number; + var behind:Boolean; + var infront:Boolean; + var current:Geometry; + var compared:Geometry; + // Поиск сплита + for (current = geometry; current != null; current = current.next) { + if (current.viewAligned) { + planeOffset = current.ml; + for (compared = geometry; compared != null; compared = compared.next) { + if (!compared.viewAligned) { + behind = false; + infront = false; + // Перебор точек + for (vertex = compared.boundVertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ > planeOffset) { + if (behind) { + break; + } else { + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + // Если встретилось препятствие + if (vertex != null) break; + } + } + // Если не встретилось препятствий + if (compared == null) break; + } else { + // Перебор плоскостей + for (plane = current.boundPlaneList; plane != null; plane = plane.next) { + planeX = plane.cameraX; + planeY = plane.cameraY; + planeZ = plane.cameraZ; + planeOffset = plane.offset; + var outside:Boolean = false; + for (compared = geometry; compared != null; compared = compared.next) { + if (current != compared) { + behind = false; + infront = false; + // Перебор точек + if (compared.viewAligned) { + for (wrapper = compared.faceStruct.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ >= planeOffset - threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + if (wrapper != null) break; + } else { + for (vertex = compared.boundVertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ >= planeOffset - threshold) { + if (behind) { + break; + } else { + outside = true; + infront = true; + } + } else if (infront) { + break; + } else { + behind = true; + } + } + if (vertex != null) break; + } + } + } + // Если не встретилось препятствий и есть объекты по обе стороны + if (compared == null && outside) break; + } + // Если найдена разделяющая плоскость + if (plane != null) break; + } + } + // Если найден сплит + if (current != null) { + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (current.viewAligned) { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + o = geometry.ml - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else { + for (vertex = geometry.boundVertexList; vertex != null; vertex = vertex.next) { + o = vertex.cameraZ - planeOffset; + if (o < -threshold) { + geometry.next = positive; + positive = geometry; + break; + } else if (o > threshold) { + geometry.next = negative; + negative = geometry; + break; + } + } + if (vertex == null) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } else { + while (geometry != null) { + next = geometry.next; + if (geometry.viewAligned) { + for (wrapper = geometry.faceStruct.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + o = vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (wrapper == null) { + geometry.next = middle; + middle = geometry; + } + } else { + for (vertex = geometry.boundVertexList; vertex != null; vertex = vertex.next) { + o = vertex.cameraX*planeX + vertex.cameraY*planeY + vertex.cameraZ*planeZ - planeOffset; + if (o < -threshold) { + geometry.next = negative; + negative = geometry; + break; + } else if (o > threshold) { + geometry.next = positive; + positive = geometry; + break; + } + } + if (vertex == null) { + geometry.next = middle; + middle = geometry; + } + } + geometry = next; + } + } + // Определение положения камеры + if (current.viewAligned || planeOffset < 0) { + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + } else { + // Отрисовка объектов сзади + if (negative != null) { + if (negative.next != null) { + drawOOBBGeometry(camera, object, canvas, negative); + } else { + negative.draw(camera, object, canvas, threshold); + negative.destroy(); + } + } + // Отрисовка объектов в плоскости + while (middle != null) { + next = middle.next; + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + middle = next; + } + // Отрисовка объектов спереди + if (positive != null) { + if (positive.next != null) { + drawOOBBGeometry(camera, object, canvas, positive); + } else { + positive.draw(camera, object, canvas, threshold); + positive.destroy(); + } + } + } + // Если не найден сплит + } else { + drawConflictGeometry(camera, object, canvas, geometry); + } + } + + protected function drawConflictGeometry(camera:Camera3D, object:Object3D, parentCanvas:Canvas, geometry:Geometry):void { + var canvas:Canvas; + var face:Face; + var next:Face; + var nextGeometry:Geometry; + // Геометрия с сортировкой предрасчитанное BSP + var bspGeometry:Geometry; + // Геометрия, которая присутствует в конфликте + var conflict:Geometry; + // Грани с сортировкой динамическое BSP + var dynamicBSPFirst:Face; + var dynamicBSPLast:Face; + // Грани с сортировкой по средним Z + var averageZFirst:Face; + var averageZLast:Face; + // Перебор геометрических объектов + for (; geometry != null; geometry = nextGeometry) { + nextGeometry = geometry.next; + // Сортировка по предрасчитанному BSP + if (geometry.sorting == 3) { + geometry.next = bspGeometry; + bspGeometry = geometry; + } else { + // Сортировка по динамическому BSP + if (geometry.sorting == 2) { + if (dynamicBSPFirst != null) { + dynamicBSPLast.processNext = geometry.faceStruct; + } else { + dynamicBSPFirst = geometry.faceStruct; + } + dynamicBSPLast = geometry.faceStruct; + dynamicBSPLast.geometry = geometry; + while (dynamicBSPLast.processNext != null) { + dynamicBSPLast = dynamicBSPLast.processNext; + dynamicBSPLast.geometry = geometry; + } + // Сортировка по средним Z + } else { + if (averageZFirst != null) { + averageZLast.processNext = geometry.faceStruct; + } else { + averageZFirst = geometry.faceStruct; + } + averageZLast = geometry.faceStruct; + averageZLast.geometry = geometry; + while (averageZLast.processNext != null) { + averageZLast = averageZLast.processNext; + averageZLast.geometry = geometry; + } + } + geometry.faceStruct = null; + geometry.next = conflict; + conflict = geometry; + } + } + // Соединение списков + if (conflict != null) { + geometry = conflict; + while (geometry.next != null) { + geometry = geometry.next; + } + geometry.next = bspGeometry; + } else { + conflict = bspGeometry; + } + // Сбор первоначальной кучи граней + var list:Face; + if (dynamicBSPFirst != null) { + list = dynamicBSPFirst; + dynamicBSPLast.processNext = averageZFirst; + } else { + list = averageZFirst; + } + // Если есть статические BSP + if (bspGeometry != null) { + // Встройка кучи в первый bsp с внутренней сортировкой + bspGeometry.faceStruct.geometry = bspGeometry; + list = collectNode(bspGeometry.faceStruct, list, camera, threshold, true); + bspGeometry.faceStruct = null; + // Встройка кучи в остальные bsp без внутренней сортировки + for (bspGeometry = bspGeometry.next; bspGeometry != null; bspGeometry = bspGeometry.next) { + bspGeometry.faceStruct.geometry = bspGeometry; + list = collectNode(bspGeometry.faceStruct, list, camera, threshold, false); + bspGeometry.faceStruct = null; + } + // Если есть динамические BSP + } else if (dynamicBSPFirst != null) { + list = collectNode(null, list, camera, threshold, true); + // Если есть сортировка по средним Z + } else if (averageZFirst != null) { + list = sortByAverageZ(list); + } + // Сбор отрисовочных вызовов + var first:Face; + var last:Face; + var drawList:Face; + for (face = list; face != null; face = next) { + next = face.processNext; + geometry = face.geometry; + face.geometry = null; + var changeGeometry:Boolean = next == null || geometry != next.geometry; + // Если сменилась геометрия или материал + if (changeGeometry || face.material != next.material) { + // Разрыв на стыке + face.processNext = null; + // Если сменилась геометрия + if (changeGeometry) { + if (first != null) { + last.negative = list; + first = null; + last = null; + } else { + list.positive = drawList; + drawList = list; + drawList.geometry = geometry; + } + // Если сменился материал + } else { + if (first != null) { + last.negative = list; + } else { + list.positive = drawList; + drawList = list; + drawList.geometry = geometry; + first = list; + } + last = list; + } + list = next; + } + } + // Дебаг + if (camera.debug) { + canvas = parentCanvas.getChildCanvas(object, true, false); + for (list = drawList; list != null; list = list.positive) { + if (list.geometry.debug & Debug.EDGES) { + for (face = list; face != null; face = face.negative) { + Debug.drawEdges(camera, canvas, face, 0xFF0000); + } + } + } + } + // Отрисовка + while (drawList != null) { + list = drawList; + drawList = list.positive; + list.positive = null; + geometry = list.geometry; + list.geometry = null; + canvas = parentCanvas.getChildCanvas(geometry.interactiveObject, true, false, geometry.alpha, geometry.blendMode, geometry.colorTransform, geometry.filters); + for (; list != null; list = next) { + next = list.negative; + list.negative = null; + if (list.material != null) { + // Отрисовка + if (geometry.viewAligned) { + list.material.drawViewAligned(camera, canvas, list, geometry.ml, geometry.tma, geometry.tmb, geometry.tmc, geometry.tmd, geometry.tmtx, geometry.tmty); + } else { + list.material.draw(camera, canvas, list, geometry.ml); + } + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + } + } + // Зачистка + for (geometry = conflict; geometry != null; geometry = nextGeometry) { + nextGeometry = geometry.next; + geometry.destroy(); + } + } + + private function collectNode(splitter:Face, list:Face, camera:Camera3D, threshold:Number, sort:Boolean, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var normalX:Number; + var normalY:Number; + var normalZ:Number; + var offset:Number; + var splitterLast:Face; + var negativeNode:Face; + var positiveNode:Face; + var geometry:Geometry; + // Статическая нода + if (splitter != null) { + geometry = splitter.geometry; + if (splitter.offset < 0) { + negativeNode = splitter.negative; + positiveNode = splitter.positive; + normalX = splitter.normalX; + normalY = splitter.normalY; + normalZ = splitter.normalZ; + offset = splitter.offset; + } else { + negativeNode = splitter.positive; + positiveNode = splitter.negative; + normalX = -splitter.normalX; + normalY = -splitter.normalY; + normalZ = -splitter.normalZ; + offset = -splitter.offset; + } + splitter.negative = null; + splitter.positive = null; + if (splitter.wrapper != null) { + splitterLast = splitter; + while (splitterLast.processNext != null) { + splitterLast = splitterLast.processNext; + splitterLast.geometry = geometry; + } + } else { + splitter.geometry = null; + splitter = null; + } + // Динамическая грань + } else { + splitter = list; + list = splitter.processNext; + splitterLast = splitter; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + normalX = 0; + normalY = 0; + normalZ = 1; + offset = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + if (splitter != null) { + splitterLast.processNext = face; + } else { + splitter = face; + } + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + negative.geometry = face.geometry; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + positive.geometry = face.geometry; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + for (w = face.wrapper.next.next; w.next != null; w = w.next); + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + face.geometry = null; + } + } + // Передняя часть + if (positiveNode != null) { + positiveNode.geometry = geometry; + if (positiveLast != null) positiveLast.processNext = null; + result = collectNode(positiveNode, positiveFirst, camera, threshold, sort, result); + } else if (positiveFirst != null) { + // Если нужно сортировать + if (sort && positiveFirst != positiveLast) { + if (positiveLast != null) positiveLast.processNext = null; + if (positiveFirst.geometry.sorting == 2) { + result = collectNode(null, positiveFirst, camera, threshold, sort, result); + } else { + positiveFirst = sortByAverageZ(positiveFirst); + // Не красиво + for (positiveLast = positiveFirst.processNext; positiveLast.processNext != null; positiveLast = positiveLast.processNext); + positiveLast.processNext = result; + result = positiveFirst; + } + } else { + positiveLast.processNext = result; + result = positiveFirst; + } + } + // Средння часть + if (splitter != null) { + splitterLast.processNext = result; + result = splitter; + } + // Задняя часть + if (negativeNode != null) { + negativeNode.geometry = geometry; + if (negativeLast != null) negativeLast.processNext = null; + result = collectNode(negativeNode, negativeFirst, camera, threshold, sort, result); + } else if (negativeFirst != null) { + // Если нужно сортировать + if (sort && negativeFirst != negativeLast) { + if (negativeLast != null) negativeLast.processNext = null; + if (negativeFirst.geometry.sorting == 2) { + result = collectNode(null, negativeFirst, camera, threshold, sort, result); + } else { + negativeFirst = sortByAverageZ(negativeFirst); + // Не красиво + for (negativeLast = negativeFirst.processNext; negativeLast.processNext != null; negativeLast = negativeLast.processNext); + negativeLast.processNext = result; + result = negativeFirst; + } + } else { + negativeLast.processNext = result; + result = negativeFirst; + } + } + return result; + } + + private function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/containers/KDTree.as b/Alternativa3D7/7.4/alternativa/engine3d/containers/KDTree.as new file mode 100644 index 0000000..e48e1a0 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/containers/KDTree.as @@ -0,0 +1,1165 @@ +package alternativa.engine3d.containers { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.KDNode; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого помещены в бинарную древовидную структуру. + * Для построения дерева нужно добавить статические дочерние объекты + * с помощью addStaticChild(), затем вызвать createTree(). По баундам статических объектов + * построится ориентированная по осям бинарная древовидная структура (KD - частный случай BSP). + * Динамические объекты, можно в любое время добавлять addDynamicChild() и удалять removeDynamicChild(). + * Объекты, добавленные с помощью addChild() будут отрисовываться поверх всего в порядке добавления. + */ + public class KDTree extends ConflictContainer { + + public var debugAlphaFade:Number = 0.8; + + alternativa3d var root:KDNode; + + // Плоскости отсечения камеры в контейнере + private var nearPlaneX:Number; + private var nearPlaneY:Number; + private var nearPlaneZ:Number; + private var nearPlaneOffset:Number; + private var farPlaneX:Number; + private var farPlaneY:Number; + private var farPlaneZ:Number; + private var farPlaneOffset:Number; + private var leftPlaneX:Number; + private var leftPlaneY:Number; + private var leftPlaneZ:Number; + private var leftPlaneOffset:Number; + private var rightPlaneX:Number; + private var rightPlaneY:Number; + private var rightPlaneZ:Number; + private var rightPlaneOffset:Number; + private var topPlaneX:Number; + private var topPlaneY:Number; + private var topPlaneZ:Number; + private var topPlaneOffset:Number; + private var bottomPlaneX:Number; + private var bottomPlaneY:Number; + private var bottomPlaneZ:Number; + private var bottomPlaneOffset:Number; + + // Перекрытия + private var occluders:Vector. = new Vector.(); + private var numOccluders:int; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Если есть корневая нода + if (root != null) { + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Расчёт плоскостей камеры в контейнере + calculateCameraPlanes(camera.nearClipping, camera.farClipping); + // Проверка на видимость рутовой ноды + var culling:int = cullingInContainer(object.culling, root.boundMinX, root.boundMinY, root.boundMinZ, root.boundMaxX, root.boundMaxY, root.boundMaxZ); + if (culling >= 0) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.NODES) { + debugNode(root, culling, camera, object, canvas, 1); + Debug.drawBounds(camera, canvas, object, root.boundMinX, root.boundMinY, root.boundMinZ, root.boundMaxX, root.boundMaxY, root.boundMaxZ, 0xDD33DD); + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Окклюдеры + numOccluders = 0; + if (camera.numOccluders > 0) { + updateOccluders(camera); + } + // Сбор видимой геометрии + var geometry:Geometry = getGeometry(camera, object); + for (var current:Geometry = geometry; current != null; current = current.next) { + current.calculateAABB(ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + } + // Отрисовка дерева + drawNode(root, culling, camera, object, canvas, geometry); + // Зачиска окклюдеров + for (var i:int = 0; i < numOccluders; i++) { + var first:Vertex = occluders[i]; + for (var last:Vertex = first; last.next != null; last = last.next); + last.next = Vertex.collector; + Vertex.collector = first; + occluders[i] = null; + } + numOccluders = 0; + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } else { + super.draw(camera, object, parentCanvas); + } + } else { + super.draw(camera, object, parentCanvas); + } + } + + private function debugNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, alpha:Number):void { + if (node.negative != null) { + var negativeCulling:int = (culling > 0) ? cullingInContainer(culling, node.negative.boundMinX, node.negative.boundMinY, node.negative.boundMinZ, node.negative.boundMaxX, node.negative.boundMaxY, node.negative.boundMaxZ) : 0; + var positiveCulling:int = (culling > 0) ? cullingInContainer(culling, node.positive.boundMinX, node.positive.boundMinY, node.positive.boundMinZ, node.positive.boundMaxX, node.positive.boundMaxY, node.positive.boundMaxZ) : 0; + if (negativeCulling >= 0) { + debugNode(node.negative, negativeCulling, camera, object, canvas, alpha*debugAlphaFade); + } + Debug.drawKDNode(camera, canvas, object, node.axis, node.coord, node.boundMinX, node.boundMinY, node.boundMinZ, node.boundMaxX, node.boundMaxY, node.boundMaxZ, alpha); + if (positiveCulling >= 0) { + debugNode(node.positive, positiveCulling, camera, object, canvas, alpha*debugAlphaFade); + } + } + } + + private function drawNode(node:KDNode, culling:int, camera:Camera3D, object:Object3D, canvas:Canvas, geometry:Geometry):void { + var i:int; + var next:Geometry; + var negative:Geometry; + var middle:Geometry; + var positive:Geometry; + if (camera.occludedAll) { + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + return; + } + var nodeObjects:Vector. = node.objects; + var nodeObjectsBounds:Vector. = node.objectsBounds; + var nodeObjectsLength:int = node.objectsLength; + var nodeOccluders:Vector. = node.occluders; + var nodeOccludersBounds:Vector. = node.occludersBounds; + var nodeOccludersLength:int = node.occludersLength; + var child:Object3D; + var bound:Object3D; + // Узловая нода + if (node.negative != null) { + var negativeCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(culling, node.negative.boundMinX, node.negative.boundMinY, node.negative.boundMinZ, node.negative.boundMaxX, node.negative.boundMaxY, node.negative.boundMaxZ) : 0; + var positiveCulling:int = (culling > 0 || numOccluders > 0) ? cullingInContainer(culling, node.positive.boundMinX, node.positive.boundMinY, node.positive.boundMinZ, node.positive.boundMaxX, node.positive.boundMaxY, node.positive.boundMaxZ) : 0; + var axisX:Boolean = node.axis == 0; + var axisY:Boolean = node.axis == 1; + var min:Number; + var max:Number; + // Если видны обе дочерние ноды + if (negativeCulling >= 0 && positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + if (min < node.minCoord) { + geometry.next = negative; + negative = geometry; + } else { + geometry.next = middle; + middle = geometry; + } + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + geometry.split(camera, (node.axis == 0) ? 1 : 0, (node.axis == 1) ? 1 : 0, (node.axis == 2) ? 1 : 0, node.coord, threshold); + // Если негативный не пустой + if (geometry.next != null) { + geometry.next.next = negative; + negative = geometry.next; + } + // Если позитивный не пустой + if (geometry.faceStruct != null) { + geometry.next = positive; + positive = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка дочерних нод и объектов в плоскости + if (axisX && imd > node.coord || axisY && imh > node.coord || !axisX && !axisY && iml > node.coord) { + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + middle.draw(camera, object, canvas, threshold); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + bound = nodeObjectsBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + bound = nodeOccludersBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + } else { + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Отрисовка динамических объектов в ноде + while (middle != null) { + next = middle.next; + // Проверка с окклюдерами и отрисовка + if (middle.numOccluders >= numOccluders || !occludeGeometry(camera, middle)) { + middle.draw(camera, object, canvas, threshold); + } + middle.destroy(); + middle = next; + } + // Отрисовка плоских статических объектов в ноде + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + bound = nodeObjectsBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + bound = nodeOccludersBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + } + // Если видна только негативная + } else if (negativeCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + geometry.next = negative; + negative = geometry; + } else if (min >= node.minCoord) { + geometry.destroy(); + } else { + geometry.crop(camera, (node.axis == 0) ? -1 : 0, (node.axis == 1) ? -1 : 0, (node.axis == 2) ? -1 : 0, -node.coord, threshold); + // Если негативный не пустой + if (geometry.faceStruct != null) { + geometry.next = negative; + negative = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка негативной ноды + drawNode(node.negative, negativeCulling, camera, object, canvas, negative); + // Если видна только позитивная + } else if (positiveCulling >= 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + min = axisX ? geometry.boundMinX : (axisY ? geometry.boundMinY : geometry.boundMinZ); + max = axisX ? geometry.boundMaxX : (axisY ? geometry.boundMaxY : geometry.boundMaxZ); + if (max <= node.maxCoord) { + geometry.destroy(); + } else if (min >= node.minCoord) { + geometry.next = positive; + positive = geometry; + } else { + geometry.crop(camera, (node.axis == 0) ? 1 : 0, (node.axis == 1) ? 1 : 0, (node.axis == 2) ? 1 : 0, node.coord, threshold); + // Если позитивный не пустой + if (geometry.faceStruct != null) { + geometry.next = positive; + positive = geometry; + } else { + geometry.destroy(); + } + } + } + geometry = next; + } + // Отрисовка позитивной ноды + drawNode(node.positive, positiveCulling, camera, object, canvas, positive); + // Если обе ноды не видны + } else { + // Уничтожение динамиков + while (geometry != null) { + next = geometry.next; + geometry.destroy(); + geometry = next; + } + } + // Конечная нода + } else { + // Если есть статические объекты, не считая окклюдеры + if (nodeObjectsLength > 0) { + // Если есть конфликт + if (nodeObjectsLength > 1 || geometry != null) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Превращение статиков в геометрию + for (i = 0; i < nodeObjectsLength; i++) { + child = nodeObjects[i]; + bound = nodeObjectsBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + geometry = child.getGeometry(camera, child); + while (geometry != null) { + next = geometry.next; + geometry.next = middle; + middle = geometry; + geometry = next; + } + } + } + // Разруливаем конфликт + if (middle != null) { + if (middle.next != null) { + drawConflictGeometry(camera, object, canvas, middle); + } else { + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + } + } + } else { + // Если только один статик + child = nodeObjects[i]; + if (child.visible) { + child.composeAndAppend(object); + child.culling = culling; + child.draw(camera, child, canvas); + } + } + // Если нет статических объектов + } else { + // Если есть динамические объекты + if (geometry != null) { + // Если динамических объектов несколько + if (geometry.next != null) { + // Если есть окклюдеры + if (numOccluders > 0) { + // Перебор динамиков + while (geometry != null) { + next = geometry.next; + // Проверка с окклюдерами + if (geometry.numOccluders < numOccluders && occludeGeometry(camera, geometry)) { + geometry.destroy(); + } else { + geometry.next = middle; + middle = geometry; + } + geometry = next; + } + // Если остались объекты + if (middle != null) { + if (middle.next != null) { + // Разруливание + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + for (geometry = middle; geometry != null; geometry = geometry.next) { + geometry.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + drawConflictGeometry(camera, object, canvas, middle); + } + } else { + middle.draw(camera, object, canvas, threshold); + middle.destroy(); + } + } + } else { + // Разруливание + middle = geometry; + if (resolveByAABB) { + drawAABBGeometry(camera, object, canvas, middle); + } else if (resolveByOOBB) { + for (geometry = middle; geometry != null; geometry = geometry.next) { + geometry.calculateOOBB(); + } + drawOOBBGeometry(camera, object, canvas, middle); + } else { + drawConflictGeometry(camera, object, canvas, middle); + } + } + } else { + // Проверка с окклюдерами и отрисовка + if (geometry.numOccluders >= numOccluders || !occludeGeometry(camera, geometry)) { + geometry.draw(camera, object, canvas, threshold); + } + geometry.destroy(); + } + } + } + // Отрисовка окклюдеров + for (i = 0; i < nodeOccludersLength; i++) { + child = nodeOccluders[i]; + bound = nodeOccludersBounds[i]; + if (child.visible && ((child.culling = culling) == 0 && numOccluders == 0 || (child.culling = cullingInContainer(culling, bound.boundMinX, bound.boundMinY, bound.boundMinZ, bound.boundMaxX, bound.boundMaxY, bound.boundMaxZ)) >= 0)) { + child.composeAndAppend(object); + child.draw(camera, child, canvas); + } + } + // Обновление окклюдеров + if (nodeOccludersLength > 0) { + updateOccluders(camera); + } + } + } + + public function createTree(staticObjects:Vector., staticOccluders:Vector. = null):void { + var i:int; + var object:Object3D; + var bound:Object3D; + var staticObjectsLength:int = staticObjects.length; + var staticOccludersLength:int = (staticOccluders != null) ? staticOccluders.length : 0; + var objects:Vector.; + var objectsBounds:Vector.; + var objectsLength:int = 0; + var occluders:Vector.; + var occludersBounds:Vector.; + var occludersLength:int = 0; + // Баунд корневой ноды + var minX:Number = 1e+22; + var minY:Number = 1e+22; + var minZ:Number = 1e+22; + var maxX:Number = -1e+22; + var maxY:Number = -1e+22; + var maxZ:Number = -1e+22; + // Обработка объектов + for (i = 0; i < staticObjectsLength; i++) { + object = staticObjects[i]; + // Расчёт баунда в координатах дерева + bound = new Object3D(); + bound.boundMinX = 1e+22; + bound.boundMinY = 1e+22; + bound.boundMinZ = 1e+22; + bound.boundMaxX = -1e+22; + bound.boundMaxY = -1e+22; + bound.boundMaxZ = -1e+22; + object.composeMatrix(); + object.updateBounds(bound, object); + // Если объект не пустой + if (bound.boundMinX <= bound.boundMaxX) { + if (objects == null) { + objects = new Vector.(); + objectsBounds = new Vector.(); + } + objects[objectsLength] = object; + objectsBounds[objectsLength] = bound; + objectsLength++; + // Коррекция баунда корневой ноды + if (bound.boundMinX < minX) minX = bound.boundMinX; + if (bound.boundMaxX > maxX) maxX = bound.boundMaxX; + if (bound.boundMinY < minY) minY = bound.boundMinY; + if (bound.boundMaxY > maxY) maxY = bound.boundMaxY; + if (bound.boundMinZ < minZ) minZ = bound.boundMinZ; + if (bound.boundMaxZ > maxZ) maxZ = bound.boundMaxZ; + } + } + // Обработка окклюдеров + for (i = 0; i < staticOccludersLength; i++) { + object = staticOccluders[i]; + // Расчёт баунда в координатах дерева + bound = new Object3D(); + bound.boundMinX = 1e+22; + bound.boundMinY = 1e+22; + bound.boundMinZ = 1e+22; + bound.boundMaxX = -1e+22; + bound.boundMaxY = -1e+22; + bound.boundMaxZ = -1e+22; + object.composeMatrix(); + object.updateBounds(bound, object); + // Если объект не пустой + if (bound.boundMinX <= bound.boundMaxX) { + // Проверка выхода окклюдера за границы ноды + if (bound.boundMinX < minX || bound.boundMaxX > maxX || bound.boundMinY < minY || bound.boundMaxY > maxY || bound.boundMinZ < minZ || bound.boundMaxZ > maxZ) { + trace("Incorrect occluder size or position"); + } else { + if (occluders == null) { + occluders = new Vector.(); + occludersBounds = new Vector.(); + } + occluders[occludersLength] = object; + occludersBounds[occludersLength] = bound; + occludersLength++; + } + } + } + // Если есть непустые объекты + if (objectsLength > 0) { + root = new KDNode(); + root.boundMinX = minX; + root.boundMinY = minY; + root.boundMinZ = minZ; + root.boundMaxX = maxX; + root.boundMaxY = maxY; + root.boundMaxZ = maxZ; + createNode(root, objects, objectsBounds, objectsLength, occluders, occludersBounds, occludersLength); + } else { + root = null; + } + } + + static private const splitCoordsX:Vector. = new Vector.(); + static private const splitCoordsY:Vector. = new Vector.(); + static private const splitCoordsZ:Vector. = new Vector.(); + + private function createNode(node:KDNode, objects:Vector., objectsBounds:Vector., numObjects:int, occluders:Vector., occludersBounds:Vector., numOccluders:int):void { + var i:int; + var j:int; + var bound:Object3D; + var coord:Number; + // Сбор потенциальных координат сплита без дубликатов + var numSplitCoordsX:int = 0; + var numSplitCoordsY:int = 0; + var numSplitCoordsZ:int = 0; + for (i = 0; i < numObjects; i++) { + bound = objectsBounds[i]; + if (bound.boundMaxX - bound.boundMinX <= threshold + threshold) { + coord = (bound.boundMinX <= node.boundMinX + threshold) ? node.boundMinX : ((bound.boundMaxX >= node.boundMaxX - threshold) ? node.boundMaxX : (bound.boundMinX + bound.boundMaxX)*0.5); + for (j = 0; j < numSplitCoordsX; j++) if (coord >= splitCoordsX[j] - threshold && coord <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = coord; + } else { + if (bound.boundMinX > node.boundMinX + threshold) { + for (j = 0; j < numSplitCoordsX; j++) if (bound.boundMinX >= splitCoordsX[j] - threshold && bound.boundMinX <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = bound.boundMinX; + } + if (bound.boundMaxX < node.boundMaxX - threshold) { + for (j = 0; j < numSplitCoordsX; j++) if (bound.boundMaxX >= splitCoordsX[j] - threshold && bound.boundMaxX <= splitCoordsX[j] + threshold) break; + if (j == numSplitCoordsX) splitCoordsX[numSplitCoordsX++] = bound.boundMaxX; + } + } + if (bound.boundMaxY - bound.boundMinY <= threshold + threshold) { + coord = (bound.boundMinY <= node.boundMinY + threshold) ? node.boundMinY : ((bound.boundMaxY >= node.boundMaxY - threshold) ? node.boundMaxY : (bound.boundMinY + bound.boundMaxY)*0.5); + for (j = 0; j < numSplitCoordsY; j++) if (coord >= splitCoordsY[j] - threshold && coord <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = coord; + } else { + if (bound.boundMinY > node.boundMinY + threshold) { + for (j = 0; j < numSplitCoordsY; j++) if (bound.boundMinY >= splitCoordsY[j] - threshold && bound.boundMinY <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = bound.boundMinY; + } + if (bound.boundMaxY < node.boundMaxY - threshold) { + for (j = 0; j < numSplitCoordsY; j++) if (bound.boundMaxY >= splitCoordsY[j] - threshold && bound.boundMaxY <= splitCoordsY[j] + threshold) break; + if (j == numSplitCoordsY) splitCoordsY[numSplitCoordsY++] = bound.boundMaxY; + } + } + if (bound.boundMaxZ - bound.boundMinZ <= threshold + threshold) { + coord = (bound.boundMinZ <= node.boundMinZ + threshold) ? node.boundMinZ : ((bound.boundMaxZ >= node.boundMaxZ - threshold) ? node.boundMaxZ : (bound.boundMinZ + bound.boundMaxZ)*0.5); + for (j = 0; j < numSplitCoordsZ; j++) if (coord >= splitCoordsZ[j] - threshold && coord <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = coord; + } else { + if (bound.boundMinZ > node.boundMinZ + threshold) { + for (j = 0; j < numSplitCoordsZ; j++) if (bound.boundMinZ >= splitCoordsZ[j] - threshold && bound.boundMinZ <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = bound.boundMinZ; + } + if (bound.boundMaxZ < node.boundMaxZ - threshold) { + for (j = 0; j < numSplitCoordsZ; j++) if (bound.boundMaxZ >= splitCoordsZ[j] - threshold && bound.boundMaxZ <= splitCoordsZ[j] + threshold) break; + if (j == numSplitCoordsZ) splitCoordsZ[numSplitCoordsZ++] = bound.boundMaxZ; + } + } + } + // Поиск лучшего сплита + var splitAxis:int = -1; + var splitCoord:Number; + var bestCost:Number = 1e+22; + var numNegative:int; + var numPositive:int; + var area:Number; + var areaNegative:Number; + var areaPositive:Number; + var cost:Number; + area = (node.boundMaxY - node.boundMinY)*(node.boundMaxZ - node.boundMinZ); + for (i = 0; i < numSplitCoordsX; i++) { + coord = splitCoordsX[i]; + areaNegative = area*(coord - node.boundMinX); + areaPositive = area*(node.boundMaxX - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + bound = objectsBounds[j]; + if (bound.boundMaxX <= coord + threshold) { + if (bound.boundMinX < coord - threshold) numNegative++; + } else { + if (bound.boundMinX >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 0; + splitCoord = coord; + } + } + } + area = (node.boundMaxX - node.boundMinX)*(node.boundMaxZ - node.boundMinZ); + for (i = 0; i < numSplitCoordsY; i++) { + coord = splitCoordsY[i]; + areaNegative = area*(coord - node.boundMinY); + areaPositive = area*(node.boundMaxY - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + bound = objectsBounds[j]; + if (bound.boundMaxY <= coord + threshold) { + if (bound.boundMinY < coord - threshold) numNegative++; + } else { + if (bound.boundMinY >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 1; + splitCoord = coord; + } + } + } + area = (node.boundMaxX - node.boundMinX)*(node.boundMaxY - node.boundMinY); + for (i = 0; i < numSplitCoordsZ; i++) { + coord = splitCoordsZ[i]; + areaNegative = area*(coord - node.boundMinZ); + areaPositive = area*(node.boundMaxZ - coord); + numNegative = 0; + numPositive = 0; + for (j = 0; j < numObjects; j++) { + bound = objectsBounds[j]; + if (bound.boundMaxZ <= coord + threshold) { + if (bound.boundMinZ < coord - threshold) numNegative++; + } else { + if (bound.boundMinZ >= coord - threshold) numPositive++; else break; + } + } + if (j == numObjects) { + cost = areaNegative*numNegative + areaPositive*numPositive; + if (cost < bestCost) { + bestCost = cost; + splitAxis = 2; + splitCoord = coord; + } + } + } + // Если сплит не найден + if (splitAxis < 0) { + node.objects = objects; + node.objectsBounds = objectsBounds; + node.objectsLength = numObjects; + node.occluders = occluders; + node.occludersBounds = occludersBounds; + node.occludersLength = numOccluders; + } else { + node.axis = splitAxis; + node.coord = splitCoord; + node.minCoord = splitCoord - threshold; + node.maxCoord = splitCoord + threshold; + // Списки разделения + var negativeObjects:Vector.; + var negativeObjectsBounds:Vector.; + var negativeObjectsLength:int = 0; + var negativeOccluders:Vector.; + var negativeOccludersBounds:Vector.; + var negativeOccludersLength:int = 0; + var positiveObjects:Vector.; + var positiveObjectsBounds:Vector.; + var positiveObjectsLength:int = 0; + var positiveOccluders:Vector.; + var positiveOccludersBounds:Vector.; + var positiveOccludersLength:int = 0; + var min:Number; + var max:Number; + // Разделение объектов + for (i = 0; i < numObjects; i++) { + bound = objectsBounds[i]; + min = (splitAxis == 0) ? bound.boundMinX : ((splitAxis == 1) ? bound.boundMinY : bound.boundMinZ); + max = (splitAxis == 0) ? bound.boundMaxX : ((splitAxis == 1) ? bound.boundMaxY : bound.boundMaxZ); + if (max <= splitCoord + threshold) { + if (min < splitCoord - threshold) { + // Объект в негативной стороне + if (negativeObjects == null) { + negativeObjects = new Vector.(); + negativeObjectsBounds = new Vector.(); + } + negativeObjects[negativeObjectsLength] = objects[i]; + negativeObjectsBounds[negativeObjectsLength] = bound; + negativeObjectsLength++; + } else { + // Остаётся в ноде + if (node.objects == null) { + node.objects = new Vector.(); + node.objectsBounds = new Vector.(); + } + node.objects[node.objectsLength] = objects[i]; + node.objectsBounds[node.objectsLength] = bound; + node.objectsLength++; + } + } else { + if (min >= splitCoord - threshold) { + // Объект в положительной стороне + if (positiveObjects == null) { + positiveObjects = new Vector.(); + positiveObjectsBounds = new Vector.(); + } + positiveObjects[positiveObjectsLength] = objects[i]; + positiveObjectsBounds[positiveObjectsLength] = bound; + positiveObjectsLength++; + } else { + // Распилился + } + } + } + // Разделение окклюдеров + for (i = 0; i < numOccluders; i++) { + bound = occludersBounds[i]; + min = (splitAxis == 0) ? bound.boundMinX : ((splitAxis == 1) ? bound.boundMinY : bound.boundMinZ); + max = (splitAxis == 0) ? bound.boundMaxX : ((splitAxis == 1) ? bound.boundMaxY : bound.boundMaxZ); + if (max <= splitCoord + threshold) { + if (min < splitCoord - threshold) { + // Объект в негативной стороне + if (negativeOccluders == null) { + negativeOccluders = new Vector.(); + negativeOccludersBounds = new Vector.(); + } + negativeOccluders[negativeOccludersLength] = occluders[i]; + negativeOccludersBounds[negativeOccludersLength] = bound; + negativeOccludersLength++; + } else { + // Остаётся в ноде + if (node.occluders == null) { + node.occluders = new Vector.(); + node.occludersBounds = new Vector.(); + } + node.occluders[node.occludersLength] = occluders[i]; + node.occludersBounds[node.occludersLength] = bound; + node.occludersLength++; + } + } else { + if (min >= splitCoord - threshold) { + // Объект в положительной стороне + if (positiveOccluders == null) { + positiveOccluders = new Vector.(); + positiveOccludersBounds = new Vector.(); + } + positiveOccluders[positiveOccludersLength] = occluders[i]; + positiveOccludersBounds[positiveOccludersLength] = bound; + positiveOccludersLength++; + } else { + // Распилился + trace("Incorrect occluder size or position"); + } + } + } + // Создание дочерних нод + node.negative = new KDNode(); + node.positive = new KDNode(); + // Назначение баундов + node.negative.boundMinX = node.boundMinX; + node.negative.boundMinY = node.boundMinY; + node.negative.boundMinZ = node.boundMinZ; + node.negative.boundMaxX = node.boundMaxX; + node.negative.boundMaxY = node.boundMaxY; + node.negative.boundMaxZ = node.boundMaxZ; + node.positive.boundMinX = node.boundMinX; + node.positive.boundMinY = node.boundMinY; + node.positive.boundMinZ = node.boundMinZ; + node.positive.boundMaxX = node.boundMaxX; + node.positive.boundMaxY = node.boundMaxY; + node.positive.boundMaxZ = node.boundMaxZ; + // Коррекция + if (splitAxis == 0) { + node.negative.boundMaxX = splitCoord; + node.positive.boundMinX = splitCoord; + } else if (splitAxis == 1) { + node.negative.boundMaxY = splitCoord; + node.positive.boundMinY = splitCoord; + } else { + node.negative.boundMaxZ = splitCoord; + node.positive.boundMinZ = splitCoord; + } + // Разделение дочерних нод + if (negativeObjectsLength > 0) { + createNode(node.negative, negativeObjects, negativeObjectsBounds, negativeObjectsLength, negativeOccluders, negativeOccludersBounds, negativeOccludersLength); + } else if (negativeOccludersLength > 0) { + trace("Incorrect occluder size or position"); + } + if (positiveObjectsLength > 0) { + createNode(node.positive, positiveObjects, positiveObjectsBounds, positiveObjectsLength, positiveOccluders, positiveOccludersBounds, positiveOccludersLength); + } else if (positiveOccludersLength > 0) { + trace("Incorrect occluder size or position"); + } + } + } + + private function calculateCameraPlanes(near:Number, far:Number):void { + // Ближняя плоскость + nearPlaneX = imc; + nearPlaneY = img; + nearPlaneZ = imk; + nearPlaneOffset = (imc*near + imd)*nearPlaneX + (img*near + imh)*nearPlaneY + (imk*near + iml)*nearPlaneZ; + // Дальняя плоскость + farPlaneX = -imc; + farPlaneY = -img; + farPlaneZ = -imk; + farPlaneOffset = (imc*far + imd)*farPlaneX + (img*far + imh)*farPlaneY + (imk*far + iml)*farPlaneZ; + // Верхняя плоскость + var ax:Number = -ima - imb + imc; + var ay:Number = -ime - imf + img; + var az:Number = -imi - imj + imk; + var bx:Number = ima - imb + imc; + var by:Number = ime - imf + img; + var bz:Number = imi - imj + imk; + topPlaneX = bz*ay - by*az; + topPlaneY = bx*az - bz*ax; + topPlaneZ = by*ax - bx*ay; + topPlaneOffset = imd*topPlaneX + imh*topPlaneY + iml*topPlaneZ; + // Правая плоскость + ax = bx; + ay = by; + az = bz; + bx = ima + imb + imc; + by = ime + imf + img; + bz = imi + imj + imk; + rightPlaneX = bz*ay - by*az; + rightPlaneY = bx*az - bz*ax; + rightPlaneZ = by*ax - bx*ay; + rightPlaneOffset = imd*rightPlaneX + imh*rightPlaneY + iml*rightPlaneZ; + // Нижняя плоскость + ax = bx; + ay = by; + az = bz; + bx = -ima + imb + imc; + by = -ime + imf + img; + bz = -imi + imj + imk; + bottomPlaneX = bz*ay - by*az; + bottomPlaneY = bx*az - bz*ax; + bottomPlaneZ = by*ax - bx*ay; + bottomPlaneOffset = imd*bottomPlaneX + imh*bottomPlaneY + iml*bottomPlaneZ; + // Левая плоскость + ax = bx; + ay = by; + az = bz; + bx = -ima - imb + imc; + by = -ime - imf + img; + bz = -imi - imj + imk; + leftPlaneX = bz*ay - by*az; + leftPlaneY = bx*az - bz*ax; + leftPlaneZ = by*ax - bx*ay; + leftPlaneOffset = imd*leftPlaneX + imh*leftPlaneY + iml*leftPlaneZ; + } + + private function updateOccluders(camera:Camera3D):void { + for (var i:int = numOccluders; i < camera.numOccluders; i++) { + var occluder:Vertex = null; + for (var cameraOccluder:Vertex = camera.occluders[i]; cameraOccluder != null; cameraOccluder = cameraOccluder.next) { + var newOccluder:Vertex = cameraOccluder.create(); + newOccluder.next = occluder; + occluder = newOccluder; + var ax:Number = ima*cameraOccluder.x + imb*cameraOccluder.y + imc*cameraOccluder.z; + var ay:Number = ime*cameraOccluder.x + imf*cameraOccluder.y + img*cameraOccluder.z; + var az:Number = imi*cameraOccluder.x + imj*cameraOccluder.y + imk*cameraOccluder.z; + var bx:Number = ima*cameraOccluder.u + imb*cameraOccluder.v + imc*cameraOccluder.offset; + var by:Number = ime*cameraOccluder.u + imf*cameraOccluder.v + img*cameraOccluder.offset; + var bz:Number = imi*cameraOccluder.u + imj*cameraOccluder.v + imk*cameraOccluder.offset; + occluder.x = bz*ay - by*az; + occluder.y = bx*az - bz*ax; + occluder.z = by*ax - bx*ay; + occluder.offset = imd*occluder.x + imh*occluder.y + iml*occluder.z; + } + occluders[numOccluders] = occluder; + numOccluders++; + } + } + + private function cullingInContainer(culling:int, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number):int { + if (culling > 0) { + // Отсечение по ниар + if (culling & 1) { + if (nearPlaneX >= 0) if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneY >= 0) if (nearPlaneZ >= 0) { + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMinX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else if (nearPlaneZ >= 0) { + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMaxZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMinZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } else { + if (boundMinX*nearPlaneX + boundMinY*nearPlaneY + boundMinZ*nearPlaneZ <= nearPlaneOffset) return -1; + if (boundMaxX*nearPlaneX + boundMaxY*nearPlaneY + boundMaxZ*nearPlaneZ > nearPlaneOffset) culling &= 62; + } + } + // Отсечение по фар + if (culling & 2) { + if (farPlaneX >= 0) if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneY >= 0) if (farPlaneZ >= 0) { + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMinX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else if (farPlaneZ >= 0) { + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMaxZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMinZ*farPlaneZ > farPlaneOffset) culling &= 61; + } else { + if (boundMinX*farPlaneX + boundMinY*farPlaneY + boundMinZ*farPlaneZ <= farPlaneOffset) return -1; + if (boundMaxX*farPlaneX + boundMaxY*farPlaneY + boundMaxZ*farPlaneZ > farPlaneOffset) culling &= 61; + } + } + // Отсечение по левой стороне + if (culling & 4) { + if (leftPlaneX >= 0) if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneY >= 0) if (leftPlaneZ >= 0) { + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMinX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else if (leftPlaneZ >= 0) { + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMaxZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMinZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } else { + if (boundMinX*leftPlaneX + boundMinY*leftPlaneY + boundMinZ*leftPlaneZ <= leftPlaneOffset) return -1; + if (boundMaxX*leftPlaneX + boundMaxY*leftPlaneY + boundMaxZ*leftPlaneZ > leftPlaneOffset) culling &= 59; + } + } + // Отсечение по правой стороне + if (culling & 8) { + if (rightPlaneX >= 0) if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneY >= 0) if (rightPlaneZ >= 0) { + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMinX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else if (rightPlaneZ >= 0) { + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMaxZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMinZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } else { + if (boundMinX*rightPlaneX + boundMinY*rightPlaneY + boundMinZ*rightPlaneZ <= rightPlaneOffset) return -1; + if (boundMaxX*rightPlaneX + boundMaxY*rightPlaneY + boundMaxZ*rightPlaneZ > rightPlaneOffset) culling &= 55; + } + } + // Отсечение по верхней стороне + if (culling & 16) { + if (topPlaneX >= 0) if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneY >= 0) if (topPlaneZ >= 0) { + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMinX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else if (topPlaneZ >= 0) { + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMaxZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMinZ*topPlaneZ > topPlaneOffset) culling &= 47; + } else { + if (boundMinX*topPlaneX + boundMinY*topPlaneY + boundMinZ*topPlaneZ <= topPlaneOffset) return -1; + if (boundMaxX*topPlaneX + boundMaxY*topPlaneY + boundMaxZ*topPlaneZ > topPlaneOffset) culling &= 47; + } + } + // Отсечение по нижней стороне + if (culling & 32) { + if (bottomPlaneX >= 0) if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneY >= 0) if (bottomPlaneZ >= 0) { + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMinX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else if (bottomPlaneZ >= 0) { + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMaxZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMinZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } else { + if (boundMinX*bottomPlaneX + boundMinY*bottomPlaneY + boundMinZ*bottomPlaneZ <= bottomPlaneOffset) return -1; + if (boundMaxX*bottomPlaneX + boundMaxY*bottomPlaneY + boundMaxZ*bottomPlaneZ > bottomPlaneOffset) culling &= 31; + } + } + } + // Отсечение по окклюдерам + for (var i:int = 0; i < numOccluders; i++) { + for (var occluder:Vertex = occluders[i]; occluder != null; occluder = occluder.next) { + if (occluder.x >= 0) if (occluder.y >= 0) if (occluder.z >= 0) { + if (boundMaxX*occluder.x + boundMaxY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMaxX*occluder.x + boundMaxY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (boundMaxX*occluder.x + boundMinY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMaxX*occluder.x + boundMinY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.y >= 0) if (occluder.z >= 0) { + if (boundMinX*occluder.x + boundMaxY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMinX*occluder.x + boundMaxY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (boundMinX*occluder.x + boundMinY*occluder.y + boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (boundMinX*occluder.x + boundMinY*occluder.y + boundMinZ*occluder.z > occluder.offset) break; + } + } + if (occluder == null) return -1; + } + return culling; + } + + private function occludeGeometry(camera:Camera3D, geometry:Geometry):Boolean { + for (var i:int = geometry.numOccluders; i < numOccluders; i++) { + for (var occluder:Vertex = occluders[i]; occluder != null; occluder = occluder.next) { + if (occluder.x >= 0) if (occluder.y >= 0) if (occluder.z >= 0) { + if (geometry.boundMaxX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMaxX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (geometry.boundMaxX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMaxX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.y >= 0) if (occluder.z >= 0) { + if (geometry.boundMinX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMinX*occluder.x + geometry.boundMaxY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } else if (occluder.z >= 0) { + if (geometry.boundMinX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMaxZ*occluder.z > occluder.offset) break; + } else { + if (geometry.boundMinX*occluder.x + geometry.boundMinY*occluder.y + geometry.boundMinZ*occluder.z > occluder.offset) break; + } + } + if (occluder == null) return true; + } + geometry.numOccluders = numOccluders; + return false; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/containers/ZSortContainer.as b/Alternativa3D7/7.4/alternativa/engine3d/containers/ZSortContainer.as new file mode 100644 index 0000000..35a18b2 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/containers/ZSortContainer.as @@ -0,0 +1,64 @@ +package alternativa.engine3d.containers { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + + use namespace alternativa3d; + + /** + * Контейнер, дочерние объекты которого отрисовываются по удалённости от камеры + */ + public class ZSortContainer extends Object3DContainer { + + static private const sortingStack:Vector. = new Vector.(); + + override protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + var i:int; + var j:int; + var l:int = 0; + var r:int = numVisibleChildren - 1; + var child:Object3D; + var stackIndex:int; + var left:Number; + var median:Number; + var right:Number; + sortingStack[0] = l; + sortingStack[1] = r; + stackIndex = 2; + while (stackIndex > 0) { + r = sortingStack[--stackIndex]; + l = sortingStack[--stackIndex]; + j = r; + i = l; + child = visibleChildren[(r + l) >> 1]; + median = child.ml; + do { + while ((left = (visibleChildren[i] as Object3D).ml) > median) i++; + while ((right = (visibleChildren[j] as Object3D).ml) < median) j--; + if (i <= j) { + child = visibleChildren[i]; + visibleChildren[i++] = visibleChildren[j]; + visibleChildren[j--] = child; + } + } while (i <= j); + if (l < j) { + sortingStack[stackIndex++] = l; + sortingStack[stackIndex++] = j; + } + if (i < r) { + sortingStack[stackIndex++] = i; + sortingStack[stackIndex++] = r; + } + } + // Отрисовка + for (i = numVisibleChildren - 1; i >= 0; i--) { + child = visibleChildren[i]; + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/controllers/SimpleObjectController.as b/Alternativa3D7/7.4/alternativa/engine3d/controllers/SimpleObjectController.as new file mode 100644 index 0000000..f930fdc --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/controllers/SimpleObjectController.as @@ -0,0 +1,461 @@ +package alternativa.engine3d.controllers { + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + + import flash.display.InteractiveObject; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Vector3D; + import flash.ui.Keyboard; + import flash.utils.getTimer; + + /** + * + */ + public class SimpleObjectController { + + /** + * Имя действия для привязки клавиш движения вперёд. + */ + public static const ACTION_FORWARD:String = "ACTION_FORWARD"; + /** + * Имя действия для привязки клавиш движения назад. + */ + public static const ACTION_BACK:String = "ACTION_BACK"; + /** + * Имя действия для привязки клавиш движения влево. + */ + public static const ACTION_LEFT:String = "ACTION_LEFT"; + /** + * Имя действия для привязки клавиш движения вправо. + */ + public static const ACTION_RIGHT:String = "ACTION_RIGHT"; + /** + * Имя действия для привязки клавиш движения вверх. + */ + public static const ACTION_UP:String = "ACTION_UP"; + /** + * Имя действия для привязки клавиш движения вниз. + */ + public static const ACTION_DOWN:String = "ACTION_DOWN"; + /** + * Имя действия для привязки клавиш поворота вверх. + */ + public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP"; + /** + * Имя действия для привязки клавиш поворота вниз. + */ + public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN"; + /** + * Имя действия для привязки клавиш поворота налево. + */ + public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT"; + /** + * Имя действия для привязки клавиш поворота направо. + */ + public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT"; + /** + * Имя действия для привязки клавиш увеличения скорости. + */ + public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE"; + /** + * Имя действия для привязки клавиш активации обзора мышью. + */ + public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK"; + + + public var speed:Number; + public var speedMultiplier:Number; + public var mouseSensitivity:Number; + public var maxPitch:Number = 1e+22; + public var minPitch:Number = -1e+22; + + private var eventSource:InteractiveObject; + private var _object:Object3D; + + private var _up:Boolean; + private var _down:Boolean; + private var _forward:Boolean; + private var _back:Boolean; + private var _left:Boolean; + private var _right:Boolean; + private var _accelerate:Boolean; + + private var displacement:Vector3D = new Vector3D(); + private var mousePoint:Point = new Point(); + private var mouseLook:Boolean; + private var objectTransform:Vector.; + + private var time:int; + + /** + * Ассоциативный массив, связывающий имена команд с реализующими их функциями. Функции должны иметь вид + * function(value:Boolean):void. Значение параметра value указывает, нажата или отпущена соответствующая команде + * клавиша. + */ + private var actionBindings:Object = {}; + /** + * Ассоциативный массив, связывающий коды клавиатурных клавиш с именами команд. + */ + protected var keyBindings:Object = {}; + + /** + * + * @param eventSource источник событий для контроллера + * @param speed скорость поступательного перемещения объекта + * @param mouseSensitivity чувствительность мыши - количество градусов поворота на один пиксель перемещения мыши + */ + public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) { + this.eventSource = eventSource; + this.object = object; + this.speed = speed; + this.speedMultiplier = speedMultiplier; + this.mouseSensitivity = mouseSensitivity; + + actionBindings[ACTION_FORWARD] = moveForward; + actionBindings[ACTION_BACK] = moveBack; + actionBindings[ACTION_LEFT] = moveLeft; + actionBindings[ACTION_RIGHT] = moveRight; + actionBindings[ACTION_UP] = moveUp; + actionBindings[ACTION_DOWN] = moveDown; + actionBindings[ACTION_ACCELERATE] = accelerate; + + setDefaultBindings(); + + enable(); + } + + /** + * Активирует контроллер. + */ + public function enable():void { + eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); + } + + /** + * Деактивирует контроллер. + */ + public function disable():void { + eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey); + eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); + stopMouseLook(); + } + + /** + * + */ + private function onMouseDown(e:MouseEvent):void { + startMouseLook(); + } + + /** + * + */ + private function onMouseUp(e:MouseEvent):void { + stopMouseLook(); + } + + /** + * Включает режим взгляда мышью. + */ + public function startMouseLook():void { + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + mouseLook = true; + } + + /** + * Отключает режим взгляда мышью. + */ + public function stopMouseLook():void { + mouseLook = false; + } + + /** + * + */ + private function onKey(e:KeyboardEvent):void { + var method:Function = keyBindings[e.keyCode]; + if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN); + } + + /** + * Управляемый объект. + */ + public function set object(value:Object3D):void { + _object = value; + updateObjectTransform(); + } + + /** + * + */ + public function get object():Object3D { + return _object; + } + + /** + * Обновляет инофрмацию о трансформации объекта. Метод следует вызывать после изменения матрицы объекта вне контролллера. + */ + public function updateObjectTransform():void { + if (_object != null) objectTransform = _object.getMatrix().decompose(); + } + + /** + * Вычисляет новое положение объекта, используя внутренний счётчик времени. + */ + public function update():void { + if (_object == null) return; + + var frameTime:Number = time; + time = getTimer(); + frameTime = 0.001*(time - frameTime); + if (frameTime > 0.1) frameTime = 0.1; + + var moved:Boolean = false; + + if (mouseLook) { + var dx:Number = eventSource.mouseX - mousePoint.x; + var dy:Number = eventSource.mouseY - mousePoint.y; + mousePoint.x = eventSource.mouseX; + mousePoint.y = eventSource.mouseY; + var v:Vector3D = objectTransform[1]; + v.x -= dy*Math.PI/180*mouseSensitivity; + if (v.x > maxPitch) v.x = maxPitch; + if (v.x < minPitch) v.x = minPitch; + v.z -= dx*Math.PI/180*mouseSensitivity; + moved = true; + } + + displacement.x = _right ? 1 : (_left ? -1 : 0); + displacement.y = _forward ? 1 : (_back ? -1 : 0); + displacement.z = _up ? 1 : (_down ? -1 : 0); + if (displacement.lengthSquared > 0) { + if (_object is Camera3D) { + var tmp:Number = displacement.z; + displacement.z = displacement.y; + displacement.y = -tmp; + } + deltaTransformVector(displacement); + if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length); + else displacement.scaleBy(speed*frameTime/displacement.length); + (objectTransform[0] as Vector3D).incrementBy(displacement); + moved = true; + } + + if (moved) { + var m:Matrix3D = new Matrix3D(); + m.recompose(objectTransform); + _object.setMatrix(m); + } + } + + /** + * + * @param pos + */ + public function setObjectPos(pos:Vector3D):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = pos.x; + v.y = pos.y; + v.z = pos.z; + } + } + + /** + * + * @param x + * @param y + * @param z + */ + public function setObjectPosXYZ(x:Number, y:Number, z:Number):void { + if (_object != null) { + var v:Vector3D = objectTransform[0]; + v.x = x; + v.y = y; + v.z = z; + } + } + + /** + * + * @param point + */ + public function lookAt(point:Vector3D):void { + lookAtXYZ(point.x, point.y, point.z); + } + + /** + * + * @param x + * @param y + * @param z + */ + public function lookAtXYZ(x:Number, y:Number, z:Number):void { + if (_object == null) return; + var v:Vector3D = objectTransform[0]; + var dx:Number = x - v.x; + var dy:Number = y - v.y; + var dz:Number = z - v.z; + v = objectTransform[1]; + v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy)); + if (_object is Camera3D) v.x -= 0.5*Math.PI; + v.y = 0; + v.z = -Math.atan2(dx, dy); + var m:Matrix3D = _object.getMatrix(); + m.recompose(objectTransform); + _object.setMatrix(m); + } + + private var _vin:Vector. = new Vector.(3); + private var _vout:Vector. = new Vector.(3); + + private function deltaTransformVector(v:Vector3D):void { + _vin[0] = v.x; + _vin[1] = v.y; + _vin[2] = v.z; + _object.getMatrix().transformVectors(_vin, _vout); + var c:Vector3D = objectTransform[0]; + v.x = _vout[0] - c.x; + v.y = _vout[1] - c.y; + v.z = _vout[2] - c.z; + } + + /** + * Активация движения вперёд. + * + * @param value true для начала движения, false для окончания + */ + public function moveForward(value:Boolean):void { + _forward = value; + } + + /** + * Активация движения назад. + * + * @param value true для начала движения, false для окончания + */ + public function moveBack(value:Boolean):void { + _back = value; + } + + /** + * Активация движения влево. + * + * @param value true для начала движения, false для окончания + */ + public function moveLeft(value:Boolean):void { + _left = value; + } + + /** + * Активация движения вправо. + * + * @param value true для начала движения, false для окончания + */ + public function moveRight(value:Boolean):void { + _right = value; + } + + /** + * Активация движения вверх. + * + * @param value true для начала движения, false для окончания + */ + public function moveUp(value:Boolean):void { + _up = value; + } + + /** + * Активация движения вниз. + * + * @param value true для начала движения, false для окончания + */ + public function moveDown(value:Boolean):void { + _down = value; + } + + /** + * Активация режима увеличенной скорости. + * + * @param value true для включения ускорения, false для выключения + */ + public function accelerate(value:Boolean):void { + _accelerate = value; + } + + /** + * Метод выполняет привязку клавиши к действию. Одной клавише может быть назначено только одно действие. + * + * @param keyCode код клавиши + * @param action наименование действия + * + * @see #unbindKey() + * @see #unbindAll() + */ + public function bindKey(keyCode:uint, action:String):void { + var method:Function = actionBindings[action]; + if (method != null) keyBindings[keyCode] = method; + } + + /** + * + */ + public function bindKeys(bindings:Array):void { + for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]); + } + + /** + * Очистка привязки клавиши. + * + * @param keyCode код клавиши + * + * @see #bindKey() + * @see #unbindAll() + */ + public function unbindKey(keyCode:uint):void { + delete keyBindings[keyCode]; + } + + /** + * Очистка привязки всех клавиш. + * + * @see #bindKey() + * @see #unbindKey() + */ + public function unbindAll():void { + for (var key:String in keyBindings) delete keyBindings[key]; + } + + /** + * Метод устанавливает привязки клавиш по умолчанию. Реализация по умолчанию не делает ничего. + * + * @see #bindKey() + * @see #unbindKey() + * @see #unbindAll() + */ + public function setDefaultBindings():void { + bindKey(87, ACTION_FORWARD); + bindKey(83, ACTION_BACK); + bindKey(65, ACTION_LEFT); + bindKey(68, ACTION_RIGHT); + bindKey(69, ACTION_UP); + bindKey(67, ACTION_DOWN); + bindKey(Keyboard.SHIFT, ACTION_ACCELERATE); + + bindKey(Keyboard.UP, ACTION_FORWARD); + bindKey(Keyboard.DOWN, ACTION_BACK); + bindKey(Keyboard.LEFT, ACTION_LEFT); + bindKey(Keyboard.RIGHT, ACTION_RIGHT); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Camera3D.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Camera3D.as new file mode 100644 index 0000000..06aa47a --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Camera3D.as @@ -0,0 +1,608 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.events.Event; + import flash.geom.Matrix3D; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.geom.Vector3D; + import flash.system.System; + import flash.text.TextField; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + import flash.utils.getTimer; + + use namespace alternativa3d; + + public class Camera3D extends Object3D { + + /** + * Вьюпорт + */ + public var view:View; + /** + * Поле зрения в радианах. + */ + public var fov:Number = Math.PI/2; + + public var nearClipping:Number = 0; + public var farClipping:Number = 5000; + + // Параметры перспективы + alternativa3d var viewSizeX:Number; + alternativa3d var viewSizeY:Number; + alternativa3d var focalLength:Number; + + // Перекрытия + alternativa3d var occluders:Vector. = new Vector.(); + alternativa3d var numOccluders:int; + alternativa3d var occludedAll:Boolean; + + alternativa3d var numDraws:int; + alternativa3d var numPolygons:int; + alternativa3d var numTriangles:int; + + /** + * + * @param fov + * @param nearClipping + * @param farClipping + */ + public function Camera3D(fov:Number = 1.5707963267948966, nearClipping:Number = 0, farClipping:Number = 5000) { + this.fov = fov; + this.nearClipping = nearClipping; + this.farClipping = farClipping; + } + + /** + * Отрисовка иерархии объектов, в которой находится камера. + * Перед render(), если менялись параметры камеры, нужно вызвать updateProjection(). + */ + public function render():void { + if (view != null) { + // Расчёт параметров проецирования + viewSizeX = view._width*0.5; + viewSizeY = view._height*0.5; + focalLength = Math.sqrt(viewSizeX*viewSizeX + viewSizeY*viewSizeY)/Math.tan(fov*0.5); + // Расчёт матрицы перевода из глобального пространства в камеру + composeMatrix(); + var root:Object3D = this; + while (root._parent != null) { + root = root._parent; + root.composeMatrix(); + appendMatrix(root); + } + invertMatrix(); + // Сброс окклюдеров + numOccluders = 0; + occludedAll = false; + // Сброс счётчиков + numDraws = 0; + numPolygons = 0; + numTriangles = 0; + // Сброс отрисовок + view.numDraws = 0; + // Отрисовка + if (root != this && root.visible) { + root.appendMatrix(this); + if (root.cullingInCamera(this, root, 63) >= 0) { + root.draw(this, root, view); + // Отложенное удаление вершин и граней в коллектор + deferredDestroy(); + // Зачистка окклюдеров + clearOccluders(); + } + } + // Зачистка ненужных канвасов + view.removeChildren(view.numDraws); + // Обработка интерактивности после рендера + if (view._interactive) { + view.onMouseMove(); + } + } + } + + override alternativa3d function composeMatrix():void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX*viewSizeX/focalLength; + var sinXscaleY:Number = sinX*scaleY*viewSizeY/focalLength; + var cosXscaleY:Number = cosX*scaleY*viewSizeY/focalLength; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + ma = cosZ*cosYscaleX; + mb = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + mc = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + md = x; + me = sinZ*cosYscaleX; + mf = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + mg = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + mh = y; + mi = -sinY*scaleX; + mj = cosY*sinXscaleY; + mk = cosY*cosXscaleZ; + ml = z; + } + + private function invertMatrix():void { + var a:Number = ma; + var b:Number = mb; + var c:Number = mc; + var d:Number = md; + var e:Number = me; + var f:Number = mf; + var g:Number = mg; + var h:Number = mh; + var i:Number = mi; + var j:Number = mj; + var k:Number = mk; + var l:Number = ml; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + ma = (-g*j + f*k)*det; + mb = (c*j - b*k)*det; + mc = (-c*f + b*g)*det; + md = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + me = (g*i - e*k)*det; + mf = (-c*i + a*k)*det; + mg = (c*e - a*g)*det; + mh = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + mi = (-f*i + e*j)*det; + mj = (b*i - a*j)*det; + mk = (-b*e + a*f)*det; + ml = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + } + + /** + * @param v + * @param result + */ + public function projectGlobal(v:Vector3D, result:Vector3D):void { + composeMatrix(); + var root:Object3D = this; + while (root._parent != null) { + root = root._parent; + root.composeMatrix(); + appendMatrix(root); + } + invertMatrix(); + var x:Number = ma*v.x + mb*v.y + mc*v.z + md; + var y:Number = me*v.x + mf*v.y + mg*v.z + mh; + var z:Number = mi*v.x + mj*v.y + mk*v.z + ml; + result.x = x*viewSizeX/z + view._width/2; + result.y = y*viewSizeY/z + view._height/2; + result.z = z; + } + + // DEBUG + + // Режим отладки + public var debug:Boolean = false; + + // Список объектов дебага + private var debugSet:Object = new Object(); + + // Добавить в дебаг + public function addToDebug(debug:int, ... rest):void { + if (!debugSet[debug]) debugSet[debug] = new Dictionary(); + for (var i:int = 0; i < rest.length;) debugSet[debug][rest[i++]] = true; + } + + // Убрать из дебага + public function removeFromDebug(debug:int, ... rest):void { + if (debugSet[debug]) { + for (var i:int = 0; i < rest.length;) delete debugSet[debug][rest[i++]]; + var key:*; + for (key in debugSet[debug]) break; + if (!key) delete debugSet[debug]; + } + } + + // Проверка, находится ли объект или один из классов, от которых он нследован, в дебаге + alternativa3d function checkInDebug(object:Object3D):int { + var res:int = 0; + for (var debug:int = 1; debug <= 512; debug <<= 1) { + if (debugSet[debug]) { + if (debugSet[debug][Object3D] || debugSet[debug][object]) { + res |= debug; + } else { + var objectClass:Class = getDefinitionByName(getQualifiedClassName(object)) as Class; + while (objectClass != Object3D) { + if (debugSet[debug][objectClass]) { + res |= debug; + break; + } + objectClass = Class(getDefinitionByName(getQualifiedSuperclassName(objectClass))); + } + } + } + } + return res; + } + + public var diagram:Sprite = createDiagram(); + + private var fpsTextField:TextField; + private var memoryTextField:TextField; + private var drawsTextField:TextField; + private var polygonsTextField:TextField; + private var trianglesTextField:TextField; + private var timerTextField:TextField; + private var graph:Bitmap; + private var rect:Rectangle; + + private var _diagramAlign:String = "TR"; + private var _diagramHorizontalMargin:Number = 2; + private var _diagramVerticalMargin:Number = 2; + + public var fpsUpdatePeriod:int = 10; + private var fpsUpdateCounter:int; + private var previousFrameTime:int; + private var previousPeriodTime:int; + + private var maxMemory:int; + + public var timerUpdatePeriod:int = 10; + private var timerUpdateCounter:int; + private var timeSum:int; + private var timeCount:int; + private var timer:int; + + private function createDiagram():Sprite { + var diagram:Sprite = new Sprite(); + diagram.mouseEnabled = false; + diagram.mouseChildren = false; + // Инициализация диаграммы + diagram.addEventListener(Event.ADDED_TO_STAGE, function():void { + // FPS + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.LEFT; + fpsTextField.text = "FPS:"; + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + diagram.addChild(fpsTextField); + fpsTextField = new TextField(); + fpsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCCCC); + fpsTextField.autoSize = TextFieldAutoSize.RIGHT; + fpsTextField.text = Number(diagram.stage.frameRate).toFixed(2); + fpsTextField.selectable = false; + fpsTextField.x = -3; + fpsTextField.y = -5; + fpsTextField.width = 65; + diagram.addChild(fpsTextField); + // Время выполнения метода + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.LEFT; + timerTextField.text = "MS:"; + timerTextField.selectable = false; + timerTextField.x = -3; + timerTextField.y = 4; + diagram.addChild(timerTextField); + timerTextField = new TextField(); + timerTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x0066FF); + timerTextField.autoSize = TextFieldAutoSize.RIGHT; + timerTextField.text = ""; + timerTextField.selectable = false; + timerTextField.x = -3; + timerTextField.y = 4; + timerTextField.width = 65; + diagram.addChild(timerTextField); + // Память + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.LEFT; + memoryTextField.text = "MEM:"; + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 13; + diagram.addChild(memoryTextField); + memoryTextField = new TextField(); + memoryTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xCCCC00); + memoryTextField.autoSize = TextFieldAutoSize.RIGHT; + memoryTextField.text = bytesToString(System.totalMemory); + memoryTextField.selectable = false; + memoryTextField.x = -3; + memoryTextField.y = 13; + memoryTextField.width = 65; + diagram.addChild(memoryTextField); + // Отрисовочные вызовы + drawsTextField = new TextField(); + drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00); + drawsTextField.autoSize = TextFieldAutoSize.LEFT; + drawsTextField.text = "DRW:"; + drawsTextField.selectable = false; + drawsTextField.x = -3; + drawsTextField.y = 22; + diagram.addChild(drawsTextField); + drawsTextField = new TextField(); + drawsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0x00CC00); + drawsTextField.autoSize = TextFieldAutoSize.RIGHT; + drawsTextField.text = "0"; + drawsTextField.selectable = false; + drawsTextField.x = -3; + drawsTextField.y = 22; + drawsTextField.width = 52; + diagram.addChild(drawsTextField); + // Полигоны + polygonsTextField = new TextField(); + polygonsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF0033); + polygonsTextField.autoSize = TextFieldAutoSize.LEFT; + polygonsTextField.text = "PLG:"; + polygonsTextField.selectable = false; + polygonsTextField.x = -3; + polygonsTextField.y = 31; + diagram.addChild(polygonsTextField); + polygonsTextField = new TextField(); + polygonsTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF0033); + polygonsTextField.autoSize = TextFieldAutoSize.RIGHT; + polygonsTextField.text = "0"; + polygonsTextField.selectable = false; + polygonsTextField.x = -3; + polygonsTextField.y = 31; + polygonsTextField.width = 52; + diagram.addChild(polygonsTextField); + // Треугольники + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.LEFT; + trianglesTextField.text = "TRI:"; + trianglesTextField.selectable = false; + trianglesTextField.x = -3; + trianglesTextField.y = 40; + diagram.addChild(trianglesTextField); + trianglesTextField = new TextField(); + trianglesTextField.defaultTextFormat = new TextFormat("Tahoma", 10, 0xFF6600); + trianglesTextField.autoSize = TextFieldAutoSize.RIGHT; + trianglesTextField.text = "0"; + trianglesTextField.selectable = false; + trianglesTextField.x = -3; + trianglesTextField.y = 40; + trianglesTextField.width = 52; + diagram.addChild(trianglesTextField); + // График + graph = new Bitmap(new BitmapData(60, 40, true, 0x20FFFFFF)); + rect = new Rectangle(0, 0, 1, 40); + graph.x = 0; + graph.y = 54; + diagram.addChild(graph); + // Сброс параметров + previousPeriodTime = getTimer(); + previousFrameTime = previousPeriodTime; + fpsUpdateCounter = 0; + maxMemory = 0; + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + // Подписка + diagram.stage.addEventListener(Event.ENTER_FRAME, updateDiagram, false, -1000); + diagram.stage.addEventListener(Event.RESIZE, resizeDiagram, false, -1000); + resizeDiagram(); + }); + // Деинициализация диаграммы + diagram.addEventListener(Event.REMOVED_FROM_STAGE, function():void { + // Обнуление + diagram.removeChild(fpsTextField); + diagram.removeChild(memoryTextField); + diagram.removeChild(drawsTextField); + diagram.removeChild(polygonsTextField); + diagram.removeChild(trianglesTextField); + diagram.removeChild(timerTextField); + diagram.removeChild(graph); + fpsTextField = null; + memoryTextField = null; + drawsTextField = null; + polygonsTextField = null; + trianglesTextField = null; + timerTextField = null; + graph.bitmapData.dispose(); + graph = null; + rect = null; + // Отписка + diagram.stage.removeEventListener(Event.ENTER_FRAME, updateDiagram); + diagram.stage.removeEventListener(Event.RESIZE, resizeDiagram); + }); + return diagram; + } + + private function resizeDiagram(e:Event = null):void { + if (diagram.stage != null) { + var coord:Point = diagram.parent.globalToLocal(new Point()); + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.BOTTOM_LEFT) { + diagram.x = Math.round(coord.x + _diagramHorizontalMargin); + } + if (_diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.BOTTOM) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth/2 - graph.width/2); + } + if (_diagramAlign == StageAlign.TOP_RIGHT || _diagramAlign == StageAlign.RIGHT || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.x = Math.round(coord.x + diagram.stage.stageWidth - _diagramHorizontalMargin - graph.width); + } + if (_diagramAlign == StageAlign.TOP_LEFT || _diagramAlign == StageAlign.TOP || _diagramAlign == StageAlign.TOP_RIGHT) { + diagram.y = Math.round(coord.y + _diagramVerticalMargin); + } + if (_diagramAlign == StageAlign.LEFT || _diagramAlign == StageAlign.RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight/2 - (graph.y + graph.height)/2); + } + if (_diagramAlign == StageAlign.BOTTOM_LEFT || _diagramAlign == StageAlign.BOTTOM || _diagramAlign == StageAlign.BOTTOM_RIGHT) { + diagram.y = Math.round(coord.y + diagram.stage.stageHeight - _diagramVerticalMargin - graph.y - graph.height); + } + } + } + + private function updateDiagram(e:Event):void { + var value:Number; + var mod:int; + var time:int = getTimer(); + var stageFrameRate:int = diagram.stage.frameRate; + + // FPS текст + if (++fpsUpdateCounter == fpsUpdatePeriod) { + value = 1000*fpsUpdatePeriod/(time - previousPeriodTime); + if (value > stageFrameRate) value = stageFrameRate; + mod = value*100 % 100; + fpsTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + previousPeriodTime = time; + fpsUpdateCounter = 0; + } + // FPS график + value = 1000/(time - previousFrameTime); + if (value > stageFrameRate) value = stageFrameRate; + graph.bitmapData.scroll(1, 0); + graph.bitmapData.fillRect(rect, 0x20FFFFFF); + graph.bitmapData.setPixel32(0, 40*(1 - value/stageFrameRate), 0xFFCCCCCC); + previousFrameTime = time; + + // Время текст + if (++timerUpdateCounter == timerUpdatePeriod) { + if (timeCount > 0) { + value = timeSum/timeCount; + mod = value*100 % 100; + timerTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + } else { + timerTextField.text = ""; + } + timerUpdateCounter = 0; + timeSum = 0; + timeCount = 0; + } + + // Память текст + var memory:int = System.totalMemory; + value = memory/1048576; + mod = value*100 % 100; + memoryTextField.text = int(value) + "." + ((mod >= 10) ? mod : ((mod > 0) ? ("0" + mod) : "00")); + + // Память график + if (memory > maxMemory) maxMemory = memory; + graph.bitmapData.setPixel32(0, 40*(1 - memory/maxMemory), 0xFFCCCC00); + + // Отрисовочные вызовы текст + drawsTextField.text = String(numDraws); + + // Полигоны текст + polygonsTextField.text = String(numPolygons); + + // Треугольники текст + trianglesTextField.text = String(numTriangles); + } + + public function startTimer():void { + timer = getTimer(); + } + + public function stopTimer():void { + timeSum += getTimer() - timer; + timeCount++; + } + + public function get diagramAlign():String { + return _diagramAlign; + } + + public function set diagramAlign(value:String):void { + _diagramAlign = value; + resizeDiagram(); + } + + public function get diagramHorizontalMargin():Number { + return _diagramHorizontalMargin; + } + + public function set diagramHorizontalMargin(value:Number):void { + _diagramHorizontalMargin = value; + resizeDiagram(); + } + + public function get diagramVerticalMargin():Number { + return _diagramVerticalMargin; + } + + public function set diagramVerticalMargin(value:Number):void { + _diagramVerticalMargin = value; + resizeDiagram(); + } + + private function bytesToString(bytes:int):String { + if (bytes < 1024) return bytes + "b"; + else if (bytes < 10240) return (bytes/1024).toFixed(2) + "kb"; + else if (bytes < 102400) return (bytes/1024).toFixed(1) + "kb"; + else if (bytes < 1048576) return (bytes >> 10) + "kb"; + else if (bytes < 10485760) return (bytes/1048576).toFixed(2);// + "mb"; + else if (bytes < 104857600) return (bytes/1048576).toFixed(1);// + "mb"; + else return String(bytes >> 20);// + "mb"; + } + + // Отложенное удаление в коллектор + + private var firstVertex:Vertex = new Vertex(); + private var firstFace:Face = new Face(); + private var firstWrapper:Wrapper = new Wrapper(); + alternativa3d var lastWrapper:Wrapper = firstWrapper; + alternativa3d var lastVertex:Vertex = firstVertex; + alternativa3d var lastFace:Face = firstFace; + + private function deferredDestroy():void { + for (var face:Face = firstFace.next; face != null; face = face.next) { + var w:Wrapper = face.wrapper; + if (w != null) { + for (var lw:Wrapper = null; w != null; lw = w,w = w.next) { + w.vertex = null; + } + lastWrapper.next = face.wrapper; + lastWrapper = lw; + } + face.material = null; + face.wrapper = null; + //face.processNext = null; + //face.geometry = null; + } + if (firstFace != lastFace) { + lastFace.next = Face.collector; + Face.collector = firstFace.next; + firstFace.next = null; + lastFace = firstFace; + } + if (firstWrapper != lastWrapper) { + lastWrapper.next = Wrapper.collector; + Wrapper.collector = firstWrapper.next; + firstWrapper.next = null; + lastWrapper = firstWrapper; + } + if (firstVertex != lastVertex) { + lastVertex.next = Vertex.collector; + Vertex.collector = firstVertex.next; + firstVertex.next = null; + lastVertex = firstVertex; + } + } + + alternativa3d function clearOccluders():void { + for (var i:int = 0; i < numOccluders; i++) { + var first:Vertex = occluders[i]; + //for (var last:Vertex = first; last.next != null; last = last.next); + var last:Vertex = first; + while (last.next != null) last = last.next; + last.next = Vertex.collector; + Vertex.collector = first; + occluders[i] = null; + } + numOccluders = 0; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Canvas.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Canvas.as new file mode 100644 index 0000000..9366221 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Canvas.as @@ -0,0 +1,108 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.display.Graphics; + import flash.display.Sprite; + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Canvas extends Sprite { + + static private const defaultColorTransform:ColorTransform = new ColorTransform(); + static private const collector:Vector. = new Vector.(); + static private var collectorLength:int = 0; + + alternativa3d var gfx:Graphics = graphics; + + private var modifiedGraphics:Boolean; + private var modifiedAlpha:Boolean; + private var modifiedBlendMode:Boolean; + private var modifiedColorTransform:Boolean; + private var modifiedFilters:Boolean; + + alternativa3d var _numChildren:int = 0; + alternativa3d var numDraws:int = 0; + + alternativa3d var interactiveObject:Object3D; + + /*public function Canvas() { + mouseEnabled = false; + mouseChildren = false; + }*/ + + alternativa3d function getChildCanvas(interactiveObject:Object3D, useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas; + var displayObject:DisplayObject; + // Зачистка не канвасов + while (_numChildren > numDraws && !((displayObject = getChildAt(_numChildren - 1 - numDraws)) is Canvas)) { + removeChild(displayObject); + _numChildren--; + } + // Получение канваса + if (_numChildren > numDraws++) { + canvas = displayObject as Canvas; + // Зачистка + if (canvas.modifiedGraphics) { + canvas.gfx.clear(); + } + if (canvas._numChildren > 0 && !useChildren) { + canvas.removeChildren(0); + } + } else { + canvas = (collectorLength > 0) ? collector[--collectorLength] : new Canvas(); + addChildAt(canvas, 0); + _numChildren++; + } + // Сохранение интерактивного объекта + canvas.interactiveObject = interactiveObject; + // Пометка о том, что в graphics будет что-то нарисовано + canvas.modifiedGraphics = useGraphics; + // Установка свойств + if (alpha != 1) { + canvas.alpha = alpha; + canvas.modifiedAlpha = true; + } else if (canvas.modifiedAlpha) { + canvas.alpha = 1; + canvas.modifiedAlpha = false; + } + if (blendMode != "normal") { + canvas.blendMode = blendMode; + canvas.modifiedBlendMode = true; + } else if (canvas.modifiedBlendMode) { + canvas.blendMode = "normal"; + canvas.modifiedBlendMode = false; + } + if (colorTransform != null) { + colorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = colorTransform; + canvas.modifiedColorTransform = true; + } else if (canvas.modifiedColorTransform) { + defaultColorTransform.alphaMultiplier = alpha; + canvas.transform.colorTransform = defaultColorTransform; + canvas.modifiedColorTransform = false; + } + if (filters != null) { + canvas.filters = filters; + canvas.modifiedFilters = true; + } else if (canvas.modifiedFilters) { + canvas.filters = null; + canvas.modifiedFilters = false; + } + return canvas; + } + + alternativa3d function removeChildren(keep:int):void { + for (var canvas:Canvas; _numChildren > keep; _numChildren--) { + if ((canvas = removeChildAt(0) as Canvas) != null) { + canvas.interactiveObject = null; + if (canvas.modifiedGraphics) canvas.gfx.clear(); + if (canvas._numChildren > 0) canvas.removeChildren(0); + collector[collectorLength++] = canvas; + } + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Clipping.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Clipping.as new file mode 100644 index 0000000..573bc32 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Clipping.as @@ -0,0 +1,19 @@ +package alternativa.engine3d.core { + + public class Clipping { + + /** + * Объект отсекается целиком, если он полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const BOUND_CULLING:int = 0; + /** + * Грань отсекается целиком, если она полностью вне пирамиды видимости или пересекает nearClipping камеры. + */ + static public const FACE_CULLING:int = 1; + /** + * Грань подрезается пирамидой видимости камеры. + */ + static public const FACE_CLIPPING:int = 2; + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Debug.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Debug.as new file mode 100644 index 0000000..5af6f97 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Debug.as @@ -0,0 +1,190 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Debug { + + static public const NAMES:int = 1; + static public const AXES:int = 2; + static public const CENTERS:int = 4; + static public const BOUNDS:int = 8; + + static public const EDGES:int = 16; + static public const VERTICES:int = 32; + static public const NORMALS:int = 64; + + static public const NODES:int = 128; + static public const SPLITS:int = 256; + static public const BONES:int = 512; + + static alternativa3d function drawEdges(camera:Camera3D, canvas:Canvas, list:Face, color:int):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var t:Number; + canvas.gfx.lineStyle(0, color); + for (var face:Face = list; face != null; face = face.processNext) { + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + var x:Number = vertex.cameraX*viewSizeX*t; + var y:Number = vertex.cameraY*viewSizeY*t; + canvas.gfx.moveTo(x, y); + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + t = 1/vertex.cameraZ; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX*t, vertex.cameraY*viewSizeY*t); + } + canvas.gfx.lineTo(x, y); + } + } + + static private const boundVertexList:Vertex = Vertex.createList(8); + + static alternativa3d function drawBounds(camera:Camera3D, canvas:Canvas, object:Object3D, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, color:int = -1):void { + var vertex:Vertex; + // Заполнение + var a:Vertex = boundVertexList; + a.x = boundMinX; + a.y = boundMinY; + a.z = boundMinZ; + var b:Vertex = a.next; + b.x = boundMaxX; + b.y = boundMinY; + b.z = boundMinZ; + var c:Vertex = b.next; + c.x = boundMinX; + c.y = boundMaxY; + c.z = boundMinZ; + var d:Vertex = c.next; + d.x = boundMaxX; + d.y = boundMaxY; + d.z = boundMinZ; + var e:Vertex = d.next; + e.x = boundMinX; + e.y = boundMinY; + e.z = boundMaxZ; + var f:Vertex = e.next; + f.x = boundMaxX; + f.y = boundMinY; + f.z = boundMaxZ; + var g:Vertex = f.next; + g.x = boundMinX; + g.y = boundMaxY; + g.z = boundMaxZ; + var h:Vertex = g.next; + h.x = boundMaxX; + h.y = boundMaxY; + h.z = boundMaxZ; + // Трансформация в камеру + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = object.ma*vertex.x + object.mb*vertex.y + object.mc*vertex.z + object.md; + vertex.cameraY = object.me*vertex.x + object.mf*vertex.y + object.mg*vertex.z + object.mh; + vertex.cameraZ = object.mi*vertex.x + object.mj*vertex.y + object.mk*vertex.z + object.ml; + if (vertex.cameraZ <= 0) return; + } + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Отрисовка + canvas.gfx.lineStyle(0, (color < 0) ? ((object.culling > 0) ? 0xFFFF00 : 0x00FF00) : color); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + canvas.gfx.moveTo(e.cameraX, e.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(e.cameraX, e.cameraY); + canvas.gfx.moveTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(f.cameraX, f.cameraY); + canvas.gfx.moveTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(h.cameraX, h.cameraY); + canvas.gfx.moveTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(g.cameraX, g.cameraY); + } + + static private const nodeVertexList:Vertex = Vertex.createList(4); + + static alternativa3d function drawKDNode(camera:Camera3D, canvas:Canvas, object:Object3D, axis:int, coord:Number, boundMinX:Number, boundMinY:Number, boundMinZ:Number, boundMaxX:Number, boundMaxY:Number, boundMaxZ:Number, alpha:Number):void { + var vertex:Vertex; + // Заполнение + var a:Vertex = nodeVertexList; + var b:Vertex = a.next; + var c:Vertex = b.next; + var d:Vertex = c.next; + if (axis == 0) { + a.x = coord; + a.y = boundMinY; + a.z = boundMaxZ; + b.x = coord; + b.y = boundMaxY; + b.z = boundMaxZ; + c.x = coord; + c.y = boundMaxY; + c.z = boundMinZ; + d.x = coord; + d.y = boundMinY; + d.z = boundMinZ; + } else if (axis == 1) { + a.x = boundMaxX; + a.y = coord; + a.z = boundMaxZ; + b.x = boundMinX; + b.y = coord; + b.z = boundMaxZ; + c.x = boundMinX; + c.y = coord; + c.z = boundMinZ; + d.x = boundMaxX; + d.y = coord; + d.z = boundMinZ; + } else { + a.x = boundMinX; + a.y = boundMinY; + a.z = coord; + b.x = boundMaxX; + b.y = boundMinY; + b.z = coord; + c.x = boundMaxX; + c.y = boundMaxY; + c.z = coord; + d.x = boundMinX; + d.y = boundMaxY; + d.z = coord; + } + // Трансформация в камеру + for (vertex = a; vertex != null; vertex = vertex.next) { + vertex.cameraX = object.ma*vertex.x + object.mb*vertex.y + object.mc*vertex.z + object.md; + vertex.cameraY = object.me*vertex.x + object.mf*vertex.y + object.mg*vertex.z + object.mh; + vertex.cameraZ = object.mi*vertex.x + object.mj*vertex.y + object.mk*vertex.z + object.ml; + if (vertex.cameraZ <= 0) return; + } + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + for (vertex = a; vertex != null; vertex = vertex.next) { + var t:Number = 1/vertex.cameraZ; + vertex.cameraX = vertex.cameraX*viewSizeX*t; + vertex.cameraY = vertex.cameraY*viewSizeY*t; + } + // Отрисовка + canvas.gfx.lineStyle(0, (axis == 0) ? 0xFF0000 : ((axis == 1) ? 0x00FF00 : 0x0000FF), alpha); + canvas.gfx.moveTo(a.cameraX, a.cameraY); + canvas.gfx.lineTo(b.cameraX, b.cameraY); + canvas.gfx.lineTo(c.cameraX, c.cameraY); + canvas.gfx.lineTo(d.cameraX, d.cameraY); + canvas.gfx.lineTo(a.cameraX, a.cameraY); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Face.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Face.as new file mode 100644 index 0000000..d3f7d68 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Face.as @@ -0,0 +1,199 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.materials.Material; + import flash.geom.Vector3D; + import __AS3__.vec.Vector; + + use namespace alternativa3d; + + public class Face { + + public var material:Material; + + alternativa3d var next:Face; + + alternativa3d var negative:Face; + alternativa3d var positive:Face; + + alternativa3d var wrapper:Wrapper; + + alternativa3d var normalX:Number; + alternativa3d var normalY:Number; + alternativa3d var normalZ:Number; + alternativa3d var offset:Number; + + alternativa3d var processNext:Face; + + alternativa3d var distance:Number; + + alternativa3d var geometry:Geometry; + + static alternativa3d var collector:Face; + + static alternativa3d function create():Face { + if (collector != null) { + var res:Face = collector; + collector = res.next; + res.next = null; + /*if (res.processNext != null) trace("!!!processNext!!!"); + if (res.geometry != null) trace("!!!geometry!!!"); + if (res.negative != null) trace("!!!negative!!!"); + if (res.positive != null) trace("!!!positive!!!");*/ + return res; + } else { + //trace("new Face"); + return new Face(); + } + } + + alternativa3d function create():Face { + if (collector != null) { + var res:Face = collector; + collector = res.next; + res.next = null; + /*if (res.processNext != null) trace("!!!processNext!!!"); + if (res.geometry != null) trace("!!!geometry!!!"); + if (res.negative != null) trace("!!!negative!!!"); + if (res.positive != null) trace("!!!positive!!!");*/ + return res; + } else { + //trace("new Face"); + return new Face(); + } + } + + /** + * Расчёт нормали + * @param normalize Флаг нормализации + */ + public function calculateNormal(normalize:Boolean = false):void { + var w:Wrapper = wrapper; + var a:Vertex = w.vertex; w = w.next; + var b:Vertex = w.vertex; w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + normalX = acz*aby - acy*abz; + normalY = acx*abz - acz*abx; + normalZ = acy*abx - acx*aby; + if (normalize) { + var length:Number = normalX*normalX + normalY*normalY + normalZ*normalZ; + if (length > 0.001) { + length = 1/Math.sqrt(length); + normalX *= length; + normalY *= length; + normalZ *= length; + } + } + offset = a.x*normalX + a.y*normalY + a.z*normalZ; + } + + public function get normal():Vector3D { + return new Vector3D(normalX, normalY, normalZ, offset); + } + + public function get vertices():Vector. { + var res:Vector. = new Vector.(); + var len:int = 0; + for (var w:Wrapper = wrapper; w != null; w = w.next) { + res[len] = w.vertex; + len++; + } + return res; + } + + /** + * Выстраивание вершин в лучшую последовательность и расчёт нормали + * @return Если грань не вырождена - true, иначе false + */ + alternativa3d function calculateBestSequenceAndNormal():void { + if (wrapper.next.next.next != null) { + var max:Number = -1e+22; + var s:Wrapper; + var sm:Wrapper; + var sp:Wrapper; + for (w = wrapper; w != null; w = w.next) { + var wn:Wrapper = (w.next != null) ? w.next : wrapper; + var wm:Wrapper = (wn.next != null) ? wn.next : wrapper; + a = w.vertex; + b = wn.vertex; + c = wm.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = nx*nx + ny*ny + nz*nz; + if (nl > max) { + max = nl; + s = w; + } + } + if (s != wrapper) { + //for (sm = wrapper.next.next.next; sm.next != null; sm = sm.next); + sm = wrapper.next.next.next; + while (sm.next != null) sm = sm.next; + //for (sp = wrapper; sp.next != s && sp.next != null; sp = sp.next); + sp = wrapper; + while (sp.next != s && sp.next != null) sp = sp.next; + sm.next = wrapper; + sp.next = null; + wrapper = s; + } + } + var w:Wrapper = wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > 0) { + nl = 1/Math.sqrt(nl); + nx *= nl; + ny *= nl; + nz *= nl; + normalX = nx; + normalY = ny; + normalZ = nz; + } + offset = a.x*nx + a.y*ny + a.z*nz; + } + + /*public function destroy():void { + var w:Wrapper = wrapper; + w.vertex = null; + do { + w = w.next; + w.vertex = null; + } while (w.next != null); + w.next = Wrapper.collector; + Wrapper.collector = wrapper; + material = null; + wrapper = null; + //temporary = false; + processNext = null; + next = collector; + collector = this; + }*/ + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Geometry.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Geometry.as new file mode 100644 index 0000000..2e6653f --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Geometry.as @@ -0,0 +1,1207 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + public class Geometry { + + alternativa3d var next:Geometry; + + alternativa3d var faceStruct:Face; + + private var transformID:int = 0; + + alternativa3d var numOccluders:int; + + alternativa3d var interactiveObject:Object3D; + + // Передаваемые при сплите свойства + alternativa3d var alpha:Number; + alternativa3d var blendMode:String; + alternativa3d var colorTransform:ColorTransform; + alternativa3d var filters:Array; + alternativa3d var sorting:int; + alternativa3d var debug:int = 0; + alternativa3d var boundType:int = 0; + alternativa3d var viewAligned:Boolean = false; + alternativa3d var tma:Number; + alternativa3d var tmb:Number; + alternativa3d var tmc:Number; + alternativa3d var tmd:Number; + alternativa3d var tmtx:Number; + alternativa3d var tmty:Number; + + // Матрица перевода из локальных координат объекта в камеру + alternativa3d var ma:Number; + alternativa3d var mb:Number; + alternativa3d var mc:Number; + alternativa3d var md:Number; + alternativa3d var me:Number; + alternativa3d var mf:Number; + alternativa3d var mg:Number; + alternativa3d var mh:Number; + alternativa3d var mi:Number; + alternativa3d var mj:Number; + alternativa3d var mk:Number; + alternativa3d var ml:Number; + + // Матрица перевода из камеры в локальные координаты объекта + alternativa3d var ima:Number; + alternativa3d var imb:Number; + alternativa3d var imc:Number; + alternativa3d var imd:Number; + alternativa3d var ime:Number; + alternativa3d var imf:Number; + alternativa3d var img:Number; + alternativa3d var imh:Number; + alternativa3d var imi:Number; + alternativa3d var imj:Number; + alternativa3d var imk:Number; + alternativa3d var iml:Number; + + // AABB + alternativa3d var boundMinX:Number; + alternativa3d var boundMinY:Number; + alternativa3d var boundMinZ:Number; + alternativa3d var boundMaxX:Number; + alternativa3d var boundMaxY:Number; + alternativa3d var boundMaxZ:Number; + + // OOBB + alternativa3d var boundVertexList:Vertex = Vertex.createList(8); + alternativa3d var boundPlaneList:Vertex = Vertex.createList(6); + + static private var collector:Geometry; + + static alternativa3d function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + alternativa3d function create():Geometry { + if (collector != null) { + var res:Geometry = collector; + collector = collector.next; + res.next = null; + return res; + } else { + return new Geometry(); + } + } + + alternativa3d function destroy():void { + if (faceStruct != null) { + destroyFaceStruct(faceStruct); + faceStruct = null; + } + interactiveObject = null; + viewAligned = false; + colorTransform = null; + filters = null; + numOccluders = 0; + debug = 0; + transformID = 0; + boundType = 0; + next = collector; + collector = this; + } + + private function destroyFaceStruct(struct:Face):void { + if (struct.negative != null) { + destroyFaceStruct(struct.negative); + struct.negative = null; + } + if (struct.positive != null) { + destroyFaceStruct(struct.positive); + struct.positive = null; + } + for (var next:Face = struct.processNext; next != null; next = struct.processNext) { + struct.processNext = null; + struct = next; + } + } + + alternativa3d function calculateAABB(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + calculateBB(faceStruct, a, b, c, d, e, f, g, h, i, j, k, l); + // Тип баунда + boundType = 1; + } + + alternativa3d function calculateOOBB():void { + if (viewAligned) { + + } else { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + calculateBB(faceStruct, ima, imb, imc, imd, ime, imf, img, imh, imi, imj, imk, iml); + // Костыль + if (boundMaxX - boundMinX < 1) { + boundMaxX = boundMinX + 1; + } + if (boundMaxY - boundMinY < 1) { + boundMaxY = boundMinY + 1; + } + if (boundMaxZ - boundMinZ < 1) { + boundMaxZ = boundMinZ + 1; + } + // Заполнениее вершин баунда + var a:Vertex = boundVertexList; + a.x = boundMinX; + a.y = boundMinY; + a.z = boundMinZ; + var b:Vertex = a.next; + b.x = boundMaxX; + b.y = boundMinY; + b.z = boundMinZ; + var c:Vertex = b.next; + c.x = boundMinX; + c.y = boundMaxY; + c.z = boundMinZ; + var d:Vertex = c.next; + d.x = boundMaxX; + d.y = boundMaxY; + d.z = boundMinZ; + var e:Vertex = d.next; + e.x = boundMinX; + e.y = boundMinY; + e.z = boundMaxZ; + var f:Vertex = e.next; + f.x = boundMaxX; + f.y = boundMinY; + f.z = boundMaxZ; + var g:Vertex = f.next; + g.x = boundMinX; + g.y = boundMaxY; + g.z = boundMaxZ; + var h:Vertex = g.next; + h.x = boundMaxX; + h.y = boundMaxY; + h.z = boundMaxZ; + // Перевод вершин баунда из локальных координат в камеру + for (var vertex:Vertex = a; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = ma*x + mb*y + mc*z + md; + vertex.cameraY = me*x + mf*y + mg*z + mh; + vertex.cameraZ = mi*x + mj*y + mk*z + ml; + } + // Заполнение плоскостей баунда + var front:Vertex = boundPlaneList; + var back:Vertex = front.next; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var acx:Number = e.cameraX - ax; + var acy:Number = e.cameraY - ay; + var acz:Number = e.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + front.cameraX = nx; + front.cameraY = ny; + front.cameraZ = nz; + front.offset = ax*nx + ay*ny + az*nz; + back.cameraX = -nx; + back.cameraY = -ny; + back.cameraZ = -nz; + back.offset = -c.cameraX*nx - c.cameraY*ny - c.cameraZ*nz; + var left:Vertex = back.next; + var right:Vertex = left.next; + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + abx = e.cameraX - ax; + aby = e.cameraY - ay; + abz = e.cameraZ - az; + acx = c.cameraX - ax; + acy = c.cameraY - ay; + acz = c.cameraZ - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + left.cameraX = nx; + left.cameraY = ny; + left.cameraZ = nz; + left.offset = ax*nx + ay*ny + az*nz; + right.cameraX = -nx; + right.cameraY = -ny; + right.cameraZ = -nz; + right.offset = -b.cameraX*nx - b.cameraY*ny - b.cameraZ*nz; + var top:Vertex = right.next; + var bottom:Vertex = top.next; + ax = e.cameraX; + ay = e.cameraY; + az = e.cameraZ; + abx = f.cameraX - ax; + aby = f.cameraY - ay; + abz = f.cameraZ - az; + acx = g.cameraX - ax; + acy = g.cameraY - ay; + acz = g.cameraZ - az; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = 1/Math.sqrt(nx*nx + ny*ny + nz*nz); + nx *= nl; + ny *= nl; + nz *= nl; + top.cameraX = nx; + top.cameraY = ny; + top.cameraZ = nz; + top.offset = ax*nx + ay*ny + az*nz; + bottom.cameraX = -nx; + bottom.cameraY = -ny; + bottom.cameraZ = -nz; + bottom.offset = -a.cameraX*nx - a.cameraY*ny - a.cameraZ*nz; + } + // Тип баунда + boundType = 2; + } + + private function calculateBB(struct:Face, a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + for (var face:Face = struct; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + var cameraX:Number = vertex.cameraX; + var cameraY:Number = vertex.cameraY; + var cameraZ:Number = vertex.cameraZ; + var x:Number = a*cameraX + b*cameraY + c*cameraZ + d; + var y:Number = e*cameraX + f*cameraY + g*cameraZ + h; + var z:Number = i*cameraX + j*cameraY + k*cameraZ + l; + vertex.x = x; + vertex.y = y; + vertex.z = z; + if (x < boundMinX) boundMinX = x; + if (x > boundMaxX) boundMaxX = x; + if (y < boundMinY) boundMinY = y; + if (y > boundMaxY) boundMaxY = y; + if (z < boundMinZ) boundMinZ = z; + if (z > boundMaxZ) boundMaxZ = z; + vertex.transformID = transformID; + } + } + } + if (struct.negative != null) calculateBB(struct.negative, a, b, c, d, e, f, g, h, i, j, k, l); + if (struct.positive != null) calculateBB(struct.positive, a, b, c, d, e, f, g, h, i, j, k, l); + } + + alternativa3d function updateBB(struct:Face):void { + for (var face:Face = struct; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + if (vertex.x < boundMinX) boundMinX = vertex.x; + if (vertex.x > boundMaxX) boundMaxX = vertex.x; + if (vertex.y < boundMinY) boundMinY = vertex.y; + if (vertex.y > boundMaxY) boundMaxY = vertex.y; + if (vertex.z < boundMinZ) boundMinZ = vertex.z; + if (vertex.z > boundMaxZ) boundMaxZ = vertex.z; + vertex.transformID = transformID; + } + } + } + if (struct.negative != null) updateBB(struct.negative); + if (struct.positive != null) updateBB(struct.positive); + } + + // Сам объект с другим набором граней остаётся спереди, его next указывает на негативную часть + alternativa3d function split(camera:Camera3D, planeX:Number, planeY:Number, planeZ:Number, planeOffset:Number, threshold:Number):void { + var result:Face = faceStruct.create(); + splitFaceStruct(camera, faceStruct, result, planeX, planeY, planeZ, planeOffset, planeOffset - threshold, planeOffset + threshold); + // Копирование свойств + if (result.negative != null) { + var negative:Geometry = create(); + next = negative; + negative.faceStruct = result.negative; + result.negative = null; + negative.ma = ma; + negative.mb = mb; + negative.mc = mc; + negative.md = md; + negative.me = me; + negative.mf = mf; + negative.mg = mg; + negative.mh = mh; + negative.mi = mi; + negative.mj = mj; + negative.mk = mk; + negative.ml = ml; + negative.interactiveObject = interactiveObject; + negative.alpha = alpha; + negative.blendMode = blendMode; + negative.colorTransform = colorTransform; + negative.filters = filters; + negative.sorting = sorting; + negative.debug = debug; + negative.boundType = boundType; + negative.viewAligned = viewAligned; + if (viewAligned) { + negative.tma = tma; + negative.tmb = tmb; + negative.tmc = tmc; + negative.tmd = tmd; + negative.tmtx = tmtx; + negative.tmty = tmty; + } else { + negative.ima = ima; + negative.imb = imb; + negative.imc = imc; + negative.imd = imd; + negative.ime = ime; + negative.imf = imf; + negative.img = img; + negative.imh = imh; + negative.imi = imi; + negative.imj = imj; + negative.imk = imk; + negative.iml = iml; + } + negative.boundMinX = 1e+22; + negative.boundMinY = 1e+22; + negative.boundMinZ = 1e+22; + negative.boundMaxX = -1e+22; + negative.boundMaxY = -1e+22; + negative.boundMaxZ = -1e+22; + negative.transformID = transformID + 1; + negative.updateBB(negative.faceStruct); + } else { + next = null; + } + if (result.positive != null) { + faceStruct = result.positive; + result.positive = null; + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + updateBB(faceStruct); + } else { + faceStruct = null; + } + result.next = Face.collector; + Face.collector = result; + } + + // Всегда отсекается негативная часть, поэтому если нужно получить негативную, следует перевернуть плоскость + alternativa3d function crop(camera:Camera3D, planeX:Number, planeY:Number, planeZ:Number, planeOffset:Number, threshold:Number):void { + faceStruct = cropFaceStruct(camera, faceStruct, planeX, planeY, planeZ, planeOffset, planeOffset - threshold, planeOffset + threshold); + if (faceStruct != null) { + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + transformID++; + updateBB(faceStruct); + } + } + + private function splitFaceStruct(camera:Camera3D, struct:Face, result:Face, normalX:Number, normalY:Number, normalZ:Number, offset:Number, offsetMin:Number, offsetMax:Number):void { + var face:Face; + var next:Face; + var w:Wrapper; + var v:Vertex; + // Разделение дочерних нод + var negativeNegative:Face; + var negativePositive:Face; + var positiveNegative:Face; + var positivePositive:Face; + if (struct.negative != null) { + splitFaceStruct(camera, struct.negative, result, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.negative = null; + negativeNegative = result.negative; + negativePositive = result.positive; + } + if (struct.positive != null) { + splitFaceStruct(camera, struct.positive, result, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.positive = null; + positiveNegative = result.negative; + positivePositive = result.positive; + } + // Разделение ноды + var negativeFirst:Face; + var negativeLast:Face; + var positiveFirst:Face; + var positiveLast:Face; + if (struct.wrapper != null) { + for (face = struct; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo < offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } else if (bo > offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } else { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + } + // Если сзади от сплита есть грани или обе дочерние ноды + if (negativeFirst != null || negativeNegative != null && positiveNegative != null) { + // Создание пустой ноды + if (negativeFirst == null) { + negativeFirst = struct.create(); + camera.lastFace.next = negativeFirst; + camera.lastFace = negativeFirst; + } else { + negativeLast.processNext = null; + } + if (sorting == 3) { + negativeFirst.normalX = struct.normalX; + negativeFirst.normalY = struct.normalY; + negativeFirst.normalZ = struct.normalZ; + negativeFirst.offset = struct.offset; + } + negativeFirst.negative = negativeNegative; + negativeFirst.positive = positiveNegative; + result.negative = negativeFirst; + } else { + result.negative = (negativeNegative != null) ? negativeNegative : positiveNegative; + } + // Если спереди от сплита есть грани или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + // Создание пустой ноды + if (positiveFirst == null) { + positiveFirst = struct.create(); + camera.lastFace.next = positiveFirst; + camera.lastFace = positiveFirst; + } else { + positiveLast.processNext = null; + } + if (sorting == 3) { + positiveFirst.normalX = struct.normalX; + positiveFirst.normalY = struct.normalY; + positiveFirst.normalZ = struct.normalZ; + positiveFirst.offset = struct.offset; + } + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + result.positive = positiveFirst; + } else { + result.positive = (negativePositive != null) ? negativePositive : positivePositive; + } + } + + private function cropFaceStruct(camera:Camera3D, struct:Face, normalX:Number, normalY:Number, normalZ:Number, offset:Number, offsetMin:Number, offsetMax:Number):Face { + var face:Face; + var next:Face; + var w:Wrapper; + var v:Vertex; + // Разделение дочерних нод + var negativePositive:Face; + var positivePositive:Face; + if (struct.negative != null) { + negativePositive = cropFaceStruct(camera, struct.negative, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.negative = null; + } + if (struct.positive != null) { + positivePositive = cropFaceStruct(camera, struct.positive, normalX, normalY, normalZ, offset, offsetMin, offsetMax); + struct.positive = null; + } + // Разделение ноды + var positiveFirst:Face; + var positiveLast:Face; + if (struct.wrapper != null) { + for (face = struct; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } else if (!infront) { + face.processNext = null; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + } + // Если спереди от сплита есть грани или обе дочерние ноды + if (positiveFirst != null || negativePositive != null && positivePositive != null) { + // Создание пустой ноды + if (positiveFirst == null) { + positiveFirst = struct.create(); + camera.lastFace.next = positiveFirst; + camera.lastFace = positiveFirst; + } else { + positiveLast.processNext = null; + } + if (sorting == 3) { + positiveFirst.normalX = struct.normalX; + positiveFirst.normalY = struct.normalY; + positiveFirst.normalZ = struct.normalZ; + positiveFirst.offset = struct.offset; + } + positiveFirst.negative = negativePositive; + positiveFirst.positive = positivePositive; + return positiveFirst; + } else { + return (negativePositive != null) ? negativePositive : positivePositive; + } + } + + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas, threshold:Number):void { + var canvas:Canvas; + var list:Face; + if (viewAligned) { + list = faceStruct; + // Дебаг + if (debug > 0) { + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, (boundType != 2) ? 0xFFFFFF : 0xFF9900); + if (debug & Debug.BOUNDS) { + if (boundType == 1) { + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0x99FF00); + } + } + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false, alpha, blendMode, colorTransform, filters); + list.material.drawViewAligned(camera, canvas, list, ml, tma, tmb, tmc, tmd, tmtx, tmty); + } else { + // Сортировка + switch (sorting) { + case 0: + list = faceStruct; + break; + case 1: + list = (faceStruct.processNext != null) ? sortByAverageZ(faceStruct) : faceStruct; + break; + case 2: + list = (faceStruct.processNext != null) ? sortByDynamicBSP(faceStruct, camera, threshold) : faceStruct; + break; + case 3: + list = collectNode(faceStruct); + break; + } + // Дебаг + if (debug > 0) { + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) { + if (boundType == 1) { + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0x99FF00); + } else if (boundType == 2) { + var oma:Number = object.ma, omb:Number = object.mb, omc:Number = object.mc, omd:Number = object.md, ome:Number = object.me, omf:Number = object.mf, omg:Number = object.mg, omh:Number = object.mh, omi:Number = object.mi, omj:Number = object.mj, omk:Number = object.mk, oml:Number = object.ml; + object.ma = ma; + object.mb = mb; + object.mc = mc; + object.md = md; + object.me = me; + object.mf = mf; + object.mg = mg; + object.mh = mh; + object.mi = mi; + object.mj = mj; + object.mk = mk; + object.ml = ml; + Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ, 0xFF9900); + object.ma = oma; + object.mb = omb; + object.mc = omc; + object.md = omd; + object.me = ome; + object.mf = omf; + object.mg = omg; + object.mh = omh; + object.mi = omi; + object.mj = omj; + object.mk = omk; + object.ml = oml; + } + } + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(interactiveObject, true, false, alpha, blendMode, colorTransform, filters); + for (var face:Face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + faceStruct = null; + } + + private function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + private function sortByDynamicBSP(list:Face, camera:Camera3D, threshold:Number, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var splitter:Face = list; + list = splitter.processNext; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var normalX:Number = 0; + var normalY:Number = 0; + var normalZ:Number = 1; + var offset:Number = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + splitterLast.processNext = face; + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + if (positiveFirst != null) { + positiveLast.processNext = null; + if (positiveFirst.processNext != null) { + result = sortByDynamicBSP(positiveFirst, camera, threshold, result); + } else { + positiveFirst.processNext = result; + result = positiveFirst; + } + } + splitterLast.processNext = result; + result = splitter; + if (negativeFirst != null) { + negativeLast.processNext = null; + if (negativeFirst.processNext != null) { + result = sortByDynamicBSP(negativeFirst, camera, threshold, result); + } else { + negativeFirst.processNext = result; + result = negativeFirst; + } + } + return result; + } + + private function collectNode(tree:Face, result:Face = null):Face { + var last:Face; + var negative:Face; + var positive:Face; + if (tree.offset < 0) { + negative = tree.negative; + positive = tree.positive; + } else { + negative = tree.positive; + positive = tree.negative; + } + tree.negative = null; + tree.positive = null; + if (positive != null) result = collectNode(positive, result); + if (tree.wrapper != null) { + //for (last = tree; last.processNext != null; last = last.processNext); + last = tree; + while (last.processNext != null) last = last.processNext; + last.processNext = result; + result = tree; + } + if (negative != null) result = collectNode(negative, result); + return result; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/KDNode.as b/Alternativa3D7/7.4/alternativa/engine3d/core/KDNode.as new file mode 100644 index 0000000..2d4cd1a --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/KDNode.as @@ -0,0 +1,28 @@ +package alternativa.engine3d.core { + + public class KDNode { + + public var negative:KDNode; + public var positive:KDNode; + + public var axis:int; + public var coord:Number; + public var minCoord:Number; + public var maxCoord:Number; + + public var boundMinX:Number; + public var boundMinY:Number; + public var boundMinZ:Number; + public var boundMaxX:Number; + public var boundMaxY:Number; + public var boundMaxZ:Number; + + public var objects:Vector.; + public var objectsBounds:Vector.; + public var objectsLength:int = 0; + public var occluders:Vector.; + public var occludersBounds:Vector.; + public var occludersLength:int = 0; + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/MipMapping.as b/Alternativa3D7/7.4/alternativa/engine3d/core/MipMapping.as new file mode 100644 index 0000000..18b2eae --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/MipMapping.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.core { + + public class MipMapping { + + /** + * Нет мипмаппинга. + */ + static public const NONE:int = 0; + /** + * Мипмаппинг по удалённости объекта от камеры. + */ + static public const OBJECT_DISTANCE:int = 1; + /** + * Мипмаппинг для каждой грани по удалённости от камеры. + */ + static public const PER_POLYGON_BY_DISTANCE:int = 2; + /** + * Мипмаппинг для каждой грани по перспективному искажению рёбер. + */ + static public const PER_POLYGON_BY_DISTORTION:int = 3; + /** + * Мипмаппинг для каждого пиксела по удалённости от камеры. + */ + static public const PER_PIXEL:int = 4; + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/MouseEvent3D.as b/Alternativa3D7/7.4/alternativa/engine3d/core/MouseEvent3D.as new file mode 100644 index 0000000..daefe33 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/MouseEvent3D.as @@ -0,0 +1,87 @@ +package alternativa.engine3d.core { + import flash.events.Event; + + public class MouseEvent3D extends Event { + /** + * Значение свойства type для объекта события click. + * @eventType click + */ + public static const CLICK:String = "click"; + /** + * Значение свойства type для объекта события doubleClick. + * @eventType doubleClick + */ + public static const DOUBLE_CLICK:String = "doubleClick"; + /** + * Значение свойства type для объекта события mouseDown. + * @eventType mouseDown + */ + public static const MOUSE_DOWN:String = "mouseDown"; + /** + * Значение свойства type для объекта события mouseUp. + * @eventType mouseUp + */ + public static const MOUSE_UP:String = "mouseUp"; + /** + * Значение свойства type для объекта события mouseOver. + * @eventType mouseOver + */ + public static const MOUSE_OVER:String = "mouseOver"; + /** + * Значение свойства type для объекта события mouseOut. + * @eventType mouseOut + */ + public static const MOUSE_OUT:String = "mouseOut"; + /** + * Значение свойства type для объекта события rollOver. + * @eventType rollOver + */ + public static const ROLL_OVER:String = "rollOver"; + /** + * Значение свойства type для объекта события rollOut. + * @eventType rollOut + */ + public static const ROLL_OUT:String = "rollOut"; + /** + * Значение свойства type для объекта события mouseMove. + * @eventType mouseMove + */ + public static const MOUSE_MOVE:String = "mouseMove"; + /** + * Значение свойства type для объекта события mouseWheel. + * @eventType mouseWheel + */ + public static const MOUSE_WHEEL:String = "mouseWheel"; + + /** + * Объект, с которым связано событие. + */ + private var _target:Object3D; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Alt. + */ + public var altKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Control. + */ + public var ctrlKey:Boolean; + /** + * Индикатор нажатой (true) или отпущенной (false) клавиши Shift. + */ + public var shiftKey:Boolean; + /** + * Количество линий прокрутки при вращении колеса мыши. + */ + public var delta:int; + + public function MouseEvent3D(type:String, target:Object3D, altKey:Boolean = false, ctrlKey:Boolean = false, shiftKey:Boolean = false, delta:int = 0) { + super(type); + _target = target; + } + + override public function get target():Object { + return _target; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Object3D.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Object3D.as new file mode 100644 index 0000000..05e851f --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Object3D.as @@ -0,0 +1,625 @@ +package alternativa.engine3d.core { + import __AS3__.vec.Vector; + + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + import flash.utils.getQualifiedClassName; + + use namespace alternativa3d; + + /** + * Событие рассылается когда пользователь последовательно нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Между нажатием и отпусканием кнопки могут происходить любые другие события. + * @eventType alternativa.engine3d.events.MouseEvent3D.CLICK + */ + [Event (name="click", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь последовательно 2 раза в нажимает и отпускает левую кнопку мыши над одним и тем же объектом. + * Событие сработает только если время между первым и вторым кликом вписывается в заданный в системе временной интервал. + * @eventType alternativa.engine3d.events.MouseEvent3D.DOUBLE_CLICK + */ + [Event (name="doubleClick", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь нажимает левую кнопку мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_DOWN + */ + [Event (name="mouseDown", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь отпускает левую кнопку мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_UP + */ + [Event (name="mouseUp", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OVER + */ + [Event (name="mouseOver", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_OUT + */ + [Event (name="mouseOut", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь наводит курсор мыши на объект. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OVER + */ + [Event (name="rollOver", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь уводит курсор мыши с объекта. + * @eventType alternativa.engine3d.events.MouseEvent3D.ROLL_OUT + */ + [Event (name="rollOut", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь перемещает курсор мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_MOVE + */ + [Event (name="mouseMove", type="alternativa.engine3d.core.MouseEvent3D")] + /** + * Событие рассылается когда пользователь вращает колесо мыши над объектом. + * @eventType alternativa.engine3d.events.MouseEvent3D.MOUSE_WHEEL + */ + [Event (name="mouseWheel", type="alternativa.engine3d.core.MouseEvent3D")] + + /** + * Базовый трёхмерный объект + */ + public class Object3D { + + static private const boundVertexList:Vertex = Vertex.createList(8); + + public var name:String; + + public var x:Number = 0; + public var y:Number = 0; + public var z:Number = 0; + public var rotationX:Number = 0; + public var rotationY:Number = 0; + public var rotationZ:Number = 0; + public var scaleX:Number = 1; + public var scaleY:Number = 1; + public var scaleZ:Number = 1; + + public var visible:Boolean = true; + + public var alpha:Number = 1; + public var blendMode:String = "normal"; + public var colorTransform:ColorTransform = null; + public var filters:Array = null; + + public var interactiveAlpha:Number = 0; + public var mouseEnabled:Boolean = true; + public var mouseChildren:Boolean = true; + public var doubleClickEnabled:Boolean = false; + public var useHandCursor:Boolean = false; + + public var boundMinX:Number = -1e+22; + public var boundMinY:Number = -1e+22; + public var boundMinZ:Number = -1e+22; + public var boundMaxX:Number = 1e+22; + public var boundMaxY:Number = 1e+22; + public var boundMaxZ:Number = 1e+22; + + alternativa3d var _parent:Object3DContainer; + + alternativa3d var next:Object3D; + alternativa3d var prev:Object3D; + + alternativa3d var culling:int = 0; + + // Матрица + alternativa3d var ma:Number; + alternativa3d var mb:Number; + alternativa3d var mc:Number; + alternativa3d var md:Number; + alternativa3d var me:Number; + alternativa3d var mf:Number; + alternativa3d var mg:Number; + alternativa3d var mh:Number; + alternativa3d var mi:Number; + alternativa3d var mj:Number; + alternativa3d var mk:Number; + alternativa3d var ml:Number; + + // Инверсная матрица + alternativa3d var ima:Number; + alternativa3d var imb:Number; + alternativa3d var imc:Number; + alternativa3d var imd:Number; + alternativa3d var ime:Number; + alternativa3d var imf:Number; + alternativa3d var img:Number; + alternativa3d var imh:Number; + alternativa3d var imi:Number; + alternativa3d var imj:Number; + alternativa3d var imk:Number; + alternativa3d var iml:Number; + + alternativa3d var listeners:Object; + + alternativa3d var weightsSum:Vector.; + + public function get parent():Object3DContainer { + return _parent; + } + + public function getMatrix():Matrix3D { + var m:Matrix3D = new Matrix3D(); + var t:Vector3D = new Vector3D(x, y, z); + var r:Vector3D = new Vector3D(rotationX, rotationY, rotationZ); + var s:Vector3D = new Vector3D(scaleX, scaleY, scaleZ); + var v:Vector. = new Vector.(); + v[0] = t; + v[1] = r; + v[2] = s; + m.recompose(v); + return m; + } + + public function setMatrix(value:Matrix3D):void { + var v:Vector. = value.decompose(); + var t:Vector3D = v[0]; + var r:Vector3D = v[1]; + var s:Vector3D = v[2]; + x = t.x; + y = t.y; + z = t.z; + rotationX = r.x; + rotationY = r.y; + rotationZ = r.z; + scaleX = s.x; + scaleY = s.y; + scaleZ = s.z; + } + + public function get position():Vector3D { + return new Vector3D(x, y, z); + } + + public function set position(value:Vector3D):void { + x = value.x; + y = value.y; + z = value.z; + } + + public function setPositionXYZ(x:Number, y:Number, z:Number):void { + this.x = x; + this.y = y; + this.z = z; + } + + public function setRotationXYZ(rx:Number, ry:Number, rz:Number):void { + rotationX = rx; + rotationY = ry; + rotationZ = rz; + } + + public function calculateBounds():void { + // Выворачивание баунда + boundMinX = 1e+22; + boundMinY = 1e+22; + boundMinZ = 1e+22; + boundMaxX = -1e+22; + boundMaxY = -1e+22; + boundMaxZ = -1e+22; + // Заполнение баунда + updateBounds(this, null); + // Если баунд вывернут + if (boundMinX > boundMaxX) { + boundMinX = -1e+22; + boundMinY = -1e+22; + boundMinZ = -1e+22; + boundMaxX = 1e+22; + boundMaxY = 1e+22; + boundMaxZ = 1e+22; + } + } + + public function removeFromParent():void { + if (_parent != null) + _parent.removeChild(this); + } + + alternativa3d function composeMatrix():void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX; + var sinXscaleY:Number = sinX*scaleY; + var cosXscaleY:Number = cosX*scaleY; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + ma = cosZ*cosYscaleX; + mb = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + mc = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + md = x; + me = sinZ*cosYscaleX; + mf = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + mg = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + mh = y; + mi = -sinY*scaleX; + mj = cosY*sinXscaleY; + mk = cosY*cosXscaleZ; + ml = z; + } + + alternativa3d function appendMatrix(object:Object3D):void { + var a:Number = ma; + var b:Number = mb; + var c:Number = mc; + var d:Number = md; + var e:Number = me; + var f:Number = mf; + var g:Number = mg; + var h:Number = mh; + var i:Number = mi; + var j:Number = mj; + var k:Number = mk; + var l:Number = ml; + ma = object.ma*a + object.mb*e + object.mc*i; + mb = object.ma*b + object.mb*f + object.mc*j; + mc = object.ma*c + object.mb*g + object.mc*k; + md = object.ma*d + object.mb*h + object.mc*l + object.md; + me = object.me*a + object.mf*e + object.mg*i; + mf = object.me*b + object.mf*f + object.mg*j; + mg = object.me*c + object.mf*g + object.mg*k; + mh = object.me*d + object.mf*h + object.mg*l + object.mh; + mi = object.mi*a + object.mj*e + object.mk*i; + mj = object.mi*b + object.mj*f + object.mk*j; + mk = object.mi*c + object.mj*g + object.mk*k; + ml = object.mi*d + object.mj*h + object.mk*l + object.ml; + } + + alternativa3d function composeAndAppend(object:Object3D):void { + var cosX:Number = Math.cos(rotationX); + var sinX:Number = Math.sin(rotationX); + var cosY:Number = Math.cos(rotationY); + var sinY:Number = Math.sin(rotationY); + var cosZ:Number = Math.cos(rotationZ); + var sinZ:Number = Math.sin(rotationZ); + var cosZsinY:Number = cosZ*sinY; + var sinZsinY:Number = sinZ*sinY; + var cosYscaleX:Number = cosY*scaleX; + var sinXscaleY:Number = sinX*scaleY; + var cosXscaleY:Number = cosX*scaleY; + var cosXscaleZ:Number = cosX*scaleZ; + var sinXscaleZ:Number = sinX*scaleZ; + var a:Number = cosZ*cosYscaleX; + var b:Number = cosZsinY*sinXscaleY - sinZ*cosXscaleY; + var c:Number = cosZsinY*cosXscaleZ + sinZ*sinXscaleZ; + var d:Number = x; + var e:Number = sinZ*cosYscaleX; + var f:Number = sinZsinY*sinXscaleY + cosZ*cosXscaleY; + var g:Number = sinZsinY*cosXscaleZ - cosZ*sinXscaleZ; + var h:Number = y; + var i:Number = -sinY*scaleX; + var j:Number = cosY*sinXscaleY; + var k:Number = cosY*cosXscaleZ; + var l:Number = z; + ma = object.ma*a + object.mb*e + object.mc*i; + mb = object.ma*b + object.mb*f + object.mc*j; + mc = object.ma*c + object.mb*g + object.mc*k; + md = object.ma*d + object.mb*h + object.mc*l + object.md; + me = object.me*a + object.mf*e + object.mg*i; + mf = object.me*b + object.mf*f + object.mg*j; + mg = object.me*c + object.mf*g + object.mg*k; + mh = object.me*d + object.mf*h + object.mg*l + object.mh; + mi = object.mi*a + object.mj*e + object.mk*i; + mj = object.mi*b + object.mj*f + object.mk*j; + mk = object.mi*c + object.mj*g + object.mk*k; + ml = object.mi*d + object.mj*h + object.mk*l + object.ml; + } + + alternativa3d function calculateInverseMatrix(object:Object3D):void { + var a:Number = object.ma; + var b:Number = object.mb; + var c:Number = object.mc; + var d:Number = object.md; + var e:Number = object.me; + var f:Number = object.mf; + var g:Number = object.mg; + var h:Number = object.mh; + var i:Number = object.mi; + var j:Number = object.mj; + var k:Number = object.mk; + var l:Number = object.ml; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + ima = (-g*j + f*k)*det; + imb = (c*j - b*k)*det; + imc = (-c*f + b*g)*det; + imd = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + ime = (g*i - e*k)*det; + imf = (-c*i + a*k)*det; + img = (c*e - a*g)*det; + imh = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + imi = (-f*i + e*j)*det; + imj = (b*i - a*j)*det; + imk = (-b*e + a*f)*det; + iml = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + } + + alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + } + + alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return null; + } + + alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + } + + alternativa3d function split(normalX:Number, normalY:Number, normalZ:Number, offset:Number, threshold:Number):Vector. { + return new Vector.(2); + } + + alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + if (camera.occludedAll) return -1; + var numOccluders:int = camera.numOccluders; + var vertex:Vertex; + // Расчёт точек баунда в координатах камеры + if (culling > 0 || numOccluders > 0) { + // Заполнение + vertex = boundVertexList; + vertex.x = boundMinX; + vertex.y = boundMinY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMinY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMaxY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMaxY; + vertex.z = boundMinZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMinY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMinY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMinX; + vertex.y = boundMaxY; + vertex.z = boundMaxZ; + vertex = vertex.next; + vertex.x = boundMaxX; + vertex.y = boundMaxY; + vertex.z = boundMaxZ; + // Трансформация в камеру + for (vertex = boundVertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = object.ma*x + object.mb*y + object.mc*z + object.md; + vertex.cameraY = object.me*x + object.mf*y + object.mg*z + object.mh; + vertex.cameraZ = object.mi*x + object.mj*y + object.mk*z + object.ml; + } + } + // Куллинг + if (culling > 0) { + var infront:Boolean; + var behind:Boolean; + if (culling & 1) { + var near:Number = camera.nearClipping; + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ > near) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 62; + } + } + if (culling & 2) { + var far:Number = camera.farClipping; + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraZ < far) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 61; + } + } + if (culling & 4) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (-vertex.cameraX < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 59; + } + } + if (culling & 8) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraX < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 55; + } + } + if (culling & 16) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (-vertex.cameraY < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 47; + } + } + if (culling & 32) { + for (vertex = boundVertexList,infront = false,behind = false; vertex != null; vertex = vertex.next) { + if (vertex.cameraY < vertex.cameraZ) { + infront = true; + if (behind) break; + } else { + behind = true; + if (infront) break; + } + } + if (behind) { + if (!infront) return -1; + } else { + culling &= 31; + } + } + } + // Окклюдинг + if (numOccluders > 0) { + for (var i:int = 0; i < numOccluders; i++) { + for (var plane:Vertex = camera.occluders[i]; plane != null; plane = plane.next) { + for (vertex = boundVertexList; vertex != null; vertex = vertex.next) { + if (plane.cameraX*vertex.cameraX + plane.cameraY*vertex.cameraY + plane.cameraZ*vertex.cameraZ >= 0) break; + } + if (vertex != null) break; + } + if (plane == null) return -1; + } + } + object.culling = culling; + return culling; + } + + public function clone():Object3D { + var res:Object3D = new Object3D(); + res.cloneBaseProperties(this); + return res; + } + + protected function cloneBaseProperties(source:Object3D):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + if (source.colorTransform != null) { + colorTransform = new ColorTransform(); + colorTransform.concat(source.colorTransform); + } + if (source.filters != null) { + filters = new Array().concat(source.filters); + } + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + } + + /** + * Добавляет обработчик события. + * @param type тип события + * @param listener обработчик события + */ + public function addEventListener(type:String, listener:Function):void { + if (listeners == null) listeners = new Object(); + var vector:Vector. = listeners[type]; + if (vector == null) { + vector = new Vector.(); + listeners[type] = vector; + } + if (vector.indexOf(listener) < 0) { + vector.push(listener); + } + } + + /** + * Удаляет обработчик события. + * @param type тип события + * @param listener обработчик события + */ + public function removeEventListener(type:String, listener:Function):void { + if (listeners != null) { + var vector:Vector. = listeners[type]; + if (vector != null) { + var i:int = vector.indexOf(listener); + if (i >= 0) { + var length:int = vector.length; + for (var j:int = i + 1; j < length; j++,i++) { + vector[i] = vector[j]; + } + if (length > 1) { + vector.length = length - 1; + } else { + delete listeners[type]; + var key:*; + for (key in listeners) break; + if (!key) listeners = null; + } + } + } + } + } + + /** + * Проверяет наличие зарегистрированных обработчиков события указанного типа. + * @param type тип события + * @return true если есть обработчики события указанного типа, иначе false + */ + public function hasEventListener(type:String):Boolean { + return listeners != null && listeners[type]; + } + + public function toString():String { + var className:String = getQualifiedClassName(this); + return "[" + className.substr(className.indexOf("::") + 2) + " " + name + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Object3DContainer.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Object3DContainer.as new file mode 100644 index 0000000..ca4ccba --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Object3DContainer.as @@ -0,0 +1,384 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.geom.ColorTransform; + + use namespace alternativa3d; + + /** + * Базовый контейнер трёхмерных объектов. + * Логика контейнеров и child-parent-отношений идентична логике + * displayObject'ов во Flash. + */ + public class Object3DContainer extends Object3D { + + alternativa3d var firstChild:Object3D; + alternativa3d var lastChild:Object3D; + alternativa3d var _numChildren:int = 0; + + protected var visibleChildren:Vector. = new Vector.(); + protected var numVisibleChildren:int = 0; + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + // Сбор видимых объектов + numVisibleChildren = 0; + for (var child:Object3D = firstChild; child != null; child = child.next) { + if (child.visible) { + child.composeAndAppend(object); + if (child.cullingInCamera(camera, child, object.culling) >= 0) { + visibleChildren[numVisibleChildren] = child; + numVisibleChildren++; + } + } + } + // Если есть видимые объекты + if (numVisibleChildren > 0) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, false, true, object.alpha, object.blendMode, object.colorTransform, object.filters); + canvas.numDraws = 0; + // Отрисовка видимых объектов + drawVisibleChildren(camera, object, canvas); + // Если была отрисовка + if (canvas.numDraws > 0) { + canvas.removeChildren(canvas.numDraws); + } else { + parentCanvas.numDraws--; + } + } + } + + protected function drawVisibleChildren(camera:Camera3D, object:Object3D, canvas:Canvas):void { + for (var i:int = numVisibleChildren - 1; i >= 0; i--) { + var child:Object3D = visibleChildren[i]; + child.draw(camera, child, canvas); + visibleChildren[i] = null; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var i:int; + var first:Geometry; + var last:Geometry; + var geometry:Geometry; + for (var child:Object3D = firstChild; child != null; child = child.next) { + if (child.visible) { + child.composeAndAppend(object); + if (child.cullingInCamera(camera, child, object.culling) >= 0) { + geometry = child.getGeometry(camera, child); + if (geometry != null) { + if (first != null) { + last.next = geometry; + } else { + first = geometry; + last = geometry; + } + while (last.next != null) { + last = last.next; + } + } + } + } + } + if (object.alpha != 1) { + geometry = first; + while (geometry != null) { + geometry.alpha *= object.alpha; + geometry = geometry.next; + } + } + if (object.blendMode != "normal") { + geometry = first; + while (geometry != null) { + if (geometry.blendMode == "normal") { + geometry.blendMode = object.blendMode; + } + geometry = geometry.next; + } + } + if (object.colorTransform != null) { + geometry = first; + while (geometry != null) { + if (geometry.colorTransform != null) { + var ct:ColorTransform = new ColorTransform(object.colorTransform.redMultiplier, object.colorTransform.greenMultiplier, object.colorTransform.blueMultiplier, object.colorTransform.alphaMultiplier, object.colorTransform.redOffset, object.colorTransform.greenOffset, object.colorTransform.blueOffset, object.colorTransform.alphaOffset); + ct.concat(geometry.colorTransform); + geometry.colorTransform = ct; + } else { + geometry.colorTransform = object.colorTransform; + } + geometry = geometry.next; + } + } + if (object.filters != null) { + geometry = first; + while (geometry != null) { + if (geometry.filters != null) { + var fs:Array = new Array(); + var fsLength:int = 0; + var num:int = geometry.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = geometry.filters[i]; + fsLength++; + } + num = object.filters.length; + for (i = 0; i < num; i++) { + fs[fsLength] = object.filters[i]; + fsLength++; + } + geometry.filters = fs; + } else { + geometry.filters = object.filters; + } + geometry = geometry.next; + } + } + return first; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var child:Object3D = firstChild; child != null; child = child.next) { + if (transformation != null) { + child.composeAndAppend(transformation); + } else { + child.composeMatrix(); + } + child.updateBounds(bounds, child); + } + } + + public function get numChildren():int { + return _numChildren; + } + + public function addChild(child:Object3D):Object3D { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + if (child == this) throw new ArgumentError("An object cannot be added as a child of itself."); + for (var container:Object3DContainer = _parent; container != null; container = container._parent) { + if (container == child) throw new ArgumentError("An object cannot be added as a child to one of it's children (or children's children, etc.)."); + } + // Удаление из старого родителя + if (child._parent != null) { + child._parent.removeFromList(child); + } + // Добавление + addToList(child); + return child; + } + + public function removeChild(child:Object3D):Object3D { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + // Удаление + removeFromList(child); + return child; + } + + public function addChildAt(child:Object3D, index:int):Object3D { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + if (child == this) throw new ArgumentError("An object cannot be added as a child of itself."); + if (index < 0 || index > _numChildren || child._parent == this && index == _numChildren) throw new RangeError("The supplied index is out of bounds."); + for (var container:Object3DContainer = _parent; container != null; container = container._parent) { + if (container == child) throw new ArgumentError("An object cannot be added as a child to one of it's children (or children's children, etc.)."); + } + // Удаление из старого родителя + if (child._parent != null) { + child._parent.removeFromList(child); + } + // Поиск элемента по индексу + var item:Object3D = firstChild; + for (var i:int = 0; i < index; i++) { + item = item.next; + } + // Добавление + addToList(child, item); + return child; + } + + public function removeChildAt(index:int):Object3D { + // Проверка на ошибки + if (index < 0 || index >= _numChildren) throw new RangeError("The supplied index is out of bounds."); + // Поиск элемента по индексу + var child:Object3D = firstChild; + for (var i:int = 0; i < index; i++) { + child = child.next; + } + // Удаление + removeFromList(child); + return child; + } + + public function getChildAt(index:int):Object3D { + // Проверка на ошибки + if (index < 0 || index >= _numChildren) throw new RangeError("The supplied index is out of bounds."); + // Поиск элемента по индексу + var child:Object3D = firstChild; + for (var i:int = 0; i < index; i++) { + child = child.next; + } + return child; + } + + public function getChildIndex(child:Object3D):int { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + // Поиск индекса элемента + var index:int = 0; + for (var object:Object3D = firstChild; object != null; object = object.next) { + if (object == child) break; + index++; + } + return index; + } + + public function setChildIndex(child:Object3D, index:int):void { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + if (child._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + if (index < 0 || index >= _numChildren) throw new RangeError("The supplied index is out of bounds."); + // Удаление + removeFromList(child); + // Поиск элемента по индексу + var item:Object3D = firstChild; + for (var i:int = 0; i < index; i++) { + item = item.next; + } + // Добавление + addToList(child, item); + } + + public function swapChildren(child1:Object3D, child2:Object3D):void { + // Проверка на ошибки + if (child1 == null || child2 == null) throw new TypeError("Parameter child must be non-null."); + if (child1._parent != this || child2._parent != this) throw new ArgumentError("The supplied Object3D must be a child of the caller."); + // Перестановка + if (child1 != child2) { + if (child1.next == child2) { + removeFromList(child2); + addToList(child2, child1); + } else if (child2.next == child1) { + removeFromList(child1); + addToList(child1, child2); + } else { + var item:Object3D = child1.next; + removeFromList(child1); + addToList(child1, child2); + removeFromList(child2); + addToList(child2, item); + } + } + } + + public function swapChildrenAt(index1:int, index2:int):void { + // Проверка на ошибки + if (index1 < 0 || index1 >= _numChildren || index2 < 0 || index2 >= _numChildren) throw new RangeError("The supplied index is out of bounds."); + // Перестановка + if (index1 != index2) { + // Поиск элементов по индексам + var i:int; + var child1:Object3D = firstChild; + for (i = 0; i < index1; i++) { + child1 = child1.next; + } + var child2:Object3D = firstChild; + for (i = 0; i < index2; i++) { + child2 = child2.next; + } + if (child1 != child2) { + if (child1.next == child2) { + removeFromList(child2); + addToList(child2, child1); + } else if (child2.next == child1) { + removeFromList(child1); + addToList(child1, child2); + } else { + var item:Object3D = child1.next; + removeFromList(child1); + addToList(child1, child2); + removeFromList(child2); + addToList(child2, item); + } + } + } + } + + public function getChildByName(name:String):Object3D { + // Проверка на ошибки + if (name == null) throw new TypeError("Parameter name must be non-null."); + // Поиск объекта + for (var child:Object3D = firstChild; child != null; child = child.next) { + if (child.name == name) return child; + } + return null; + } + + public function contains(child:Object3D):Boolean { + // Проверка на ошибки + if (child == null) throw new TypeError("Parameter child must be non-null."); + // Поиск объекта + if (child == this) return true; + for (var object:Object3D = firstChild; object != null; object = object.next) { + if (object is Object3DContainer) { + if ((object as Object3DContainer).contains(child)) { + return true; + } + } else if (object == child) { + return true; + } + } + return false; + } + + private function removeFromList(child:Object3D):void { + if (child.prev != null) { + child.prev.next = child.next; + } else { + firstChild = child.next; + } + if (child.next != null) { + child.next.prev = child.prev; + } else { + lastChild = child.prev; + } + child.prev = null; + child.next = null; + child._parent = null; + _numChildren--; + } + + private function addToList(child:Object3D, item:Object3D = null):void { + if (item != null) { + // Добавление перед элементом + if (item.prev != null) { + item.prev.next = child; + } else { + firstChild = child; + } + child.prev = item.prev; + child.next = item; + item.prev = child; + } else { + // Добавление в конец + if (lastChild != null) { + lastChild.next = child; + child.prev = lastChild; + } else { + firstChild = child; + } + lastChild = child; + } + child._parent = this; + _numChildren++; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Sorting.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Sorting.as new file mode 100644 index 0000000..7512c9a --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Sorting.as @@ -0,0 +1,23 @@ +package alternativa.engine3d.core { + + public class Sorting { + + /** + * Грани не сортируются. + */ + static public const NONE:int = 0; + /** + * Грани сортируются по средним Z. + */ + static public const AVERAGE_Z:int = 1; + /** + * Грани при отрисовке образуют BSP-дерево. + */ + static public const DYNAMIC_BSP:int = 2; + /** + * Грани находятся в BSP-дереве. + */ + static public const STATIC_BSP:int = 3; + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Vertex.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Vertex.as new file mode 100644 index 0000000..6e2a611 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Vertex.as @@ -0,0 +1,82 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Vertex { + + public var x:Number = 0; + public var y:Number = 0; + public var z:Number = 0; + + public var u:Number = 0; + public var v:Number = 0; + + alternativa3d var next:Vertex; + alternativa3d var value:Vertex; + + alternativa3d var cameraX:Number; + alternativa3d var cameraY:Number; + alternativa3d var cameraZ:Number; + alternativa3d var offset:Number; + + alternativa3d var transformID:int = 0; + alternativa3d var drawID:int = 0; + alternativa3d var index:int; + + static alternativa3d var collector:Vertex; + + static alternativa3d function createList(num:int):Vertex { + var res:Vertex = collector; + var last:Vertex; + if (res != null) { + for (last = res; num > 1; last = last.next,num--) { + last.transformID = 0; + last.drawID = 0; + if (last.next == null) { + //for (; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--); + while (num > 1) { + //trace("new Vertex"); + last.next = new Vertex(); + last = last.next; + num--; + } + break; + } + } + collector = last.next; + last.transformID = 0; + last.drawID = 0; + last.next = null; + } else { + //for (res = new Vertex(), /*trace("new Vertex"), */last = res; num > 1; /*trace("new Vertex"), */last.next = new Vertex(), last = last.next, num--); + //trace("new Vertex"); + res = new Vertex(); + last = res; + while (num > 1) { + //trace("new Vertex"); + last.next = new Vertex(); + last = last.next; + num--; + } + } + return res; + } + + alternativa3d function create():Vertex { + if (collector != null) { + var res:Vertex = collector; + collector = res.next; + res.next = null; + res.transformID = 0; + res.drawID = 0; + return res; + } else { + //trace("new Vertex"); + return new Vertex(); + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/View.as b/Alternativa3D7/7.4/alternativa/engine3d/core/View.as new file mode 100644 index 0000000..876ace8 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/View.as @@ -0,0 +1,328 @@ +package alternativa.engine3d.core { + import alternativa.engine3d.alternativa3d; + + import flash.display.DisplayObject; + import flash.events.Event; + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; + import flash.geom.ColorTransform; + import flash.geom.Point; + + use namespace alternativa3d; + + public class View extends Canvas { + + static private const mouse:Point = new Point(); + static private const listeners:Vector. = new Vector.(); + + alternativa3d var _width:Number; + alternativa3d var _height:Number; + + alternativa3d var _interactive:Boolean = false; + + private var altKey:Boolean; + private var ctrlKey:Boolean; + private var shiftKey:Boolean; + + private var pressedObject:Object3D; + private var clickedObject:Object3D; + private var overedObject:Object3D; + private var overedBranch:Vector. = new Vector.(); + + public function View(width:Number, height:Number, interactive:Boolean = false) { + _width = width; + _height = height; + _interactive = interactive; + mouseEnabled = false; + mouseChildren = false; + tabEnabled = false; + tabChildren = false; + + addEventListener(Event.ADDED_TO_STAGE, onAddToStage); + addEventListener(Event.REMOVED_FROM_STAGE, onRemoveFromStage); + } + + private function onAddToStage(e:Event):void { + if (_interactive) addListeners(); + } + + private function onRemoveFromStage(e:Event):void { + if (_interactive) removeListeners(); + } + + public function get interactive():Boolean { + return _interactive; + } + + public function set interactive(value:Boolean):void { + if (_interactive != value) { + if (stage != null) { + if (value) { + addListeners(); + } else { + removeListeners(); + } + } + _interactive = value; + } + } + + private function addListeners():void { + stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + stage.addEventListener(MouseEvent.CLICK, onClick); + stage.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); + stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + stage.addEventListener(KeyboardEvent.KEY_DOWN, onKey); + stage.addEventListener(KeyboardEvent.KEY_UP, onKey); + } + + private function removeListeners():void { + stage.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); + stage.removeEventListener(MouseEvent.CLICK, onClick); + stage.removeEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); + stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); + stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); + stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey); + stage.removeEventListener(KeyboardEvent.KEY_UP, onKey); + pressedObject = null; + clickedObject = null; + overedObject = null; + overedBranch.length = 0; + } + + private function onMouseDown(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + pressedObject = targetCanvas.interactiveObject; + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_DOWN, targetCanvas); + } + } + + private function onClick(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_UP, targetCanvas); + if (pressedObject == targetCanvas.interactiveObject) { + clickedObject = targetCanvas.interactiveObject; + dispatchEventToCanvasHierarchy(MouseEvent3D.CLICK, targetCanvas); + } + } + pressedObject = null; + } + + private function onDoubleClick(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_UP, targetCanvas); + if (pressedObject == targetCanvas.interactiveObject) { + dispatchEventToCanvasHierarchy(clickedObject == targetCanvas.interactiveObject && targetCanvas.interactiveObject.doubleClickEnabled ? MouseEvent3D.DOUBLE_CLICK : MouseEvent3D.CLICK, targetCanvas); + } + } + clickedObject = null; + pressedObject = null; + } + + alternativa3d function onMouseMove(mouseEvent:MouseEvent = null):void { + if (mouseEvent != null) { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + } + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + var i:int; + var canvas:Canvas; + var object:Object3D; + if (overedObject != targetCanvas.interactiveObject) { + if (overedObject != null) { + var length:int = overedBranch.length; + dispatchEventToObjectHierarchy(MouseEvent3D.MOUSE_OUT); + for (i = 0; i < length; i++) { + object = overedBranch[i]; + canvas = targetCanvas; + while (canvas != this) { + if (object == canvas.interactiveObject) break; + canvas = Canvas(canvas.parent); + } + if (canvas == this) { + dispatchEvent3D(object, MouseEvent3D.ROLL_OUT, object); + } + } + canvas = targetCanvas; + while (canvas != this) { + object = canvas.interactiveObject; + for (i = 0; i < length; i++) { + if (object == overedBranch[i]) break; + } + if (i == length) { + dispatchEvent3D(object, MouseEvent3D.ROLL_OVER, object); + } + canvas = Canvas(canvas.parent); + } + } else { + dispatchEventToCanvasHierarchy(MouseEvent3D.ROLL_OVER, targetCanvas); + } + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_OVER, targetCanvas); + } + if (mouseEvent != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_MOVE, targetCanvas); + } + i = 0; + canvas = targetCanvas; + while (canvas != this) { + overedBranch[i] = canvas.interactiveObject; + i++; + canvas = Canvas(canvas.parent); + } + overedObject = targetCanvas.interactiveObject; + overedBranch.length = i; + } else if (overedObject != null) { + dispatchEventToObjectHierarchy(MouseEvent3D.MOUSE_OUT); + dispatchEventToObjectHierarchy(MouseEvent3D.ROLL_OUT); + overedObject = null; + overedBranch.length = 0; + } + } + + private function onMouseWheel(mouseEvent:MouseEvent):void { + altKey = mouseEvent.altKey; + ctrlKey = mouseEvent.ctrlKey; + shiftKey = mouseEvent.shiftKey; + var targetCanvas:Canvas = defineTargetCanvas(); + if (targetCanvas != null) { + dispatchEventToCanvasHierarchy(MouseEvent3D.MOUSE_WHEEL, targetCanvas, mouseEvent.delta); + } + } + + private function dispatchEventToCanvasHierarchy(type:String, canvas:Canvas, delta:int = 0):void { + var target:Object3D = canvas.interactiveObject; + while (canvas != this) { + dispatchEvent3D(canvas.interactiveObject, type, target, delta); + canvas = Canvas(canvas.parent); + } + } + + private function dispatchEventToObjectHierarchy(type:String, delta:int = 0):void { + var target:Object3D = overedBranch[0]; + var length:int = overedBranch.length; + for (var i:int = 0; i < length; i++) { + dispatchEvent3D(overedBranch[i], type, target, delta); + } + } + + private function dispatchEvent3D(object:Object3D, type:String, target:Object3D, delta:int = 0):void { + if (object.listeners != null) { + var vector:Vector. = object.listeners[type]; + if (vector != null) { + var i:int; + var length:int = vector.length; + for (i = 0; i < length; i++) { + listeners[i] = vector[i]; + } + for (i = 0; i < length; i++) { + (listeners[i] as Function).call(null, new MouseEvent3D(type, target, altKey, ctrlKey, shiftKey, delta)); + } + } + } + } + + private function defineTargetCanvas():Canvas { + // Если мышь внутри области вьюпорта + if (mouseX >= 0 && mouseY >= 0 && mouseX <= _width && mouseY <= _height) { + // Получение объектов под мышью + mouse.x = stage.mouseX; + mouse.y = stage.mouseY; + var displayObjects:Array = stage.getObjectsUnderPoint(mouse); + // Перебор объектов + for (var i:int = displayObjects.length - 1; i >= 0; i--) { + var displayObject:DisplayObject = displayObjects[i]; + // Поиск канваса + while (displayObject != null && !(displayObject is Canvas)) { + displayObject = displayObject.parent; + } + // Если канвас найден + if (displayObject != null) { + var canvas:Canvas = Canvas(displayObject); + // Проверка на прозрачность + if (canvas.interactiveObject.interactiveAlpha <= 1) { + // TODO: проверка порога альфы if (canvas.interactiveObject.interactiveAlpha > 0) + // Определение целевого объекта + var target:Canvas = null; + while (canvas != this) { + if (!canvas.interactiveObject.mouseChildren) { + target = null; + } + if (target == null && canvas.interactiveObject.mouseEnabled) { + target = canvas; + } + canvas = Canvas(canvas.parent); + } + if (target != null) { + return target; + } + } + } else { + // TODO: тут должна быть проверка на интерактивность, чтобы понять перекрывает что-то вьюпорт или нет + } + } + } + return null; + } + + private function onKey(keyboardEvent:KeyboardEvent):void { + altKey = keyboardEvent.altKey; + ctrlKey = keyboardEvent.ctrlKey; + shiftKey = keyboardEvent.shiftKey; + } + + override public function get width():Number { + return _width; + } + + override public function set width(value:Number):void { + _width = value; + } + + override public function get height():Number { + return _height; + } + + override public function set height(value:Number):void { + _height = value; + } + + public function clear():void { + removeChildren(0); + numDraws = 0; + } + + override alternativa3d function getChildCanvas(interactiveObject:Object3D, useGraphics:Boolean, useChildren:Boolean, alpha:Number = 1, blendMode:String = "normal", colorTransform:ColorTransform = null, filters:Array = null):Canvas { + var canvas:Canvas = super.getChildCanvas(interactiveObject, useGraphics, useChildren, alpha, blendMode, colorTransform, filters); + canvas.x = _width/2; + canvas.y = _height/2; + return canvas; + } + + override alternativa3d function removeChildren(keep:int):void { + for (var i:int = 0; i < _numChildren - keep; i++) { + var canvas:Canvas = getChildAt(i) as Canvas; + if (canvas != null) { + canvas.x = 0; + canvas.y = 0; + } + } + super.removeChildren(keep); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/core/Wrapper.as b/Alternativa3D7/7.4/alternativa/engine3d/core/Wrapper.as new file mode 100644 index 0000000..b9264e7 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/core/Wrapper.as @@ -0,0 +1,40 @@ +package alternativa.engine3d.core { + + import alternativa.engine3d.alternativa3d; + + use namespace alternativa3d; + + public class Wrapper { + + alternativa3d var next:Wrapper; + + alternativa3d var vertex:Vertex; + + static alternativa3d var collector:Wrapper; + + static alternativa3d function create():Wrapper { + if (collector != null) { + var res:Wrapper = collector; + collector = collector.next; + res.next = null; + return res; + } else { + //trace("new Wrapper"); + return new Wrapper(); + } + } + + alternativa3d function create():Wrapper { + if (collector != null) { + var res:Wrapper = collector; + collector = collector.next; + res.next = null; + return res; + } else { + //trace("new Wrapper"); + return new Wrapper(); + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/MaterialLoader.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/MaterialLoader.as new file mode 100644 index 0000000..4fff4a7 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/MaterialLoader.as @@ -0,0 +1,230 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.loaders.events.LoaderErrorEvent; + import alternativa.engine3d.loaders.events.LoaderEvent; + import alternativa.engine3d.loaders.events.LoaderProgressEvent; + import alternativa.engine3d.materials.TextureMaterial; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.BitmapDataChannel; + import flash.display.BlendMode; + import flash.display.Loader; + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.events.SecurityErrorEvent; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.net.URLRequest; + import flash.system.LoaderContext; + + /** + * Рассылается вначале загрузки очередного материала. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_OPEN + */ + [Event (name="partOpen", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается после окончания этапа очередного материала. + * В событии в свойстве target содержится загруженный материал. + * + * @eventType alternativa.engine3d.loaders.events.LoaderEvent.PART_COMPLETE + */ + [Event (name="partComplete", type="alternativa.engine3d.loaders.events.LoaderEvent")] + /** + * Рассылается после окончания загрузки всех материалов. + * + * @eventType flash.events.Event.COMPLETE + */ + [Event (name="complete", type="flash.events.Event")] + /** + * Рассылается, если в процессе загрузки возникает ошибка. + * + * @eventType alternativa.engine3d.loaders.events.LoaderErrorEvent.LOADER_ERROR + */ + [Event (name="loaderError", type="alternativa.engine3d.loaders.events.LoaderErrorEvent")] + + /** + * Загрузчик текстур материалов + */ + public class MaterialLoader extends EventDispatcher { + + static private var stub:BitmapData; + + private var loader:Loader; + private var context:LoaderContext; + + private var materials:Vector.; + private var urls:Vector.; + private var filesTotal:int; + private var filesLoaded:int; + + private var diffuse:BitmapData; + private var currentURL:String; + private var index:int; + + /** + * Начинает загрузку текстур материалов + * + * @param materials список материалов для загрузки их текстур + * @param context + */ + public function load(materials:Vector., context:LoaderContext = null):void { + this.context = context; + this.materials = materials; + urls = new Vector.(); + for (var i:int = 0, j:int = 0; i < materials.length; i++) { + var material:TextureMaterial = materials[i]; + urls[j++] = material.diffuseMapURL; + filesTotal++; + if (material.opacityMapURL != null) { + urls[j++] = material.opacityMapURL; + filesTotal++; + } else { + urls[j++] = null; + } + } + filesLoaded = 0; + index = -1; + loadNext(null); + } + + /** + * Останавливает загрузку и выполняет зачистку загрузчика. + */ + public function close():void { + destroyLoader(); + materials = null; + urls = null; + diffuse = null; + currentURL = null; + context = null; + } + + private function destroyLoader():void { + if (loader != null) { + loader.unload(); + loader.contentLoaderInfo.removeEventListener(Event.OPEN, onPartOpen); + loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadNext); + loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, onFileProgress); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.DISK_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.NETWORK_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.VERIFY_ERROR, loadNext); + loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext); + loader = null; + } + } + + private function loadNext(e:Event):void { + if (index >= 0) { + if (index % 2 == 0) { + // Завершение загрузки диффузии + if (e is ErrorEvent) { + diffuse = getStub(); + onFileError((e as ErrorEvent).text); + } else { + diffuse = (loader.content as Bitmap).bitmapData; + } + filesLoaded++; + } else { + // Завершение загрузки альфы + var material:TextureMaterial = materials[(index - 1) >> 1]; + if (e == null) { + material.texture = diffuse; + } else { + if (e is ErrorEvent) { + material.texture = diffuse; + onFileError((e as ErrorEvent).text); + } else { + material.texture = merge(diffuse, (loader.content as Bitmap).bitmapData); + } + filesLoaded++; + } + onPartComplete((index - 1) >> 1, material); + diffuse = null; + } + destroyLoader(); + } + if (++index >= urls.length) { + // Завершение всей загрузки + close(); + if (hasEventListener(Event.COMPLETE)) { + dispatchEvent(new Event(Event.COMPLETE)); + } + } else { + // Загрузка следующего файла + currentURL = urls[index]; + if (currentURL != null && (diffuse == null || diffuse != stub)) { + loader = new Loader(); + if (index % 2 == 0) { + loader.contentLoaderInfo.addEventListener(Event.OPEN, onPartOpen); + } + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNext); + loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onFileProgress); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.DISK_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, loadNext); + loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadNext); + loader.load(new URLRequest(currentURL), context); + } else { + loadNext(null); + } + } + } + + private function onPartOpen(e:Event):void { + if (hasEventListener(LoaderEvent.PART_OPEN)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_OPEN, urls.length >> 1, index >> 1, materials[index >> 1])); + } + } + + private function onPartComplete(partsLoaded:int, material:TextureMaterial):void { + if (hasEventListener(LoaderEvent.PART_COMPLETE)) { + dispatchEvent(new LoaderEvent(LoaderEvent.PART_COMPLETE, urls.length >> 1, partsLoaded, material)); + } + } + + private function onFileProgress(e:ProgressEvent):void { + if (hasEventListener(LoaderProgressEvent.LOADER_PROGRESS)) { + dispatchEvent(new LoaderProgressEvent(LoaderProgressEvent.LOADER_PROGRESS, filesTotal, filesLoaded, (filesLoaded + e.bytesLoaded/e.bytesTotal)/filesTotal, e.bytesLoaded, e.bytesTotal)); + } + } + + private function onFileError(text:String):void { + dispatchEvent(new LoaderErrorEvent(LoaderErrorEvent.LOADER_ERROR, currentURL, text)); + } + + private function merge(diffuse:BitmapData, alpha:BitmapData):BitmapData { + var res:BitmapData = new BitmapData(diffuse.width, diffuse.height); + res.copyPixels(diffuse, diffuse.rect, new Point()); + if (diffuse.width != alpha.width || diffuse.height != alpha.height) { + diffuse.draw(alpha, new Matrix(diffuse.width/alpha.width, 0, 0, diffuse.height/alpha.height), null, BlendMode.NORMAL, null, true); + alpha.dispose(); + alpha = diffuse; + } else { + diffuse.dispose(); + } + res.copyChannel(alpha, alpha.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA); + alpha.dispose(); + return res; + } + + private function getStub():BitmapData { + if (stub == null) { + var size:uint = 20; + stub = new BitmapData(size, size, false, 0); + for (var i:uint = 0; i < size; i++) { + for (var j:uint = 0; j < size; j += 2) { + stub.setPixel((i % 2) ? j : (j + 1), i, 0xFF00FF); + } + } + } + return stub; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/Parser3DS.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/Parser3DS.as new file mode 100644 index 0000000..c2e6e38 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/Parser3DS.as @@ -0,0 +1,791 @@ +package alternativa.engine3d.loaders { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + import alternativa.engine3d.objects.Mesh; + + import flash.geom.Matrix; + import flash.geom.Vector3D; + import flash.utils.ByteArray; + import flash.utils.Endian; + + use namespace alternativa3d; + + public class Parser3DS { + + private static const CHUNK_MAIN:int = 0x4D4D; + private static const CHUNK_VERSION:int = 0x0002; + private static const CHUNK_SCENE:int = 0x3D3D; + private static const CHUNK_ANIMATION:int = 0xB000; + private static const CHUNK_OBJECT:int = 0x4000; + private static const CHUNK_TRIMESH:int = 0x4100; + private static const CHUNK_VERTICES:int = 0x4110; + private static const CHUNK_FACES:int = 0x4120; + private static const CHUNK_FACESMATERIAL:int = 0x4130; + private static const CHUNK_MAPPINGCOORDS:int = 0x4140; + //private static const CHUNK_OBJECTCOLOR:int = 0x4165; + private static const CHUNK_TRANSFORMATION:int = 0x4160; + //private static const CHUNK_MESHANIMATION:int = 0xB002; + private static const CHUNK_MATERIAL:int = 0xAFFF; + + private var data:ByteArray; + private var objectDatas:Object; + private var animationDatas:Array; + private var materialDatas:Object; + + public var objects:Vector.; + public var parents:Vector.; + public var materials:Vector.; + public var textureMaterials:Vector.; + + public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1):void { + if (data.bytesAvailable < 6) return; + this.data = data; + data.endian = Endian.LITTLE_ENDIAN; + parse3DSChunk(data.position, data.bytesAvailable); + objects = new Vector.(); + parents = new Vector.(); + materials = new Vector.(); + textureMaterials = new Vector.(); + buildContent(texturesBaseURL, scale); + data = null; + objectDatas = null; + animationDatas = null; + materialDatas = null; + } + + private function readChunkInfo(dataPosition:int):ChunkInfo { + data.position = dataPosition; + var chunkInfo:ChunkInfo = new ChunkInfo(); + chunkInfo.id = data.readUnsignedShort(); + chunkInfo.size = data.readUnsignedInt(); + chunkInfo.dataSize = chunkInfo.size - 6; + chunkInfo.dataPosition = data.position; + chunkInfo.nextChunkPosition = dataPosition + chunkInfo.size; + return chunkInfo; + } + + private function parse3DSChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + data.position = dataPosition; + switch (chunkInfo.id) { + // Главный + case CHUNK_MAIN: + parseMainChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + parse3DSChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMainChunk(dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Версия + case CHUNK_VERSION: + //version = data.readUnsignedInt(); + break; + // 3D-сцена + case CHUNK_SCENE: + parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Анимация + case CHUNK_ANIMATION: + parseAnimationChunk(chunkInfo.dataPosition, chunkInfo.dataSize); + break; + } + parseMainChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parse3DChunk(dataPosition:int, bytesAvailable:int):void { + while (bytesAvailable >= 6) { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Материал + case CHUNK_MATERIAL: + // Парсим материал + var material:MaterialData = new MaterialData(); + parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Объект + case CHUNK_OBJECT: + parseObject(chunkInfo); + break; + } + dataPosition = chunkInfo.nextChunkPosition; + bytesAvailable -= chunkInfo.size; + } + } + + private function parseObject(chunkInfo:ChunkInfo):void { + // Создаём список объектов, если надо + if (objectDatas == null) { + objectDatas = new Object(); + } + // Создаём данные объекта + var object:ObjectData = new ObjectData(); + // Получаем название объекта + object.name = getString(chunkInfo.dataPosition); + // Помещаем данные объекта в список + objectDatas[object.name] = object; + // Парсим объект + var offset:int = object.name.length + 1; + parseObjectChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + private function parseObjectChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Меш + case CHUNK_TRIMESH: + parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Источник света + case 0x4600: + break; + // Камера + case 0x4700: + break; + } + parseObjectChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMeshChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Вершины + case CHUNK_VERTICES: + parseVertices(object); + break; + // UV + case CHUNK_MAPPINGCOORDS: + parseUVs(object); + break; + // Трансформация + case CHUNK_TRANSFORMATION: + parseMatrix(object); + break; + // Грани + case CHUNK_FACES: + parseFaces(object, chunkInfo); + break; + } + parseMeshChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseVertices(object:ObjectData):void { + var num:int = data.readUnsignedShort(); + object.vertices = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.vertices[j++] = data.readFloat(); + object.vertices[j++] = data.readFloat(); + object.vertices[j++] = data.readFloat(); + } + } + + private function parseUVs(object:ObjectData):void { + var num:int = data.readUnsignedShort(); + object.uvs = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.uvs[j++] = data.readFloat(); + object.uvs[j++] = data.readFloat(); + } + } + + private function parseMatrix(object:ObjectData):void { + object.a = data.readFloat(); + object.e = data.readFloat(); + object.i = data.readFloat(); + object.b = data.readFloat(); + object.f = data.readFloat(); + object.j = data.readFloat(); + object.c = data.readFloat(); + object.g = data.readFloat(); + object.k = data.readFloat(); + object.d = data.readFloat(); + object.h = data.readFloat(); + object.l = data.readFloat(); + } + + private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void { + var num:int = data.readUnsignedShort(); + object.faces = new Vector.(); + for (var i:int = 0, j:int = 0; i < num; i++) { + object.faces[j++] = data.readUnsignedShort(); + object.faces[j++] = data.readUnsignedShort(); + object.faces[j++] = data.readUnsignedShort(); + data.position += 2; // Пропускаем флаг отрисовки рёбер + } + var offset:int = 2 + 8*num; + parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); + } + + private function parseFacesChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Поверхности + case CHUNK_FACESMATERIAL: + parseSurface(object); + break; + } + parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseSurface(object:ObjectData):void { + // Создаём список поверхностей, если надо + if (object.surfaces == null) { + object.surfaces = new Object(); + } + // Создаём данные поверхности + var surface:Vector. = new Vector.; + // Помещаем данные поверхности в список + object.surfaces[getString(data.position)] = surface; + // Получаем грани поверхности + var num:int = data.readUnsignedShort(); + for (var i:int = 0; i < num; i++) { + surface[i] = data.readUnsignedShort(); + } + } + + private function parseAnimationChunk(dataPosition:int, bytesAvailable:int):void { + while (bytesAvailable >= 6) { + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Анимация объекта + case 0xB001: + case 0xB002: + case 0xB003: + case 0xB004: + case 0xB005: + case 0xB006: + case 0xB007: + if (animationDatas == null) { + animationDatas = new Array(); + } + var animation:AnimationData = new AnimationData(); + animationDatas.push(animation); + parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Таймлайн + case 0xB008: + break; + } + dataPosition = chunkInfo.nextChunkPosition; + bytesAvailable -= chunkInfo.size; + } + } + + private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Идентификация объекта и его связь + case 0xB010: + // Имя объекта + animation.objectName = getString(data.position); + data.position += 4; + // Индекс родительского объекта в линейном списке объектов сцены + animation.parentIndex = data.readUnsignedShort(); + break; + // Имя dummy объекта + case 0xB011: + animation.objectName = getString(data.position); + break; + // Точка привязки объекта (pivot) + case 0xB013: + animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Смещение объекта относительно родителя + case 0xB020: + data.position += 20; + animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Поворот объекта относительно родителя (angle-axis) + case 0xB021: + data.position += 20; + animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); + break; + // Масштабирование объекта относительно родителя + case 0xB022: + data.position += 20; + animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); + break; + } + parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMaterialChunk(material:MaterialData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Имя материала + case 0xA000: + parseMaterialName(material); + break; + // Ambient color + case 0xA010: + break; + // Diffuse color + case 0xA020: + data.position = chunkInfo.dataPosition + 6; + material.color = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); + break; + // Specular color + case 0xA030: + break; + // Shininess percent + case 0xA040: + data.position = chunkInfo.dataPosition + 6; + material.glossiness = data.readUnsignedShort(); + break; + // Shininess strength percent + case 0xA041: + data.position = chunkInfo.dataPosition + 6; + material.specular = data.readUnsignedShort(); + break; + // Transperensy + case 0xA050: + data.position = chunkInfo.dataPosition + 6; + material.transparency = data.readUnsignedShort(); + break; + // Texture map 1 + case 0xA200: + material.diffuseMap = new MapData(); + parseMapChunk(material.name, material.diffuseMap, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Texture map 2 + case 0xA33A: + break; + // Opacity map + case 0xA210: + material.opacityMap = new MapData(); + parseMapChunk(material.name, material.opacityMap, chunkInfo.dataPosition, chunkInfo.dataSize); + break; + // Bump map + case 0xA230: + break; + // Shininess map + case 0xA33C: + break; + // Specular map + case 0xA204: + break; + // Self-illumination map + case 0xA33D: + break; + // Reflection map + case 0xA220: + break; + } + parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function parseMaterialName(material:MaterialData):void { + // Создаём список материалов, если надо + if (materialDatas == null) { + materialDatas = new Object(); + } + // Получаем название материала + material.name = getString(data.position); + // Помещаем данные материала в список + materialDatas[material.name] = material; + } + + private function parseMapChunk(materialName:String, map:MapData, dataPosition:int, bytesAvailable:int):void { + if (bytesAvailable < 6) return; + var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); + switch (chunkInfo.id) { + // Имя файла + case 0xA300: + map.filename = getString(chunkInfo.dataPosition).toLowerCase(); + break; + case 0xA351: + // Параметры текстурирования + //trace("MAP OPTIONS", data.readShort().toString(2)); + break; + // Масштаб по U + case 0xA354: + map.scaleU = data.readFloat(); + break; + // Масштаб по V + case 0xA356: + map.scaleV = data.readFloat(); + break; + // Смещение по U + case 0xA358: + map.offsetU = data.readFloat(); + break; + // Смещение по V + case 0xA35A: + map.offsetV = data.readFloat(); + break; + // Угол поворота + case 0xA35C: + map.rotation = data.readFloat(); + break; + } + parseMapChunk(materialName, map, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); + } + + private function buildContent(texturesBaseURL:String, scale:Number):void { + // Расчёт матриц текстурных материалов + for (var materialName:String in materialDatas) { + var materialData:MaterialData = materialDatas[materialName]; + var mapData:MapData = materialData.diffuseMap; + if (mapData != null) { + var materialMatrix:Matrix = new Matrix(); + var rot:Number = mapData.rotation*Math.PI/180; + materialMatrix.translate(-mapData.offsetU, mapData.offsetV); + materialMatrix.translate(-0.5, -0.5); + materialMatrix.rotate(-rot); + materialMatrix.scale(mapData.scaleU, mapData.scaleV); + materialMatrix.translate(0.5, 0.5); + materialData.matrix = materialMatrix; + var textureMaterial:TextureMaterial = new TextureMaterial(); + textureMaterial.name = materialName; + textureMaterial.diffuseMapURL = texturesBaseURL + mapData.filename; + textureMaterial.opacityMapURL = (materialData.opacityMap != null) ? (texturesBaseURL + materialData.opacityMap.filename) : null; + materialData.material = textureMaterial; + textureMaterial.name = materialData.name; + textureMaterials.push(textureMaterial); + } else { + var fillMaterial:FillMaterial = new FillMaterial(materialData.color); + materialData.material = fillMaterial; + fillMaterial.name = materialData.name; + } + materials.push(materialData.material); + } + var objectName:String; + var objectData:ObjectData; + var object:Object3D; + // В сцене есть иерархически связанные оьъекты и (или) указаны данные о трансформации объектов. + if (animationDatas != null) { + if (objectDatas != null) { + var i:int; + var length:int = animationDatas.length; + var animationData:AnimationData; + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objectName = animationData.objectName; + objectData = objectDatas[objectName]; + // Проверка на инстансы + if (objectData != null) { + for (var j:int = i + 1, nameCounter:int = 1; j < length; j++) { + var animationData2:AnimationData = animationDatas[j]; + if (!animationData2.isInstance && objectName == animationData2.objectName) { + // Найдено совпадение имени объекта в проверяемой секции анимации. Создаём референс. + var newObjectData:ObjectData = new ObjectData(); + var newName:String = objectName + nameCounter++; + newObjectData.name = newName; + objectDatas[newName] = newObjectData; + animationData2.objectName = newName; + newObjectData.vertices = objectData.vertices; + newObjectData.uvs = objectData.uvs; + newObjectData.faces = objectData.faces; + newObjectData.surfaces = objectData.surfaces; + newObjectData.a = objectData.a; + newObjectData.b = objectData.b; + newObjectData.c = objectData.c; + newObjectData.d = objectData.d; + newObjectData.e = objectData.e; + newObjectData.f = objectData.f; + newObjectData.g = objectData.g; + newObjectData.h = objectData.h; + newObjectData.i = objectData.i; + newObjectData.j = objectData.j; + newObjectData.k = objectData.k; + newObjectData.l = objectData.l; + } + } + } + // Если меш + if (objectData != null && objectData.vertices != null) { + // Создание полигонального объекта + object = new Mesh(); + buildMesh(object as Mesh, objectData, animationData, scale); + } else { + // Создание пустого 3д-объекта + object = new Object3D(); + } + object.name = objectName; + animationData.object = object; + if (animationData.position != null) { + object.x = animationData.position.x*scale; + object.y = animationData.position.y*scale; + object.z = animationData.position.z*scale; + } + if (animationData.rotation != null) { + object.rotationX = animationData.rotation.x; + object.rotationY = animationData.rotation.y; + object.rotationZ = animationData.rotation.z; + } + if (animationData.scale != null) { + object.scaleX = animationData.scale.x; + object.scaleY = animationData.scale.y; + object.scaleZ = animationData.scale.z; + } + } + // Добавление объектов + for (i = 0; i < length; i++) { + animationData = animationDatas[i]; + objects.push(animationData.object); + parents.push((animationData.parentIndex == 0xFFFF) ? null : AnimationData(animationDatas[animationData.parentIndex]).object); + } + } + // В сцене нет иерархически связанных объектов и не заданы трансформации для объектов. В контейнер добавляются только полигональные объекты. + } else { + for (objectName in objectDatas) { + objectData = objectDatas[objectName]; + if (objectData.vertices != null) { + object = new Mesh(); + object.name = objectName; + buildMesh(object as Mesh, objectData, null, scale); + objects.push(object); + parents.push(null); + } + } + } + } + + private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, scale:Number):void { + var vertices:Vector. = new Vector.(); + var faces:Vector. = new Vector.(); + var numVertices:int = 0; + var numFaces:int = 0; + var n:int; + var m:int; + var face:Face; + var vertex:Vertex; + var correct:Boolean = false; + if (animationData != null) { + var a:Number = objectData.a; + var b:Number = objectData.b; + var c:Number = objectData.c; + var d:Number = objectData.d; + var e:Number = objectData.e; + var f:Number = objectData.f; + var g:Number = objectData.g; + var h:Number = objectData.h; + var i:Number = objectData.i; + var j:Number = objectData.j; + var k:Number = objectData.k; + var l:Number = objectData.l; + var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); + objectData.a = (-g*j + f*k)*det; + objectData.b = (c*j - b*k)*det; + objectData.c = (-c*f + b*g)*det; + objectData.d = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; + objectData.e = (g*i - e*k)*det; + objectData.f = (-c*i + a*k)*det; + objectData.g = (c*e - a*g)*det; + objectData.h = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; + objectData.i = (-f*i + e*j)*det; + objectData.j = (b*i - a*j)*det; + objectData.k = (-b*e + a*f)*det; + objectData.l = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; + if (animationData.pivot != null) { + objectData.d -= animationData.pivot.x; + objectData.h -= animationData.pivot.y; + objectData.l -= animationData.pivot.z; + } + correct = true; + } + // Создание и корректировка вершин + var uv:Boolean = objectData.uvs != null && objectData.uvs.length > 0; + for (n = 0, m = 0; n < objectData.vertices.length;) { + vertex = new Vertex(); + if (correct) { + var x:Number = objectData.vertices[n++]; + var y:Number = objectData.vertices[n++]; + var z:Number = objectData.vertices[n++]; + vertex.x = objectData.a*x + objectData.b*y + objectData.c*z + objectData.d; + vertex.y = objectData.e*x + objectData.f*y + objectData.g*z + objectData.h; + vertex.z = objectData.i*x + objectData.j*y + objectData.k*z + objectData.l; + } else { + vertex.x = objectData.vertices[n++]; + vertex.y = objectData.vertices[n++]; + vertex.z = objectData.vertices[n++]; + } + vertex.x *= scale; + vertex.y *= scale; + vertex.z *= scale; + if (uv) { + vertex.u = objectData.uvs[m++]; + vertex.v = 1 - objectData.uvs[m++]; + } + vertex.transformID = -1; + vertices[numVertices++] = vertex; + vertex.next = mesh.vertexList; + mesh.vertexList = vertex; + } + // Создание граней + var last:Face; + for (n = 0; n < objectData.faces.length;) { + face = new Face(); + face.wrapper = new Wrapper(); + face.wrapper.next = new Wrapper(); + face.wrapper.next.next = new Wrapper(); + face.wrapper.vertex = vertices[objectData.faces[n++]]; + face.wrapper.next.vertex = vertices[objectData.faces[n++]]; + face.wrapper.next.next.vertex = vertices[objectData.faces[n++]]; + faces[numFaces++] = face; + if (last != null) { + last.next = face; + } else { + mesh.faceList = face; + } + last = face; + } + // Назначение материалов + if (objectData.surfaces != null) { + for (var key:String in objectData.surfaces) { + var surface:Vector. = objectData.surfaces[key]; + var materialData:MaterialData = materialDatas[key]; + var material:Material = materialData.material; + for (n = 0; n < surface.length; n++) { + face = faces[surface[n]]; + face.material = material; + // Коррекция UV-координат + if (materialData.matrix != null) { + for (var w:Wrapper = face.wrapper; w != null; w = w.next) { + vertex = w.vertex; + if (vertex.transformID < 0) { + var u:Number = vertex.u; + var v:Number = vertex.v; + vertex.u = materialData.matrix.a*u + materialData.matrix.b*v + materialData.matrix.tx; + vertex.v = materialData.matrix.c*u + materialData.matrix.d*v + materialData.matrix.ty; + vertex.transformID = 0; + } + } + } + } + } + } + // Назначение материала по-умолчанию для граней без поверхностей + var defaultMaterial:FillMaterial = new FillMaterial(0x7F7F7F); + defaultMaterial.name = "default"; + for (face = mesh.faceList; face != null; face = face.next) { + if (face.material == null) { + face.material = defaultMaterial; + } + } + // Расчёт нормалей + mesh.calculateNormals(true); + mesh.calculateBounds(); + } + + private function getString(index:int):String { + data.position = index; + var charCode:int; + var res:String = ""; + while ((charCode = data.readByte()) != 0) { + res += String.fromCharCode(charCode); + } + return res; + } + + private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { + var res:Vector3D = new Vector3D(); + var s:Number = Math.sin(angle); + var c:Number = Math.cos(angle); + var t:Number = 1 - c; + var k:Number = x*y*t + z*s; + var half:Number; + if (k >= 1) { + half = angle/2; + res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = -Math.PI/2; + res.x = 0; + return res; + } + if (k <= -1) { + half = angle/2; + res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); + res.y = Math.PI/2; + res.x = 0; + return res; + } + res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); + res.y = -Math.asin(x*y*t + z*s); + res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); + return res; + } + + } +} + +import alternativa.engine3d.core.Object3D; +import alternativa.engine3d.materials.Material; + +import flash.geom.Matrix; +import flash.geom.Vector3D; + +class MaterialData { + public var name:String; + public var color:int; + public var specular:int; + public var glossiness:int; + public var transparency:int; + public var diffuseMap:MapData; + public var opacityMap:MapData; + public var matrix:Matrix; + public var material:Material; +} + +class MapData { + public var filename:String; + public var scaleU:Number = 1; + public var scaleV:Number = 1; + public var offsetU:Number = 0; + public var offsetV:Number = 0; + public var rotation:Number = 0; +} + +class ObjectData { + public var name:String; + public var vertices:Vector.; + public var uvs:Vector.; + public var faces:Vector.; + public var surfaces:Object; + public var a:Number; + public var b:Number; + public var c:Number; + public var d:Number; + public var e:Number; + public var f:Number; + public var g:Number; + public var h:Number; + public var i:Number; + public var j:Number; + public var k:Number; + public var l:Number; +} + +class AnimationData { + public var objectName:String; + public var object:Object3D; + public var parentIndex:int; + public var pivot:Vector3D; + public var position:Vector3D; + public var rotation:Vector3D; + public var scale:Vector3D; + public var isInstance:Boolean; +} + +class ChunkInfo { + public var id:int; + public var size:int; + public var dataSize:int; + public var dataPosition:int; + public var nextChunkPosition:int; +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/ParserCollada.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/ParserCollada.as new file mode 100644 index 0000000..f6c5daf --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/ParserCollada.as @@ -0,0 +1,599 @@ +package alternativa.engine3d.loaders { + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.loaders.collada.DaeAnimatedObject; + import alternativa.engine3d.loaders.collada.DaeDocument; + import alternativa.engine3d.loaders.collada.DaeMaterial; + import alternativa.engine3d.loaders.collada.DaeNode; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + + import flash.geom.Matrix3D; + + /** + * Класс выполняет парсинг xml коллады. + */ + public class ParserCollada { + + /** + * Список объектов. + */ + public var objects:Vector.; + /** + * Список родителей объектов. Количество и порядок расположения элементов соответствует массиву objects. + * + * @see #objects + */ + public var parents:Vector.; + /** + * Список корневых (без родителей) объектов. + */ + public var hierarchy:Vector.; + /** + * Список камер. + */ + public var cameras:Vector.; + /** + * Список всех материалов. + * + * @see #textureMaterials + */ + public var materials:Vector.; + /** + * Список текстурных материалов. + * Можно использовать класс MaterialLoader для загрузки текстур этих материалов. + * + * @see #materials + * @see MaterialLoader + */ + public var textureMaterials:Vector.; + /** + * Массив анимаций. + */ + public var animations:Vector.; + + /** + * Создает экземпляр парсера + */ + public function ParserCollada() { + } + + /** + * Зачищает все ссылки на внешние объекты. + */ + public function clean():void { + objects = null; + parents = null; + hierarchy = null; + cameras = null; + animations = null; + materials = null; + textureMaterials = null; + } + + /** + * Инициализация перед парсингом. + */ + private function init(data:XML):DaeDocument { + clean(); + + objects = new Vector.(); + parents = new Vector.(); + hierarchy = new Vector.(); + cameras = new Vector.(); + animations = new Vector.(); + materials = new Vector.(); + textureMaterials = new Vector.(); + return new DaeDocument(data); + } + + /** + * Метод распарсивает xml коллады и заполняет массивы objects, parents, hierarchy, cameras, materials, textureMaterials, animations. + * Для загрузки текстур сцены можно использовать класс MaterialLoader. + * + * @param data xml содержимое коллады + * @param baseURL адрес относительно которого выполняется поиск текстур. Путь к файлу коллады. + * Должен соответствовать спецификации URI. Например file:///C:/test.dae или /C:/test.dae для полных путей или test.dae, ./test.dae для относительных. + * @param skipEmptyObjects при значении true объекты без содержимого не будут создаваться без необходимости. + * + * @example Пример загрузки файла коллады, парсинга, загрузки текстур и создания иерархии: + * package { + * + * import alternativa.engine3d.containers.AverageZContainer; + * import alternativa.engine3d.core.Object3D; + * import alternativa.engine3d.core.Object3DContainer; + * import alternativa.engine3d.loaders.MaterialLoader; + * import alternativa.engine3d.loaders.ParserCollada; + * + * import flash.display.Sprite; + * import flash.events.Event; + * import flash.geom.Matrix3D; + * import flash.net.URLLoader; + * import flash.net.URLRequest; + * + * public class ColladaExample extends Sprite { + * + * private const modelURL:String = "model.dae"; + * + * private var loader:URLLoader; + * private var materialLoader:MaterialLoader; + * + * public function ColladaExample() { + * loader = new URLLoader(); + * loader.addEventListener(Event.COMPLETE, onModelLoad); + * loader.load(new URLRequest(modelURL)); + * } + * + * private function onModelLoad(e:Event):void { + * var collada:ParserCollada = new ParserCollada(); + * collada.parse(XML(loader.data), modelURL); + * + * var container:AverageZContainer = new AverageZContainer(); + * // Создаем иерархию объектов + * var objects:Vector. = collada.hierarchy; + * for (var o:int = 0, count:int = objects.length; o < count; o++) { + * container.addChild(objects[o]); + * } + * + * // Начинаем загрузку текстур материалов + * materialLoader = new MaterialLoader(); + * materialLoader.addEventListener(Event.COMPLETE, onMaterialsLoad); + * materialLoader.load(collada.textureMaterials); + * } + * + * private function onMaterialsLoad(e:Event):void { + * trace("Loading complete"); + * } + * + * }} + * + * @see MaterialLoader + * @see #objects + * @see #parents + * @see #hierarchy + * @see #cameras + * @see #materials + * @see #textureMaterials + */ + public function parse(data:XML, baseURL:String = null, skipEmptyObjects:Boolean = true):void { + var document:DaeDocument = init(data); + if (document.scene != null) { + parseNodes(document.scene.nodes, null, skipEmptyObjects); + parseMaterials(document.materials, baseURL); + } + } + + /** + * Метод распарсивает xml коллады и заполняет массивы objects, parents, hierarchy, cameras, materials, textureMaterials, animations. + * Для загрузки текстур сцены можно использовать класс MaterialLoader. + * После парсинга для объектов, которые содержат детей, но не являются контейнерами, будут созданы контейнеры. + * + * @param data xml содержимое коллады + * @param baseURL адрес относительно которого выполняется поиск текстур. Путь к файлу коллады. + * Должен соответствовать спецификации URI. Например file:///C:/test.dae или /C:/test.dae для полных путей или test.dae, ./test.dae для относительных. + * @param skipEmptyObjects при значении true объекты без содержимого не будут создаваться без необходимости. + * + * @see MaterialLoader + * @see #objects + * @see #parents + * @see #hierarchy + * @see #cameras + * @see #materials + * @see #textureMaterials + */ + public function parseForAnimation(data:XML, baseURL:String = null, skipEmptyObjects:Boolean = true):void { + var document:DaeDocument = init(data); + if (document.scene != null) { + parseNodesForAnimation(document.scene.nodes, null, skipEmptyObjects); + parseMaterials(document.materials, baseURL); + } + } + + /** + * Метод распарсивает xml коллады и заполняет массивы objects, parents, hierarchy, cameras, materials, textureMaterials, animations. + * Для загрузки текстур сцены можно использовать класс MaterialLoader. + * После парсинга объекты, у которых родитель не является контейнером, будут переведены в систему координат ближайшего родительского контейнера. + * + * @param data xml содержимое коллады + * @param baseURL адрес относительно которого выполняется поиск текстур. Путь к файлу коллады. + * Должен соответствовать спецификации URI. Например file:///C:/test.dae или /C:/test.dae для полных путей или test.dae, ./test.dae для относительных. + * @param skipEmptyObjects при значении true объекты без содержимого не будут создаваться без необходимости. + * + * @see MaterialLoader + * @see #objects + * @see #parents + * @see #hierarchy + * @see #cameras + * @see #materials + * @see #textureMaterials + */ + public function parseForStatic(data:XML, baseURL:String = null, skipEmptyObjects:Boolean = true):void { + var document:DaeDocument = init(data); + if (document.scene != null) { + parseNodesForStatic(document.scene.nodes, null, null, skipEmptyObjects); + parseMaterials(document.materials, baseURL); + } + } + + /** + * Проверяет есть ли в иерархии объекты с заданными параметрами + * + * @param skipEmptyObjects Если установлен в true, будет пропускать ноды без объектов + * @param skinsOnly Если установлен в true, будут пропускаться все ноды кроме тех, что содержат скин. + */ + private function hasSignifiantChildren(node:DaeNode, skipEmptyObjects:Boolean, skinsOnly:Boolean):Boolean { + var nodes:Vector. = node.nodes; + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var child:DaeNode = nodes[i]; + child.parse(); + if (child.skins != null) { + return true; + } else { + if (!skinsOnly && !node.skinOrRootJoint && (!skipEmptyObjects || child.objects != null)) { + return true; + } + } + if (hasSignifiantChildren(child, skipEmptyObjects, skinsOnly)) { + return true; + } + } + return false; + } + + /** + * Добавляет компоненты анимированного объекта в списки objects, parents, hierarchy, cameras, animations и в родительский контейнер. + */ + private function addObject(animatedObject:DaeAnimatedObject, parent:Object3D):Object3D { + var object:Object3D = animatedObject.object; + this.objects.push(object); + this.parents.push(parent); + if (parent == null) { + this.hierarchy.push(object); + } + var container:Object3DContainer = parent as Object3DContainer; + if (container != null) { + container.addChild(object); + } + if (object is Camera3D) { + this.cameras.push(object as Camera3D); + } + if (animatedObject.animation != null) { + animatedObject.animation.updateLength(); + this.animations.push(animatedObject.animation); + } + return object; + } + + /** + * Добавляет объекты в списки objects, parents, hierarchy, cameras, animations и в родительский контейнер. + * + * @return первый объект + */ + private function addObjects(animatedObjects:Vector., parent:Object3D):Object3D { + var first:Object3D = addObject(animatedObjects[0], parent); + for (var i:int = 1, count:int = animatedObjects.length; i < count; i++) { + addObject(animatedObjects[i], parent); + } + return first; + } + + + private function parseNodes(nodes:Vector., parent:Object3D, skipEmptyObjects:Boolean, skinsOnly:Boolean = false):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + node.parse(); + var object:Object3D; + if (node.skins != null) { + object = addObjects(node.skins, parent); + } else { + if (!node.skinOrRootJoint && !skinsOnly) { + if (node.objects != null) { + object = addObjects(node.objects, parent); + } else { + if (!skipEmptyObjects) { + object = new Object3D(); + object.name = node.name; + object = addObject(node.applyAnimation(node.applyTransformations(object)), parent); + } + } + } + } + // Если это кость или скин, парсим только скины у детей + skinsOnly = skinsOnly || node.skinOrRootJoint; + if (object == null) { + if (hasSignifiantChildren(node, skipEmptyObjects, skinsOnly)) { + object = new Object3D(); + object.name = node.name; + parseNodes(node.nodes, addObject(node.applyAnimation(node.applyTransformations(object)), parent), skipEmptyObjects, skinsOnly); + } + } else { + parseNodes(node.nodes, object, skipEmptyObjects, skinsOnly); + } + } + } + + private function parseNodesForAnimation(nodes:Vector., parent:Object3DContainer, skipEmptyObjects:Boolean, skinsOnly:Boolean = false):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + node.parse(); + var container:Object3DContainer = null; + if (node.skins != null) { + // Основная кость скина + addObjects(node.skins, parent); + } else { + if (!node.skinOrRootJoint && !skinsOnly) { + if (node.objects != null) { + container = addObjects(node.objects, parent) as Object3DContainer; + } else { + // Нет объектов в ноде + if (!skipEmptyObjects) { + var object:Object3D = new Object3D(); + object.name = node.name; + addObject(node.applyAnimation(node.applyTransformations(object)), parent); + } + } + } + } + // Парсим детей + // Если это кость или скин, парсим только скины у детей + skinsOnly = skinsOnly || node.skinOrRootJoint; + if (container == null) { + if (hasSignifiantChildren(node, skipEmptyObjects, skinsOnly)) { + container = new Object3DContainer(); + if (node.name != null) { + container.name = node.name + "-container"; + } + addObject(node.applyAnimation(node.applyTransformations(container)), parent); + parseNodesForAnimation(node.nodes, container, skipEmptyObjects, skinsOnly); + } + } else { + parseNodesForAnimation(node.nodes, container, skipEmptyObjects, skinsOnly); + } + } + } + + private function appendMatrixToObjects(objects:Vector., append:Matrix3D):void { + for (var i:int = 0, count:int = objects.length; i < count; i++) { + var object:Object3D = objects[i].object; + var matrix:Matrix3D = object.getMatrix(); + matrix.append(append); + object.setMatrix(matrix); + } + } + + private function parseNodesForStatic(nodes:Vector., parent:Object3DContainer, toParentMatrix:Matrix3D, skipEmptyObjects:Boolean, skinsOnly:Boolean = false):void { + for (var i:int = 0, count:int = nodes.length; i < count; i++) { + var node:DaeNode = nodes[i]; + node.parse(); + var container:Object3DContainer = null; + if (node.skins != null) { + if (toParentMatrix != null) { + appendMatrixToObjects(node.skins, toParentMatrix); + } + // Основная кость скина + addObjects(node.skins, parent); + } else { + if (!node.skinOrRootJoint && !skinsOnly) { + if (node.objects != null) { + if (toParentMatrix != null) { + appendMatrixToObjects(node.skins, toParentMatrix); + } + container = addObjects(node.objects, parent) as Object3DContainer; + } else { + // Нет объектов в ноде + if (!skipEmptyObjects) { + var object:Object3D = new Object3DContainer(); + object.name = node.name; + addObject(node.applyAnimation(node.applyTransformations(object, null, toParentMatrix)), parent); + } + } + } + } + // Парсим детей + // Если это кость или скин, парсим только скины у детей + skinsOnly = skinsOnly || node.skinOrRootJoint; + if (container == null) { + if (toParentMatrix == null) { + toParentMatrix = node.getMatrix(); + } else { + toParentMatrix.append(node.getMatrix()); + } + parseNodesForStatic(node.nodes, parent, toParentMatrix, skipEmptyObjects, skinsOnly); + } else { + parseNodesForStatic(node.nodes, container, null, skipEmptyObjects, skinsOnly); + } + } + } + + private function parseMaterials(materials:Object, baseURL:String):void { + var tmaterial:TextureMaterial; + for each (var material:DaeMaterial in materials) { + if (material.used) { + material.parse(); + this.materials.push(material.material); + if (material.material is TextureMaterial) { + tmaterial = material.material as TextureMaterial; + if (tmaterial.texture == null) { + // Филлы тоже задаются текстурным материалом на данный момент + textureMaterials.push(tmaterial); + } + } + } + } + if (baseURL != null) { + baseURL = fixURL(baseURL); + var end:int = baseURL.lastIndexOf("/"); + var base:String = (end < 0) ? "" : baseURL.substring(0, end + 1); + for each (tmaterial in textureMaterials) { + if (tmaterial.diffuseMapURL != null) { + tmaterial.diffuseMapURL = resolveURL(fixURL(tmaterial.diffuseMapURL), base); + } + if (tmaterial.opacityMapURL != null) { + tmaterial.opacityMapURL = resolveURL(fixURL(tmaterial.opacityMapURL), base); + } + } + } else { + for each (tmaterial in textureMaterials) { + if (tmaterial.diffuseMapURL != null) { + tmaterial.diffuseMapURL = fixURL(tmaterial.diffuseMapURL); + } + if (tmaterial.opacityMapURL != null) { + tmaterial.opacityMapURL = fixURL(tmaterial.opacityMapURL); + } + } + } + } + + /** + * @private + * Приводит урл к следующему виду: + * Обратные слеши в пути заменяет на прямые + * Три прямых слеша после схемы file: + */ + private function fixURL(url:String):String { + var pathStart:int = url.indexOf("://"); + pathStart = (pathStart < 0) ? 0 : pathStart + 3; + var pathEnd:int = url.indexOf("?", pathStart); + pathEnd = (pathEnd < 0) ? url.indexOf("#", pathStart) : pathEnd; + var path:String = url.substring(pathStart, (pathEnd < 0) ? 0x7FFFFFFF : pathEnd); + path = path.replace(/\\/g, "/"); + var fileIndex:int = url.indexOf("file://"); + if (fileIndex >= 0) { + if (url.charAt(pathStart) == "/") { + return "file://" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + return "file:///" + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + return url.substring(0, pathStart) + path + ((pathEnd >= 0) ? url.substring(pathEnd) : ""); + } + + // public function resolveTest(url:String, base:String):void { + // trace('was::"' + base + '" "' + url + '"'); + // trace('now::"' + resolveURL(fixURL(url), fixURL(base)) + '"'); + // } + + /** + * @private + */ + private function mergePath(path:String, base:String, relative:Boolean = false):String { + var baseParts:Array = base.split("/"); + var parts:Array = path.split("/"); + for (var i:int = 0, count:int = parts.length; i < count; i++) { + var part:String = parts[i]; + if (part == "..") { + var basePart:String = baseParts.pop(); + while (basePart == "." || basePart == "" && basePart != null) basePart = baseParts.pop(); + if (relative) { + if (basePart == "..") { + baseParts.push("..", ".."); + } else if (basePart == null) { + baseParts.push(".."); + } + } + } else { + baseParts.push(part); + } + } + return baseParts.join("/"); + } + + /** + * @private + * Конвертирует относительные пути в полные + */ + private function resolveURL(url:String, base:String):String { + // http://labs.apache.org/webarch/uri/rfc/rfc3986.html + if (url.charAt(0) == "." && url.charAt(1) == "/") { + // Файл в той же папке + return base + url.substring(2); + } else if (url.charAt(0) == "/") { + // Полный путь + return url; + } else if (url.charAt(0) == "." && url.charAt(1) == ".") { + // Выше по уровню + var queryAndFragmentIndex:int = url.indexOf("?"); + queryAndFragmentIndex = (queryAndFragmentIndex < 0) ? url.indexOf("#") : queryAndFragmentIndex; + var path:String; + var queryAndFragment:String; + if (queryAndFragmentIndex < 0) { + queryAndFragment = ""; + path = url; + } else { + queryAndFragment = url.substring(queryAndFragmentIndex); + path = url.substring(0, queryAndFragmentIndex); + } + // Делим базовый урл на составные части + var bPath:String; + var bSlashIndex:int = base.indexOf("/"); + var bShemeIndex:int = base.indexOf(":"); + var bAuthorityIndex:int = base.indexOf("//"); + if (bAuthorityIndex < 0 || bAuthorityIndex > bSlashIndex) { + if (bShemeIndex >= 0 && bShemeIndex < bSlashIndex) { + // Присутствует схема, нет домена + var bSheme:String = base.substring(0, bShemeIndex + 1); + bPath = base.substring(bShemeIndex + 1); + if (bPath.charAt(0) == "/") { + return bSheme + "/" + mergePath(path, bPath.substring(1), false) + queryAndFragment; + } else { + return bSheme + mergePath(path, bPath, false) + queryAndFragment; + } + } else { + // Нет схемы, нет домена + if (base.charAt(0) == "/") { + return "/" + mergePath(path, base.substring(1), false) + queryAndFragment; + } else { + return mergePath(path, base, true) + queryAndFragment; + } + } + } else { + bSlashIndex = base.indexOf("/", bAuthorityIndex + 2); + var bAuthority:String; + if (bSlashIndex >= 0) { + bAuthority = base.substring(0, bSlashIndex + 1); + bPath = base.substring(bSlashIndex + 1); + return bAuthority + mergePath(path, bPath, false) + queryAndFragment; + } else { + bAuthority = base; + return bAuthority + "/" + mergePath(path, "", false); + } + } + } + var shemeIndex:int = url.indexOf(":"); + var slashIndex:int = url.indexOf("/"); + if (shemeIndex >= 0 && (shemeIndex < slashIndex || slashIndex < 0)) { + // Содержит схему + return url; + } + return base + "/" + url; + } + + /** + * Возвращает объект из массива object по его имени. + */ + public function getObjectByName(name:String):Object3D { + for (var i:int = 0, count:int = objects.length; i < count; i++) { + var object:Object3D = objects[i]; + if (object.name == name) { + return object; + } + } + return null; + } + + /** + * Возвращает анимацию из массива animation по объекту на который она ссылается. + */ + public function getAnimationByObject(object:Object3D):Animation { + for (var i:int = 0, count:int = animations.length; i < count; i++) { + var animation:Animation = animations[i]; + if (animation.object == object) { + return animation; + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as new file mode 100644 index 0000000..e57ebcc --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAlternativa3DObject.as @@ -0,0 +1,235 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.containers.ConflictContainer; + import alternativa.engine3d.containers.KDTree; + import alternativa.engine3d.containers.ZSortContainer; + import alternativa.engine3d.core.Clipping; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Object3DContainer; + import alternativa.engine3d.core.Sorting; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.LOD; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + import alternativa.engine3d.objects.Sprite3D; + + import flash.utils.getDefinitionByName; + + /** + * @private + */ + public class DaeAlternativa3DObject extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + public function DaeAlternativa3DObject(data:XML, document:DaeDocument) { + super(data, document); + } + + private function createObject(className:String):* { + try { + var ClassDef:Class = getDefinitionByName(className) as Class; + return new ClassDef(); + } catch (e:ReferenceError) { + trace("[ERROR]", e.message); + } + return null; + } + + public function parseContainer():Object3DContainer { + var container:Object3DContainer; + var classNameXML:XML = data.@className[0]; + switch (data.localName()) { + case "object3d": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new Object3DContainer(); + } + return setParams(container); + } + case "averageZ": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new ZSortContainer(); + } + return setParams(container); + } + case "conflict": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new ConflictContainer(); + } + return setParams(container); + } + case "kdTree": { + if (classNameXML != null) { + container = createObject(classNameXML.toString()); + } else { + container = new KDTree(); + } + return setParams(container); + } + } + return null; + } + + private function getClippingValue(clipping:XML):int { + switch (clipping.toString()) { + case "BOUND_CULLING": + return Clipping.BOUND_CULLING; + break; + case "FACE_CULLING": + return Clipping.FACE_CULLING; + break; + case "FACE_CLIPPING": + return Clipping.FACE_CLIPPING; + break; + } + return Clipping.BOUND_CULLING; + } + + private function getSortingValue(sorting:XML):int { + switch (sorting.toString()) { + case "STATIC_BSP": + return Sorting.STATIC_BSP; + break; + case "DYNAMIC_BSP": + return Sorting.DYNAMIC_BSP; + break; + case "NONE": + return Sorting.NONE; + break; + case "AVERAGE_Z": + return Sorting.AVERAGE_Z; + break; + } + return Sorting.NONE; + } + + public function parseSprite3D(material:Material = null):Sprite3D { + if (data.localName() == "sprite") { + var sprite:Sprite3D; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + sprite = createObject(classNameXML.toString()); + } else { + sprite = new Sprite3D(); + } + sprite.material = material; + var sortingXML:XML = data.sorting[0]; + var clippingXML:XML = data.clipping[0]; + if (sortingXML != null) { + sprite.sorting = getSortingValue(sortingXML); + } + if (clippingXML != null) { + sprite.clipping = getClippingValue(clippingXML); + } + return setParams(sprite); + } + return null; + } + + public function parseMesh(skin:Boolean = false):Mesh { + if (data.localName() == "mesh") { + var mesh:Mesh; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + mesh = createObject(classNameXML.toString()); + } else { + mesh = (skin) ? new Skin() : new Mesh(); + } + var sortingXML:XML = data.sorting[0]; + var clippingXML:XML = data.clipping[0]; + var optimizeXML:XML = data.optimizeBSP[0]; + if (clippingXML != null) { + mesh.clipping = getClippingValue(clippingXML); + } + var optimize:Boolean = (optimizeXML != null) ? optimizeXML.toString() != "false" : true; + if (sortingXML != null) { + mesh.sorting = getSortingValue(sortingXML); + if (mesh.sorting == Sorting.STATIC_BSP) { + mesh.calculateBSP(optimize); + } else if (mesh.sorting == Sorting.DYNAMIC_BSP && optimize) { + mesh.optimizeForDynamicBSP(); + } + } + return setParams(mesh); + } + return null; + } + + public function parseLOD():LOD { + if (data.localName() == "LOD" || data.localName() == "lod") { + var lod:LOD; + var classNameXML:XML = data.@className[0]; + if (classNameXML != null) { + lod = createObject(classNameXML.toString()); + } else { + lod = new LOD(); + } + var levels:XMLList = data.level; + var count:int = levels.length(); + var distances:Vector. = lod.lodDistances = new Vector.(count); + var objects:Vector. = lod.lodObjects = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var level:XML = levels[i]; + distances[i] = parseNumber(level.@distance[0]); + var node:DaeNode = document.findNode(level.@url[0]); + if (node != null) { + if (node.rootJoint != null) { + node = node.rootJoint; + node.parse(); + if (node.skins.length > 0) { + objects[i] = node.skins[0].object; + } + } else { + if (node.objects.length > 0) { + objects[i] = node.objects[0].object; + } + } + } else { + document.logger.logNotFoundError(level.@url[0]); + } + } + return setParams(lod); + } + return new LOD(); + } + + private function setParams(object:*):* { + var params:XMLList = data.param; + for (var i:int = 0, count:int = params.length(); i < count; i++) { + var param:XML = params[i]; + try { + var name:String = param.@name[0].toString(); + var value:String = param.text().toString(); + if (value == "true") { + object[name] = true; + } else if (value == "false") { + object[name] = false; + } else if ((value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') || (value.charAt(0) == "'" && value.charAt(value.length - 1) == "'")) { + object[name] = value; + } else { + if (value.indexOf(".") >= 0) { + object[name] = parseFloat(value); + } else if (value.indexOf(",") >= 0) { + value = value.replace(/,/, "."); + object[name] = parseFloat(value); + } else { + object[name] = parseInt(value); + } + } + } catch (e:Error) { + trace("[ERROR]", e.message); + } + } + return object; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as new file mode 100644 index 0000000..21990fc --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeAnimatedObject.as @@ -0,0 +1,20 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.core.Object3D; + + /** + * @private + */ + public class DaeAnimatedObject { + + public var object:Object3D; + public var animation:Animation; + + public function DaeAnimatedObject(object:Object3D, animation:Animation = null) { + this.object = object; + this.animation = animation; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeArray.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeArray.as new file mode 100644 index 0000000..8c44997 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeArray.as @@ -0,0 +1,41 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeArray extends DaeElement { + + use namespace collada; + + /** + * Массив значений типа String. + * Перед использованием вызвать parse(). + */ + public var array:Array; + + public function DaeArray(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get type():String { + return String(data.localName()); + } + + override protected function parseImplementation():Boolean { + array = parseStringArray(data); + var countXML:XML = data.@count[0]; + if (countXML != null) { + var count:int = parseInt(countXML.toString(), 10); + if (array.length < count) { + document.logger.logNotEnoughDataError(data.@count[0]); + return false; + } else { + array.length = count; + return true; + } + } + return false; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeCamera.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeCamera.as new file mode 100644 index 0000000..dc3d3e2 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeCamera.as @@ -0,0 +1,55 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.core.Camera3D; + + /** + * @private + */ + public class DaeCamera extends DaeElement { + + use namespace collada; + + public function DaeCamera(data:XML, document:DaeDocument) { + super(data, document); + } + + private function setXFov(camera:Camera3D, xFov:Number):void { + //camera.fov = 2*Math.atan(0.5*Math.sqrt(camera.width*camera.width + camera.height*camera.height)/(camera.width/(2*Math.tan(0.5*xFov)))); + } + + public function parseCamera():Camera3D { + var camera:Camera3D = new Camera3D(); + var perspectiveXML:XML = data.optics.technique_common.perspective[0]; + if (perspectiveXML) { + const DEG2RAD:Number = Math.PI/180; + var xfovXML:XML = perspectiveXML.xfov[0]; + var yfovXML:XML = perspectiveXML.yfov[0]; + var ratioXML:XML = perspectiveXML.aspect_ratio[0]; + if (ratioXML == null) { + if (xfovXML != null) { + setXFov(camera, parseNumber(xfovXML)*DEG2RAD); + } else if (yfovXML != null) { + setXFov(camera, parseNumber(yfovXML)*DEG2RAD); + } + } else { + var ratio:Number = parseNumber(ratioXML); + //camera.height = camera.width/ratio; + if (xfovXML != null) { + setXFov(camera, parseNumber(xfovXML)*DEG2RAD); + } else if (yfovXML != null) { + setXFov(camera, ratio*parseNumber(yfovXML)*DEG2RAD); + } + } + var znearXML:XML = perspectiveXML.znear[0]; + var zfarXML:XML = perspectiveXML.zfar[0]; + if (znearXML != null) { + camera.nearClipping = parseNumber(znearXML); + } + if (zfarXML != null) { + camera.farClipping = parseNumber(zfarXML); + } + } + return camera; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeChannel.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeChannel.as new file mode 100644 index 0000000..59a60a8 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeChannel.as @@ -0,0 +1,185 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.Track; + import alternativa.engine3d.animation.keys.Key; + import alternativa.engine3d.animation.keys.ValueKey; + + /** + * @private + */ + public class DaeChannel extends DaeElement { + + static public const PARAM_UNDEFINED:int = -1; + static public const PARAM_TRANSLATE_X:int = 0; + static public const PARAM_TRANSLATE_Y:int = 1; + static public const PARAM_TRANSLATE_Z:int = 2; + static public const PARAM_SCALE_X:int = 3; + static public const PARAM_SCALE_Y:int = 4; + static public const PARAM_SCALE_Z:int = 5; + static public const PARAM_ROTATION_X:int = 6; + static public const PARAM_ROTATION_Y:int = 7; + static public const PARAM_ROTATION_Z:int = 8; + static public const PARAM_TRANSLATE:int = 9; + static public const PARAM_SCALE:int = 10; + static public const PARAM_MATRIX:int = 11; + + /** + * Анимационный трек с ключами. + * Перед использованием вызвать parse(). + */ + public var track:Track; + + /** + * Тип анимированного параметра, принимает одно из значений DaeChannel.PARAM_*. + * Перед использованием вызвать parse(). + */ + public var animatedParam:int = PARAM_UNDEFINED; + + public function DaeChannel(data:XML, document:DaeDocument) { + super(data, document); + } + + /** + * Возвращает ноду которой предназначена анимация. + */ + public function get node():DaeNode { + var targetXML:XML = data.@target[0]; + if (targetXML != null) { + var targetParts:Array = targetXML.toString().split("/"); + // Первая часть это id элемента + var node:DaeNode = document.findNodeByID(targetParts[0]); + if (node != null) { + // Последняя часть это трансформируемый элемент + targetParts.pop(); + for (var i:int = 1, count:int = targetParts.length; i < count; i++) { + var sid:String = targetParts[i]; + node = node.getNodeBySid(sid); + if (node == null) { + return null; + } + } + return node; + } + } + return null; + } + + override protected function parseImplementation():Boolean { + parseTransformationType(); + parseSampler(); + return true; + } + + private function parseTransformationType():void { + var targetXML:XML = data.@target[0]; + if (targetXML == null) return; + + // Разбиваем путь на части + var targetParts:Array = targetXML.toString().split("/"); + var sid:String = targetParts.pop(); + var sidParts:Array = sid.split("."); + var sidPartsCount:int = sidParts.length; + + // Определяем тип свойства + var transformationXML:XML; + var children:XMLList = node.data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + var attr:XML = child.@sid[0]; + if (attr != null && attr.toString() == sidParts[0]) { + transformationXML = child; + break; + } + } + // TODO:: вариант со скобками на всякий случай + var transformationName:String = transformationXML.localName() as String; + if (sidPartsCount > 1) { + var componentName:String = sidParts[1]; + switch (transformationName) { + case "translate": + switch (componentName) { + case "X": + animatedParam = PARAM_TRANSLATE_X; + break; + case "Y": + animatedParam = PARAM_TRANSLATE_Y; + break; + case "Z": + animatedParam = PARAM_TRANSLATE_Z; + break; + } + break; + case "rotate": { + var axis:Array = parseNumbersArray(transformationXML); + // TODO:: искать максимальное значение, а не единицу + switch (axis.indexOf(1)) { + case 0: + animatedParam = PARAM_ROTATION_X; + break; + case 1: + animatedParam = PARAM_ROTATION_Y; + break; + case 2: + animatedParam = PARAM_ROTATION_Z; + break; + } + break; + } + case "scale": + switch (componentName) { + case "X": + animatedParam = PARAM_SCALE_X; + break; + case "Y": + animatedParam = PARAM_SCALE_Y; + break; + case "Z": + animatedParam = PARAM_SCALE_Z; + break; + } + break; + } + } else { + switch (transformationName) { + case "translate": + animatedParam = PARAM_TRANSLATE; + break; + case "scale": + animatedParam = PARAM_SCALE; + break; + case "matrix": + animatedParam = PARAM_MATRIX; + break; + } + } + } + + private function parseSampler():void { + var sampler:DaeSampler = document.findSampler(data.@source[0]); + if (sampler != null) { + sampler.parse(); + + if (animatedParam == PARAM_MATRIX) { + track = sampler.parseMatrixTrack(); + return; + } + if (animatedParam == PARAM_TRANSLATE || animatedParam == PARAM_SCALE) { + track = sampler.parsePointsTrack(); + return; + } + track = sampler.parseValuesTrack(); + if (animatedParam == PARAM_ROTATION_X || animatedParam == PARAM_ROTATION_Y || animatedParam == PARAM_ROTATION_Z) { + // Переводим углы в радианы + var toRad:Number = Math.PI/180; + for (var key:Key = track.keyList; key != null; key = key.next) { + var valueKey:ValueKey = ValueKey(key); + valueKey.value *= toRad; + } + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeController.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeController.as new file mode 100644 index 0000000..e2e0b88 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeController.as @@ -0,0 +1,449 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.*; + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.animation.AnimationGroup; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Joint; + import alternativa.engine3d.objects.Skin; + + import flash.geom.Matrix3D; + import flash.utils.Dictionary; + + /** + * @private + */ + public class DaeController extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + private var jointsBindMatrices:Vector. >; + private var vcounts:Array; + private var indices:Array; + private var jointsInput:DaeInput; + private var weightsInput:DaeInput; + private var inputsStride:int; + + public function DaeController(data:XML, document:DaeDocument) { + super(data, document); + + // sources мы создаем внутри DaeDocument, здесь не нужно. + } + + override protected function parseImplementation():Boolean { + var vertexWeightsXML:XML = data.skin.vertex_weights[0]; + if (vertexWeightsXML == null) { + return false; + } + var vcountsXML:XML = vertexWeightsXML.vcount[0]; + if (vcountsXML == null) { + return false; + } + vcounts = parseIntsArray(vcountsXML); + var indicesXML:XML = vertexWeightsXML.v[0]; + if (indicesXML == null) { + return false; + } + indices = parseIntsArray(indicesXML); + parseInputs(); + parseJointsBindMatrices(); + return true; + } + + private function parseInputs():void { + var inputsList:XMLList = data.skin.vertex_weights.input; + var maxInputOffset:int = 0; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "JOINT" : + if (jointsInput == null) { + jointsInput = input; + } + break; + case "WEIGHT" : + if (weightsInput == null) { + weightsInput = input; + } + break; + } + } + var offset:int = input.offset; + maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset; + } + inputsStride = maxInputOffset + 1; + } + + /** + * Парсит инверсные матрицы для костей и сохраняет их в вектор. + */ + private function parseJointsBindMatrices():void { + var jointsXML:XML = data.skin.joints.input.(@semantic == "INV_BIND_MATRIX")[0]; + if (jointsXML != null) { + var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]); + if (jointsSource != null) { + if (jointsSource.parse() && jointsSource.numbers != null && jointsSource.stride >= 16) { + var stride:int = jointsSource.stride; + var count:int = jointsSource.numbers.length/stride; + jointsBindMatrices = new Vector. >(count); + for (var i:int = 0; i < count; i++) { + var index:int = stride*i; + var matrix:Vector. = new Vector.(16); + jointsBindMatrices[i] = matrix; + for (var j:int = 0; j < 16; j++) { + matrix[j] = jointsSource.numbers[int(index + j)]; + } + } + } + } else { + document.logger.logNotFoundError(jointsXML.@source[0]); + } + } + } + + private function get geometry():DaeGeometry { + var geom:DaeGeometry = document.findGeometry(data.skin.@source[0]); + if (geom == null) { + document.logger.logNotFoundError(data.@source[0]); + } + return geom; + } + + /** + * Возвращает геометрию с костями и контроллер для костей. + * Перед использованием вызвать parse(). + */ + public function parseSkin(skinNode:DaeNode, materials:Object, skeletons:Vector.):DaeAnimatedObject { + var skinXML:XML = data.skin[0]; + if (skinXML != null) { + var skin:Skin; + var geom:DaeGeometry = geometry; + if (geom != null) { + geom.parse(); + skin = geom.parseAlternativa3DObject(true) as Skin; + if (skin == null) { + skin = new Skin(); + } + var vertices:Vector. = geom.fillInMesh(skin, materials); + applyBindShapeMatrix(skin); + var joints:Vector. = addJointsToSkin(skin, skinNode, findNodes(skeletons)); + setJointsBindMatrices(joints); + linkVerticesToJoints(joints, vertices); + skin.normalizeWeights(); + geom.cleanVertices(skin); + skin.calculateNormals(true); + skin.calculateBounds(); + return new DaeAnimatedObject(skin, mergeJointsAnimations(skin, joints)); + } else { + skin = new Skin(); + skin.calculateNormals(true); + skin.calculateBounds(); + return new DaeAnimatedObject(skin); + } + } + return null; + } + + /** + * Объединяет анимацию костей в одну анимацию, если требуется + */ + private function mergeJointsAnimations(skin:Skin, joints:Vector.):Animation { + var complex:AnimationGroup = new AnimationGroup(skin); + for (var i:int = 0, count:int = joints.length; i < count; i++) { + var animatedObject:DaeAnimatedObject = joints[i]; + if (animatedObject.animation != null) { + complex.addAnimation(animatedObject.animation); + } + } + return (complex.numAnimations > 0) ? complex : null; + } + + /** + * Задает костям их инверсные матрицы. + */ + private function setJointsBindMatrices(animatedJoints:Vector.):void { + for (var i:int = 0, count:int = jointsBindMatrices.length; i < count; i++) { + var animatedJoint:DaeAnimatedObject = animatedJoints[i]; + var bindMatrix:Vector. = jointsBindMatrices[i]; + Joint(animatedJoint.object).setBindingMatrix(bindMatrix[0], bindMatrix[1], bindMatrix[2], bindMatrix[3], + bindMatrix[4], bindMatrix[5], bindMatrix[6], bindMatrix[7], + bindMatrix[8], bindMatrix[9], bindMatrix[10], bindMatrix[11]); + } + } + + /** + * Связывает вершину и все ее дубликаты с костью + */ + private function linkVertexToJoint(joint:Joint, vertex:Vertex, weight:Number):void { + joint.bindVertex(vertex, weight); + // Цепляем дубликаты + while ((vertex = vertex.value) != null) { + joint.bindVertex(vertex, weight); + } + } + + /** + * Связывает вершины с костями + */ + private function linkVerticesToJoints(animatedJoints:Vector., vertices:Vector.):void { + var jointsOffset:int = jointsInput.offset; + var weightsOffset:int = weightsInput.offset; + var weightsSource:DaeSource = weightsInput.prepareSource(1); + var weights:Vector. = weightsSource.numbers; + var weightsStride:int = weightsSource.stride; + var vertexIndex:int = 0; + for (var i:int = 0, numVertices:int = vertices.length; i < numVertices; i++) { + var vertex:Vertex = vertices[i]; + var count:int = vcounts[i]; + for (var j:int = 0; j < count; j++) { + var index:int = inputsStride*(vertexIndex + j); + var jointIndex:int = indices[int(index + jointsOffset)]; + if (jointIndex >= 0) { + var weightIndex:int = indices[int(index + weightsOffset)]; + var weight:Number = weights[int(weightsStride*weightIndex)]; + linkVertexToJoint(Joint(animatedJoints[jointIndex].object), vertex, weight); + } + } + vertexIndex += count; + } + } + + /** + * Создает иерархию костей и добавляет к скину. + * + * @return вектор добавленых к скину костей с анимацией. + * Если были добавлены вспомогательные кости, длина вектора будет отличаться от длины вектора nodes + */ + private function addJointsToSkin(skin:Skin, skinNode:DaeNode, nodes:Vector.):Vector. { + // Словарь, в котором ключ-нода, значение-позиция в векторе nodes + var nodesDictionary:Dictionary = new Dictionary(); + var count:int = nodes.length; + var i:int; + for (i = 0; i < count; i++) { + nodesDictionary[nodes[i]] = i; + } + var animatedJoints:Vector. = new Vector.(count); + for (i = 0; i < count; i++) { + var node:DaeNode = nodes[i]; + if (isRootJointNode(node, nodesDictionary)) { + var animatedJoint:DaeAnimatedObject = addRootJointToSkin(skin, skinNode, node); + if (animatedJoint != null) { + animatedJoints[i] = animatedJoint; + addJointChildren(Joint(animatedJoint.object), animatedJoints, node, nodesDictionary); + } + } + } + return animatedJoints; + } + + /** + * Возвращает true если у кости нет родительской кости + * @param node нода кости + * @param nodes словарь, в котором ключи это ноды всех костей + */ + private function isRootJointNode(node:DaeNode, nodes:Dictionary):Boolean { + for (var parent:DaeNode = node.parent; parent != null; parent = parent.parent) { + if (parent in nodes) { + return false; + } + } + return true; + } + + /** + * Добавляет рутовую кость к скину + */ + private function addRootJointToSkin(skin:Skin, skinNode:DaeNode, node:DaeNode):DaeAnimatedObject { + var joint:Joint; + if (skinNode == node) { + // Кость и является скином + joint = new Joint(); + joint.name = node.name; + skin.addJoint(joint); + return new DaeAnimatedObject(joint); + } else { + if (node.scene == skinNode.scene) { + var parent:DaeNode = node.parent; + var toSceneMatrix:Matrix3D; + if (parent != null) { + // Считаем матрицу перевода кости в сцену + toSceneMatrix = parent.getMatrix(); + while ((parent = parent.parent) != null) { + toSceneMatrix.append(parent.getMatrix()); + } + } + // Считаем матрицу перевода локального пространства скина в сцену + var skinMatrix:Matrix3D = skinNode.getMatrix(); + for (parent = skinNode.parent; parent != null; parent = parent.parent) { + skinMatrix.append(parent.getMatrix()); + } + skinMatrix.invert(); + // Считаем матрицу перевода в скин + var toSkinMatrix:Matrix3D; + if (toSceneMatrix != null) { + toSkinMatrix = toSceneMatrix; + toSkinMatrix.append(skinMatrix); + } else { + toSkinMatrix = skinMatrix; + } + // Если кость анимирована, создаем вспомогательную кость перевода в скин + var additionalJoint:Joint = new Joint(); + additionalJoint.setMatrix(toSkinMatrix); + skin.addJoint(additionalJoint); + joint = new Joint(); + joint.name = node.name; + additionalJoint.addJoint(joint); + return node.applyAnimation(node.applyTransformations(joint)); + } else { + // Не обрабатывается + document.logger.logJointInAnotherSceneError(node.data); + return null; + } + } + } + + /** + * Создает иерархию дочерних костей и добавляет к родительской кости. + * + * @param parent родительская кость + * @param animatedJoints вектор костей в который положить созданные кости. + * В конец вектора будут добавлены вспомогательные кости, если понадобятся. + * @param parentNode нода родительской кости + * @param nodes словарь, в котором ключ это нода кости, а значение это индекс кости в векторе animatedJoints + */ + private function addJointChildren(parent:Joint, animatedJoints:Vector., parentNode:DaeNode, nodes:Dictionary):void { + var children:Vector. = parentNode.nodes; + for (var i:int = 0, count:int = children.length; i < count; i++) { + var child:DaeNode = children[i]; + var joint:Joint; + if (child in nodes) { + joint = new Joint(); + joint.name = child.name; + animatedJoints[nodes[child]] = child.applyAnimation(child.applyTransformations(joint)); + parent.addJoint(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } else { + // Нода не является костью + if (hasJointInDescendants(child, nodes)) { + // Если среди ее потомков есть кость, нужно создать вспомогательную кость вместо этой ноды. + joint = new Joint(); + joint.name = child.name; + // Добавляем в конец новую кость + animatedJoints.push(child.applyAnimation(child.applyTransformations(joint))); + parent.addJoint(joint); + addJointChildren(joint, animatedJoints, child, nodes); + } + } + } + } + + private function hasJointInDescendants(parentNode:DaeNode, nodes:Dictionary):Boolean { + var children:Vector. = parentNode.nodes; + for (var i:int = 0, count:int = children.length; i < count; i++) { + var child:DaeNode = children[i]; + if (child in nodes || hasJointInDescendants(child, nodes)) { + return true; + } + } + return false; + } + + /** + * Трансформирует все вершины объекта при помощи BindShapeMatrix из коллады + */ + private function applyBindShapeMatrix(skin:Skin):void { + var matrixXML:XML = data.skin.bind_shape_matrix[0]; + if (matrixXML != null) { + var matrix:Array = parseNumbersArray(matrixXML); + if (matrix.length >= 16) { + var a:Number = matrix[0]; + var b:Number = matrix[1]; + var c:Number = matrix[2]; + var d:Number = matrix[3]; + var e:Number = matrix[4]; + var f:Number = matrix[5]; + var g:Number = matrix[6]; + var h:Number = matrix[7]; + var i:Number = matrix[8]; + var j:Number = matrix[9]; + var k:Number = matrix[10]; + var l:Number = matrix[11]; + for (var vertex:Vertex = skin.vertexList; vertex != null; vertex = vertex.next) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.x = a*x + b*y + c*z + d; + vertex.y = e*x + f*y + g*z + h; + vertex.z = i*x + j*y + k*z + l; + } + } + } + } + + public function findRootJointNodes(skeletons:Vector.):Vector. { + var nodes:Vector. = findNodes(skeletons); + var i:int = 0; + var count:int = nodes.length; + if (count > 0) { + var nodesDictionary:Dictionary = new Dictionary(); + for (i = 0; i < count; i++) { + nodesDictionary[nodes[i]] = i; + } + var rootNodes:Vector. = new Vector.(); + for (i = 0; i < count; i++) { + var node:DaeNode = nodes[i]; + if (isRootJointNode(node, nodesDictionary)) { + rootNodes.push(node); + } + } + return rootNodes; + } + return null; + } + + /** + * Находит ноду по ее сиду в векторе скелетов + */ + private function findNode(nodeName:String, skeletons:Vector.):DaeNode { + var count:int = skeletons.length; + for (var i:int = 0; i < count; i++) { + var node:DaeNode = skeletons[i].getNodeBySid(nodeName); + if (node != null) { + return node; + } + } + return null; + } + + /** + * Возвращает вектор нод костей. + */ + private function findNodes(skeletons:Vector.):Vector. { + var jointsXML:XML = data.skin.joints.input.(@semantic == "JOINT")[0]; + if (jointsXML != null) { + var jointsSource:DaeSource = document.findSource(jointsXML.@source[0]); + if (jointsSource != null) { + if (jointsSource.parse() && jointsSource.names != null) { + var stride:int = jointsSource.stride; + var count:int = jointsSource.names.length/stride; + var nodes:Vector. = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = findNode(jointsSource.names[int(stride*i)], skeletons); + if (node == null) { + // Ошибка, нет ноды + } + nodes[i] = node; + } + return nodes; + } + } else { + document.logger.logNotFoundError(jointsXML.@source[0]); + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeDocument.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeDocument.as new file mode 100644 index 0000000..b934d57 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeDocument.as @@ -0,0 +1,295 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.alternativa3d; + + /** + * @private + */ + public class DaeDocument { + + use namespace collada; + use namespace alternativa3d; + + public var scene:DaeVisualScene; + + /** + * Файл коллады + */ + private var data:XML; + + // Словари для хранения соответствий id->DaeElement + internal var sources:Object; + internal var arrays:Object; + internal var vertices:Object; + internal var geometries:Object; + internal var nodes:Object; + internal var cameras:Object; + internal var images:Object; + internal var effects:Object; + internal var controllers:Object; + internal var samplers:Object; + internal var alternativa3DObjects:Object; + + public var materials:Object; + + internal var logger:DaeLogger; + + public var versionMajor:uint; + public var versionMinor:uint; + public var alternativa3DExtensionVersionMajor:uint = 0; + public var alternativa3DExtensionVersionMinor:uint = 0; + + public function DaeDocument(document:XML) { + this.data = document; + + var versionComponents:Array = data.@version[0].toString().split(/[.,]/); + versionMajor = parseInt(versionComponents[1], 10); + versionMinor = parseInt(versionComponents[2], 10); + + logger = new DaeLogger(); + + constructStructures(); + constructScenes(); + constructInstanceControllers(); + constructAnimations(); + constructAlternativa3DObjects(); + } + + private function isLocalURL(url:String):Boolean { + return url.charAt(0) == "#"; + } + + private function getLocalID(url:XML):String { + var path:String = url.toString(); + if (isLocalURL(path)) { + var i:int = path.lastIndexOf("#"); + return (i >= 0) ? path.substring(i + 1) : null; + } else { + logger.logExternalError(url); + return null; + } + } + + // Ищем объявления всех элементов и заполняем словари + private function constructStructures():void { + var element:XML; + + sources = new Object(); + arrays = new Object(); + for each (element in data..source) { + // Собираем все . В конструкторах заполняется словарь arrays + var source:DaeSource = new DaeSource(element, this); + if (source.id != null) { + sources[source.id] = source; + } + } + cameras = new Object(); + for each (element in data.library_cameras.camera) { + // Собираем все . + var camera:DaeCamera = new DaeCamera(element, this); + if (camera.id != null) { + cameras[camera.id] = camera; + } + } + images = new Object(); + for each (element in data.library_images.image) { + // Собираем все . + var image:DaeImage = new DaeImage(element, this); + if (image.id != null) { + images[image.id] = image; + } + } + effects = new Object(); + for each (element in data.library_effects.effect) { + // Собираем все . В конструкторах заполняется словарь images + var effect:DaeEffect = new DaeEffect(element, this); + if (effect.id != null) { + effects[effect.id] = effect; + } + } + materials = new Object(); + for each (element in data.library_materials.material) { + // Собираем все . + var material:DaeMaterial = new DaeMaterial(element, this); + if (material.id != null) { + materials[material.id] = material; + } + } + geometries = new Object(); + vertices = new Object(); + for each (element in data.library_geometries.geometry) { + // Собираем все . В конструкторах заполняется словарь vertices + var geom:DaeGeometry = new DaeGeometry(element, this); + if (geom.id != null) { + geometries[geom.id] = geom; + } + } + + controllers = new Object(); + for each (element in data.library_controllers.controller) { + // Собираем все + var controller:DaeController = new DaeController(element, this); + if (controller.id != null) { + controllers[controller.id] = controller; + } + } + + nodes = new Object(); + for each (element in data.library_nodes.node) { + // Создаем только корневые ноды, остальные создаются рекурсивно в конструкторах + var node:DaeNode = new DaeNode(element, this); + if (node.id != null) { + nodes[node.id] = node; + } + } + } + + private function constructInstanceControllers():void { + for each (var node:DaeNode in nodes) { + var instanceControllerXML:XML = node.data.instance_controller[0]; + if (instanceControllerXML != null) { + node.skinOrRootJoint = true; + var instanceController:DaeInstanceController = new DaeInstanceController(instanceControllerXML, this, node); + var jointNodes:Vector. = instanceController.findRootJointNodes(); + var i:int; + var count:int = jointNodes.length; + if (count > 0) { + var jointNode:DaeNode = jointNodes[0]; + jointNode.addInstanceController(instanceController); + node.rootJoint = jointNode; + for (i = 0; i < count; i++) { + jointNodes[i].skinOrRootJoint = true; + } + } + } + } + } + + private function constructScenes():void { + var vsceneURL:XML = data.scene.instance_visual_scene.@url[0]; + var vsceneID:String = getLocalID(vsceneURL); + for each (var element:XML in data.library_visual_scenes.visual_scene) { + // Создаем visual_scene, в конструкторах создаются node + var vscene:DaeVisualScene = new DaeVisualScene(element, this); + if (vscene.id == vsceneID) { + this.scene = vscene; + } + } + if (vsceneID != null && scene == null) { + logger.logNotFoundError(vsceneURL); + } + } + + private function constructAnimations():void { + var element:XML; + samplers = new Object(); + for each (element in data.library_animations..sampler) { + // Собираем все + var sampler:DaeSampler = new DaeSampler(element, this); + if (sampler.id != null) { + samplers[sampler.id] = sampler; + } + } + + for each (element in data.library_animations..channel) { + var channel:DaeChannel = new DaeChannel(element, this); + var node:DaeNode = channel.node; + if (node != null) { + node.addChannel(channel); + } + } + } + + private function constructAlternativa3DObjects():void { + alternativa3DObjects = new Object(); + var alternativa3dXML:XML = data.extra.technique.(@profile = "Alternativa3D")[0]; + if (alternativa3dXML != null) { + var versionComponents:Array = alternativa3dXML.version[0].text().toString().split(/[.,]/); + alternativa3DExtensionVersionMajor = parseInt(versionComponents[0], 10); + alternativa3DExtensionVersionMinor = parseInt(versionComponents[1], 10); + var element:XML; + var object:DaeAlternativa3DObject; + for each (element in alternativa3dXML.library_containers.children()) { + // контейнеры + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + for each (element in alternativa3dXML.library_sprites.sprite) { + // спрайты + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + for each (element in alternativa3dXML.library_lods.lod) { + // лоды + object = new DaeAlternativa3DObject(element, this); + if (object.id != null) { + alternativa3DObjects[object.id] = object; + } + } + } else { + alternativa3DExtensionVersionMajor = alternativa3DExtensionVersionMinor = 0; + } + } + + public function findArray(url:XML):DaeArray { + return arrays[getLocalID(url)]; + } + + public function findSource(url:XML):DaeSource { + return sources[getLocalID(url)]; + } + + public function findCamera(url:XML):DaeCamera { + return cameras[getLocalID(url)]; + } + + public function findImage(url:XML):DaeImage { + return images[getLocalID(url)]; + } + + public function findImageByID(id:String):DaeImage { + return images[id]; + } + + public function findEffect(url:XML):DaeEffect { + return effects[getLocalID(url)]; + } + + public function findMaterial(url:XML):DaeMaterial { + return materials[getLocalID(url)]; + } + + public function findVertices(url:XML):DaeVertices { + return vertices[getLocalID(url)]; + } + + public function findGeometry(url:XML):DaeGeometry { + return geometries[getLocalID(url)]; + } + + public function findNode(url:XML):DaeNode { + return nodes[getLocalID(url)]; + } + + public function findNodeByID(id:String):DaeNode { + return nodes[id]; + } + + public function findController(url:XML):DaeController { + return controllers[getLocalID(url)]; + } + + public function findSampler(url:XML):DaeSampler { + return samplers[getLocalID(url)]; + } + + public function findAlternativa3DObject(url:XML):DaeAlternativa3DObject { + return alternativa3DObjects[getLocalID(url)]; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffect.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffect.as new file mode 100644 index 0000000..cc95976 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffect.as @@ -0,0 +1,157 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.materials.FillMaterial; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.materials.TextureMaterial; + + /** + * @private + */ +public class DaeEffect extends DaeElement { + + use namespace collada; + + private var effectParams:Object; + private var commonParams:Object; + private var techniqueParams:Object; + + private var diffuse:DaeEffectParam; + private var emission:DaeEffectParam; + private var transparent:DaeEffectParam; + private var transparency:DaeEffectParam; + + public function DaeEffect(data:XML, document:DaeDocument) { + super(data, document); + + // Внтри объявляются image. + constructImages(); + } + + private function constructImages():void { + var list:XMLList = data..image; + for each (var element:XML in list) { + var image:DaeImage = new DaeImage(element, document); + if (image.id != null) { + document.images[image.id] = image; + } + } + } + + override protected function parseImplementation():Boolean { + var element:XML; + var param:DaeParam; + effectParams = new Object(); + for each (element in data.newparam) { + param = new DaeParam(element, document); + effectParams[param.sid] = param; + } + commonParams = new Object(); + for each (element in data.profile_COMMON.newparam) { + param = new DaeParam(element, document); + commonParams[param.sid] = param; + } + techniqueParams = new Object(); + var technique:XML = data.profile_COMMON.technique[0]; + if (technique != null) { + for each (element in technique.newparam) { + param = new DaeParam(element, document); + techniqueParams[param.sid] = param; + } + } + var shader:XML = data.profile_COMMON.technique.*.(localName() == "constant" || localName() == "lambert" || localName() == "phong" || localName() == "blinn")[0]; + if (shader != null) { + if (shader.localName() == "constant") { + var emissionXML:XML = shader.emission[0]; + if (emissionXML != null) { + emission = new DaeEffectParam(emissionXML, this); + } + } else { + var diffuseXML:XML = shader.diffuse[0]; + if (diffuseXML != null) { + diffuse = new DaeEffectParam(diffuseXML, this); + } + } + var transparentXML:XML = shader.transparent[0]; + if (transparentXML != null) { + transparent = new DaeEffectParam(transparentXML, this); + } + var transparencyXML:XML = shader.transparency[0]; + if (transparencyXML != null) { + transparency = new DaeEffectParam(transparencyXML, this); + } + } + return true; + } + + internal function getParam(name:String, setparams:Object):DaeParam { + var param:DaeParam = setparams[name]; + if (param != null) { + return param; + } + param = techniqueParams[name]; + if (param != null) { + return param; + } + param = commonParams[name]; + if (param != null) { + return param; + } + return effectParams[name]; + } + + private function float4ToUint(value:Array, alpha:Boolean = true):uint { + var r:uint = (value[0] * 255); + var g:uint = (value[1] * 255); + var b:uint = (value[2] * 255); + if (alpha) { + var a:uint = (value[3] * 255); + return (a << 24) | (r << 16) | (g << 8) | b; + } else { + return (r << 16) | (g << 8) | b; + } + } + + /** + * Возвращает материал движка с заданными параметрами. + * Перед использованием вызвать parse(). + */ + public function getMaterial(setparams:Object):Material { + var diffuse:DaeEffectParam = (diffuse != null) ? diffuse : emission; + if (diffuse != null) { + var color:Array = diffuse.getColor(setparams); + if (color != null) { + var fillMaterial:FillMaterial = new FillMaterial(float4ToUint(color, false), color[3]); + if (transparency != null) { + var value:Number = transparency.getFloat(setparams); + if (!isNaN(value)) { + fillMaterial.alpha = value; + } + } + return fillMaterial; + } else { + var image:DaeImage = diffuse.getImage(setparams); + if (image != null) { + var sampler:DaeParam = diffuse.getSampler(setparams); + var textureMaterial:TextureMaterial = new TextureMaterial(); + textureMaterial.repeat = (sampler == null) ? true : (sampler.wrap_s == null || sampler.wrap_s == "WRAP"); + textureMaterial.diffuseMapURL = image.init_from; + var transparentImage:DaeImage = (transparent == null) ? null : transparent.getImage(setparams); + if (transparentImage != null) { + textureMaterial.opacityMapURL = transparentImage.init_from; + } + return textureMaterial; + } + } + } + return null; + } + + /** + * Имя текстурного канала для карты цвета объекта + * Перед использованием вызвать parse(). + */ + public function get diffuseTexCoords():String { + return (diffuse == null && emission == null) ? null : ((diffuse != null) ? diffuse.texCoord : emission.texCoord); + } + +} +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffectParam.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffectParam.as new file mode 100644 index 0000000..68282bb --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeEffectParam.as @@ -0,0 +1,85 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeEffectParam extends DaeElement { + + use namespace collada; + + private var effect:DaeEffect; + + public function DaeEffectParam(data:XML, effect:DaeEffect) { + super(data, effect.document); + this.effect = effect; + } + + public function getFloat(setparams:Object):Number { + var floatXML:XML = data.float[0]; + if (floatXML != null) { + return parseNumber(floatXML); + } + var paramRef:XML = data.param.@ref[0]; + if (paramRef != null) { + var param:DaeParam = effect.getParam(paramRef.toString(), setparams); + if (param != null) { + return param.getFloat(); + } + } + return NaN; + } + + public function getColor(setparams:Object):Array { + var colorXML:XML = data.color[0]; + if (colorXML != null) { + return parseNumbersArray(colorXML); + } + var paramRef:XML = data.param.@ref[0]; + if (paramRef != null) { + var param:DaeParam = effect.getParam(paramRef.toString(), setparams); + if (param != null) { + return param.getFloat4(); + } + } + return null; + } + + private function get texture():String { + var attr:XML = data.texture.@texture[0]; + return (attr == null) ? null : attr.toString(); + } + + public function getSampler(setparams:Object):DaeParam { + var sid:String = texture; + if (sid != null) { + return effect.getParam(sid, setparams); + } + return null; + } + + public function getImage(setparams:Object):DaeImage { + var sampler:DaeParam = getSampler(setparams); + if (sampler != null) { + var surfaceSID:String = sampler.surfaceSID; + if (surfaceSID != null) { + var surface:DaeParam = effect.getParam(surfaceSID, setparams); + if (surface != null) { + return surface.image; + } + } else { + return sampler.image; + } + } else { + // Возможно файл был экспортирован стандартным экспортом макса, который забивает на спецификацию и хранит ссылку прямо на image. + return document.findImageByID(texture); + } + return null; + } + + public function get texCoord():String { + var attr:XML = data.texture.@texcoord[0]; + return (attr == null) ? null : attr.toString(); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeElement.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeElement.as new file mode 100644 index 0000000..d7bd448 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeElement.as @@ -0,0 +1,94 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeElement { + + use namespace collada; + + public var document:DaeDocument; + + public var data:XML; + + /** + * -1 - not parsed, 0 - parsed with error, 1 - parsed without error. + */ + private var _parsed:int = -1; + + public function DaeElement(data:XML, document:DaeDocument) { + this.document = document; + this.data = data; + } + + /** + * Выполняет предварительную настройку объекта. + * + * @return false в случае ошибки. + */ + public function parse():Boolean { + // -1 - not parsed, 0 - parsed with error, 1 - parsed without error. + if (_parsed < 0) { + _parsed = parseImplementation() ? 1 : 0; + return _parsed != 0; + } + return _parsed != 0; + } + + /** + * Переопределяемый метод parse() + */ + protected function parseImplementation():Boolean { + return true; + } + + /** + * Возвращает массив значений типа String. + */ + protected function parseStringArray(element:XML):Array { + return element.text().toString().split(/\s+/); + } + + protected function parseNumbersArray(element:XML):Array { + var arr:Array = element.text().toString().split(/\s+/); + for (var i:int = 0, count:int = arr.length; i < count; i++) { + var value:String = arr[i]; + if (value.indexOf(",") != -1) { + value = value.replace(/,/, "."); + } + arr[i] = parseFloat(value); + } + return arr; + } + + protected function parseIntsArray(element:XML):Array { + var arr:Array = element.text().toString().split(/\s+/); + for (var i:int = 0, count:int = arr.length; i < count; i++) { + var value:String = arr[i]; + arr[i] = parseInt(value, 10); + } + return arr; + } + + protected function parseNumber(element:XML):Number { + var value:String = element.toString().replace(/,/, "."); + return parseFloat(value); + } + + public function get id():String { + var idXML:XML = data.@id[0]; + return (idXML == null) ? null : idXML.toString(); + } + + public function get sid():String { + var attr:XML = data.@sid[0]; + return (attr == null) ? null : attr.toString(); + } + + public function get name():String { + var nameXML:XML = data.@name[0]; + return (nameXML == null) ? null : nameXML.toString(); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeGeometry.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeGeometry.as new file mode 100644 index 0000000..ab68d87 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeGeometry.as @@ -0,0 +1,125 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + /** + * @private + */ + public class DaeGeometry extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + private var primitives:Vector.; + + private var vertices:DaeVertices; + + public function DaeGeometry(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются элементы: sources, vertices. + // sources мы создаем внутри DaeDocument, здесь не нужно. + constructVertices(); + } + + private function constructVertices():void { + var verticesXML:XML = data.mesh.vertices[0]; + if (verticesXML != null) { + vertices = new DaeVertices(verticesXML, document); + document.vertices[vertices.id] = vertices; + } + } + + override protected function parseImplementation():Boolean { + if (vertices != null) { + return parsePrimitives(); + } + return false; + } + + private function parsePrimitives():Boolean { + primitives = new Vector.(); + var children:XMLList = data.mesh.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case "polygons": + case "polylist": + case "triangles": + case "trifans": + case "tristrips": + primitives.push(new DaePrimitive(child, document)); + break; + } + } + return true; + } + + /** + * Создает геометрию и возвращает в виде меша. + * Перед использованием вызвать parse(). + * + * @param materials словарь материалов. + */ + public function parseMesh(materials:Object):Mesh { + if (data.mesh.length() > 0) { + var mesh:Mesh = parseAlternativa3DObject(false); + if (mesh == null) { + mesh = new Mesh(); + } + fillInMesh(mesh, materials); + cleanVertices(mesh); + mesh.calculateNormals(true); + mesh.calculateBounds(); + return mesh; + } + return null; + } + + /** + * Заполняет заданный объект геометрией и возвращает массив вершин с индексами. + * Перед использованием вызвать parse(). + * Некоторые вершины в поле value содержат ссылку на дубликат вершины. + * После использования нужно вызвать cleanVertices для зачистки вершин от временных данных. + * + * @return массив вершин с индексами. У вершины в поле value задается дубликат вершины. + */ + public function fillInMesh(mesh:Mesh, materials:Object):Vector. { + vertices.parse(); + var createdVertices:Vector. = vertices.fillInMesh(mesh); + for (var i:int = 0, count:int = primitives.length; i < count; i++) { + var primitive:DaePrimitive = primitives[i]; + primitive.parse(); + if (primitive.verticesEquals(vertices)) { + primitive.fillInMesh(mesh, createdVertices, materials[primitive.materialSymbol]); + } else { + // Ошибка, нельзя использовать вершины из другой геометрии + } + } + return createdVertices; + } + + /** + * Зачищает вершины от временных данных + */ + public function cleanVertices(mesh:Mesh):void { + for (var vertex:Vertex = mesh.vertexList; vertex != null; vertex = vertex.next) { + vertex.index = 0; + vertex.value = null; + } + } + + public function parseAlternativa3DObject(skin:Boolean = false):Mesh { + var profile:XML = data.mesh.extra.technique.(@profile == "Alternativa3D")[0]; + if (profile != null) { + var meshXML:XML = profile.mesh[0]; + if (meshXML != null) { + return (new DaeAlternativa3DObject(meshXML, document)).parseMesh(skin); + } + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeImage.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeImage.as new file mode 100644 index 0000000..8c403b9 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeImage.as @@ -0,0 +1,27 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeImage extends DaeElement { + + use namespace collada; + + public function DaeImage(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get init_from():String { + var element:XML = data.init_from[0]; + if (element != null) { + if (document.versionMajor > 4) { + var refXML:XML = element.ref[0]; + return (refXML == null) ? null : refXML.text().toString(); + } + return element.text().toString(); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInput.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInput.as new file mode 100644 index 0000000..441efee --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInput.as @@ -0,0 +1,53 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeInput extends DaeElement { + + use namespace collada; + + public function DaeInput(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get semantic():String { + var attribute:XML = data.@semantic[0]; + return (attribute == null) ? null : attribute.toString(); + } + + public function get source():XML { + return data.@source[0]; + } + + public function get offset():int { + var attr:XML = data.@offset[0]; + return (attr == null) ? 0 : parseInt(attr.toString(), 10); + } + + public function get setNum():int { + var attr:XML = data.@set[0]; + return (attr == null) ? 0 : parseInt(attr.toString(), 10); + } + + /** + * Если DaeSource по ссылке source имеет тип значений Number и + * количество компонент не меньше заданного, то этот метод его вернет. + */ + public function prepareSource(minComponents:int):DaeSource { + var source:DaeSource = document.findSource(this.source); + if (source != null) { + source.parse(); + if (source.numbers != null && source.stride >= minComponents) { + return source; + } else { + // document.logger.logNotEnoughDataError(); + } + } else { + document.logger.logNotFoundError(data.@source[0]); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceController.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceController.as new file mode 100644 index 0000000..eb8f85d --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceController.as @@ -0,0 +1,55 @@ +package alternativa.engine3d.loaders.collada { + + + /** + * @private + */ + public class DaeInstanceController extends DaeElement { + + use namespace collada; + + public var node:DaeNode; + + public var skin:DaeAnimatedObject; + + public function DaeInstanceController(data:XML, document:DaeDocument, node:DaeNode) { + super(data, document); + this.node = node; + } + + public function get controller():DaeController { + var controller:DaeController = document.findController(data.@url[0]); + if (controller == null) { + document.logger.logNotFoundError(data.@url[0]); + } + return controller; + } + + public function get skeletons():Vector. { + var list:XMLList = data.skeleton; + if (list.length() > 0) { + var skeletons:Vector. = new Vector.(); + for (var i:int = 0, count:int = list.length(); i < count; i++) { + var skeletonXML:XML = list[i]; + var skel:DaeNode = document.findNode(skeletonXML.text()[0]); + if (skel != null) { + skeletons.push(skel); + } else { + document.logger.logNotFoundError(skeletonXML); + } + } + return skeletons; + } + return null; + } + + public function findRootJointNodes():Vector. { + var controller:DaeController = this.controller; + if (controller != null) { + return controller.findRootJointNodes(this.skeletons); + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as new file mode 100644 index 0000000..fef0585 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeInstanceMaterial.as @@ -0,0 +1,39 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeInstanceMaterial extends DaeElement { + + use namespace collada; + + public function DaeInstanceMaterial(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get symbol():String { + var attribute:XML = data.@symbol[0]; + return (attribute == null) ? null : attribute.toString(); + } + + private function get target():XML { + return data.@target[0]; + } + + public function get material():DaeMaterial { + var mat:DaeMaterial = document.findMaterial(target); + if (mat == null) { + document.logger.logNotFoundError(target); + } + return mat; + } + + public function getBindVertexInputSetNum(semantic:String):int { + var bindVertexInputXML:XML = data.bind_vertex_input.(@semantic == semantic)[0]; + if (bindVertexInputXML == null) return 0; + var setNumXML:XML = bindVertexInputXML.@input_set[0]; + return (setNumXML == null) ? 0 : parseInt(setNumXML.toString(), 10); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeLogger.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeLogger.as new file mode 100644 index 0000000..6887544 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeLogger.as @@ -0,0 +1,53 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeLogger { + + public function DaeLogger() { + } + + private function logMessage(message:String, element:XML):void { + // var index:int = element.childIndex(); + var index:int = 0; + var name:String = (element.nodeKind() == "attribute") ? "@" + element.localName() : element.localName() + ((index > 0) ? "[" + index + "]" : ""); + var parent:* = element.parent(); + while (parent != null) { + // index = parent.childIndex(); + name = parent.localName() + ((index > 0) ? "[" + index + "]" : "") + "." + name; + parent = parent.parent(); + } + trace(message, '| "' + name + '"'); + } + + private function logError(message:String, element:XML):void { + logMessage("[ERROR] " + message, element); + } + + public function logExternalError(element:XML):void { + logError("External urls don't supported", element); + } + + public function logSkewError(element:XML):void { + logError(" don't supported", element); + } + + public function logJointInAnotherSceneError(element:XML):void { + logError("Joints in different scenes don't supported", element); + } + + public function logInstanceNodeError(element:XML):void { + logError(" don't supported", element); + } + + public function logNotFoundError(element:XML):void { + logError("Element with url \"" + element.toString() + "\" not found", element); + } + + public function logNotEnoughDataError(element:XML):void { + logError("Not enough data", element); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeMaterial.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeMaterial.as new file mode 100644 index 0000000..01f7c78 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeMaterial.as @@ -0,0 +1,61 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.materials.Material; + + /** + * @private + */ + public class DaeMaterial extends DaeElement { + + use namespace collada; + + /** + * Материал движка. + * Перед использованием вызвать parse(). + */ + public var material:Material; + + /** + * Имя текстурного канала для карты цвета объекта + * Перед использованием вызвать parse(). + */ + public var diffuseTexCoords:String; + + /** + * Материал используется. + */ + public var used:Boolean = false; + + public function DaeMaterial(data:XML, document:DaeDocument) { + super(data, document); + } + + private function parseSetParams():Object { + var params:Object = new Object(); + var list:XMLList = data.instance_effect.setparam; + for each (var element:XML in list) { + var param:DaeParam = new DaeParam(element, document); + params[param.ref] = param; + } + return params; + } + + private function get effectURL():XML { + return data.instance_effect.@url[0]; + } + + override protected function parseImplementation():Boolean { + var effect:DaeEffect = document.findEffect(effectURL); + if (effect != null) { + effect.parse(); + material = effect.getMaterial(parseSetParams()); + diffuseTexCoords = effect.diffuseTexCoords; + if (material != null) { + material.name = name; + } + return true; + } + return false; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeNode.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeNode.as new file mode 100644 index 0000000..ac29db4 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeNode.as @@ -0,0 +1,465 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.animation.Animation; + import alternativa.engine3d.animation.AnimationGroup; + import alternativa.engine3d.animation.MatrixAnimation; + import alternativa.engine3d.animation.TransformAnimation; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.objects.Mesh; + import alternativa.engine3d.objects.Skin; + + import flash.geom.Matrix3D; + import flash.geom.Vector3D; + + /** + * @private + */ + public class DaeNode extends DaeElement { + + use namespace collada; + use namespace alternativa3d; + + public var scene:DaeVisualScene; + public var parent:DaeNode; + + // Скин или рутовая кость + public var skinOrRootJoint:Boolean = false; + // Ссылка на рутовую кость скина, если есть + public var rootJoint:DaeNode; + + /** + * Анимационные каналы этой ноды + */ + private var channels:Vector.; + + /** + * Вектор контроллеров, которые ссылаются на эту ноду + */ + private var instanceControllers:Vector.; + + /** + * Массив нод в этой ноде. + */ + public var nodes:Vector.; + + /** + * Массив объектов в этой ноде. + * Перед использованием вызвать parse(). + */ + public var objects:Vector.; + + /** + * Вектор скинов в этой ноде. + * Перед использованием вызвать parse(). + */ + public var skins:Vector.; + + /** + * Создание ноды из xml. Рекурсивно создаются дочерние ноды. + */ + public function DaeNode(data:XML, document:DaeDocument, scene:DaeVisualScene = null, parent:DaeNode = null) { + super(data, document); + + this.scene = scene; + this.parent = parent; + + // Внутри объявляются другие node. + constructNodes(); + } + + private function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = new DaeNode(nodesList[i], document, scene, this); + if (node.id != null) { + document.nodes[node.id] = node; + } + nodes[i] = node; + } + } + + public function addChannel(channel:DaeChannel):void { + if (channels == null) { + channels = new Vector.(); + } + channels.push(channel); + } + + public function addInstanceController(controller:DaeInstanceController):void { + if (instanceControllers == null) { + instanceControllers = new Vector.(); + } + instanceControllers.push(controller); + } + + override protected function parseImplementation():Boolean { + this.skins = parseSkins(); + this.objects = parseObjects(); + return true; + } + + private function parseInstanceMaterials(geometry:XML):Object { + var instances:Object = new Object(); + var list:XMLList = geometry.bind_material.technique_common.instance_material; + for (var i:int = 0, count:int = list.length(); i < count; i++) { + var instance:DaeInstanceMaterial = new DaeInstanceMaterial(list[i], document); + instances[instance.symbol] = instance; + } + return instances; + } + + /** + * Возвращает ноду по сиду. + */ + public function getNodeBySid(sid:String):DaeNode { + if (sid == this.sid) { + return this; + } + + var levelNodes:Vector. > = new Vector. >; + var levelNodes2:Vector. > = new Vector. >; + + levelNodes.push(nodes); + var len:int = levelNodes.length; + while (len > 0) { + for (var i:int = 0; i < len; i++) { + var children:Vector. = levelNodes[i]; + var count:int = children.length; + for (var j:int = 0; j < count; j++) { + var node:DaeNode = children[j]; + if (node.sid == sid) { + return node; + } + if (node.nodes.length > 0) { + levelNodes2.push(node.nodes); + } + } + } + var temp:Vector. > = levelNodes; + levelNodes = levelNodes2; + levelNodes2 = temp; + levelNodes2.length = 0; + + len = levelNodes.length; + } + return null; + } + + /** + * Парсит и возвращает массив скинов, связанных с этой нодой. + */ + public function parseSkins():Vector. { + if (instanceControllers == null) { + return null; + } + var skins:Vector. = new Vector.(); + for (var i:int = 0, count:int = instanceControllers.length; i < count; i++) { + var instanceController:DaeInstanceController = instanceControllers[i]; + var controller:DaeController = instanceController.controller; + if (controller != null) { + controller.parse(); + var animatedSkinAndJoints:DaeAnimatedObject = controller.parseSkin(this, parseInstanceMaterials(instanceController.data), instanceController.skeletons); + if (animatedSkinAndJoints != null) { + var skin:Skin = Skin(animatedSkinAndJoints.object); + // Имя берем из ноды, содержащей instance_controller + skin.name = instanceController.node.name; + var animatedSkin:DaeAnimatedObject = applyAnimation(applyTransformations(skin)); + if (animatedSkin.animation != null) { + var animatedSkinAndJointsAnimation:AnimationGroup = animatedSkinAndJoints.animation as AnimationGroup; + if (animatedSkinAndJointsAnimation != null) { + var animatedSkinAnimation:AnimationGroup = animatedSkin.animation as AnimationGroup; + if (animatedSkinAnimation == null) { + animatedSkinAnimation = new AnimationGroup(skin); + animatedSkinAnimation.addAnimation(animatedSkin.animation); + animatedSkin.animation = animatedSkinAnimation; + } + // Переносим анимации + for (var a:int = 0, numAnimations:int = animatedSkinAndJointsAnimation.numAnimations; a < numAnimations; a++) { + animatedSkinAnimation.addAnimation(animatedSkinAndJointsAnimation.getAnimationAt(a)); + } + } + } else { + animatedSkin.animation = animatedSkinAndJoints.animation; + } + skins.push(animatedSkin); + } + } + } + return (skins.length > 0) ? skins : null; + } + + private function getNewName(index:int = 0):String { + var name:String = this.name; + if (name != null) { + if (index == 0) { + return name; + } else { + return name + "-" + index; + } + } + return null; + } + + /** + * Парсит и возвращает массив объектов, связанных с этой нодой. + * Может быть Mesh или Object3D, если неизвестен тип объекта. + */ + public function parseObjects():Vector. { + var objects:Vector. = new Vector.(); + if (isAlternativa3DObject()) { + var a3dObject:Object3D = parseAlternativa3DObject(); + if (a3dObject != null) { + a3dObject.name = name; + objects.push(applyAnimation(applyTransformations(a3dObject))); + return objects; + } + } else { + var children:XMLList = data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case "instance_camera": + var cam:DaeCamera = document.findCamera(child.@url[0]); + var camera:Camera3D = cam.parseCamera(); + camera.name = getNewName(objects.length); + // Поворачиваем на 180 градусов по оси X, чтобы соответствовало движку. + var rotXMatrix:Matrix3D = new Matrix3D(); + rotXMatrix.appendRotation(180, Vector3D.X_AXIS); + objects.push(applyAnimation(applyTransformations(camera, rotXMatrix))); + break; + case "instance_geometry": + var geom:DaeGeometry = document.findGeometry(child.@url[0]); + if (geom != null) { + geom.parse(); + var mesh:Mesh = geom.parseMesh(parseInstanceMaterials(child)); + if (mesh != null) { + mesh.name = getNewName(objects.length); + objects.push(applyAnimation(applyTransformations(mesh))); + } + } else { + document.logger.logNotFoundError(child.@url[0]); + } + break; + // case "instance_controller": + // Парсится в методе parseSkins(); + // break; + case "instance_node": + document.logger.logInstanceNodeError(child); + // var instanceNode:DaeNode = document.findNode(child.@url[0]); + // instanceNode.parse(); + // if (instanceNode != null) { + // var instances:Vector. = instanceNode.parseObjects(); + // for (var j:int = 0, num:int = instances.length; j < num; j++) { + // var instance:Object3D = instances[j]; + // objects.push(applyTransformationsAndAnimation(instance, instance.getMatrix())); + // } + // } else { + // document.logger.logNotFoundError(child.@url[0]); + // } + break; + } + } + } +// if (objects.length == 0) { +// // Не нашлось ни одного подходящего объекта, создаем Object3D. +// var object:Object3D = new Object3D(); +// object.name = name; +// objects.push(applyAnimation(applyTransformations(object))); +// } + return (objects.length > 0) ? objects : null; + } + + /** + * Возвращает трансформацию ноды в виде матрицы + * + * @param initialMatrix матрица, к которой будет добавлена трансформация ноды + */ + public function getMatrix(initialMatrix:Matrix3D = null):Matrix3D { + var matrix:Matrix3D = (initialMatrix == null) ? new Matrix3D() : initialMatrix; + var components:Array; + var children:XMLList = data.children(); + for (var i:int = children.length() - 1; i >= 0; i--) { + // Трансформации накладываются с конца в начало + var child:XML = children[i]; + var sid:XML = child.@sid[0]; + if (sid != null && sid.toString() == "post-rotationY") { + // Стандартный экспорт макса записал какой-то хлам, игнорируем + continue; + } + switch (child.localName()) { + case "scale" : { + components = parseNumbersArray(child); + matrix.appendScale(components[0], components[1], components[2]); + break; + } + case "rotate" : { + components = parseNumbersArray(child); + matrix.appendRotation(components[3], new Vector3D(components[0], components[1], components[2])); + break; + } + case "translate" : { + components = parseNumbersArray(child); + matrix.appendTranslation(components[0], components[1], components[2]); + break; + } + case "matrix" : { + components = parseNumbersArray(child); + matrix.append(new Matrix3D(Vector.([components[0], components[4], components[8], components[12], + components[1], components[5], components[9], components[13], + components[2], components[6], components[10], components[14], + components[3] ,components[7], components[11], components[15]]))); + break; + } + case "lookat" : { + // components = parseNumbersArray(child); + break; + } + case "skew" : { + document.logger.logSkewError(child); + break; + } + } + } + return matrix; + } + + /** + * Назначает контроллер анимации к объекту. + * + * @param animation анимация которую следует применить к объекту, + * если null, будет создана новая анимация из ноды. + */ + public function applyAnimation(object:Object3D, animation:Animation = null):DaeAnimatedObject { + animation = (animation == null) ? parseAnimation() : animation; + if (animation != null) { + animation.object = object; + } + return new DaeAnimatedObject(object, animation); + } + + /** + * Применяет трансформацию к объекту. + * + * @param prepend если не равен null, трансформация добавляется к этой матрице. + */ + public function applyTransformations(object:Object3D, prepend:Matrix3D = null, append:Matrix3D = null):Object3D { + if (append != null) { + var matrix:Matrix3D = getMatrix(prepend); + matrix.append(append); + object.setMatrix(matrix); + } else { + object.setMatrix(getMatrix(prepend)); + } + return object; + } + + private function isAlternativa3DObject():Boolean { + return data.extra.technique.(@profile == "Alternativa3D")[0] != null; + } + + private function parseAlternativa3DObject():Object3D { + var profile:XML = data.extra.technique.(@profile == "Alternativa3D")[0]; + if (profile != null) { + var containerXML:XML = profile.instance_container[0]; + if (containerXML != null) { + var container:DaeAlternativa3DObject = document.findAlternativa3DObject(containerXML.@url[0]); + if (container != null) { + return container.parseContainer(); + } else { + document.logger.logNotFoundError(containerXML.@url[0]) + } + } + var spriteXML:XML = profile.instance_sprite[0]; + if (spriteXML != null) { + var sprite:DaeAlternativa3DObject = document.findAlternativa3DObject(spriteXML.@url[0]); + if (sprite != null) { + var material:DaeMaterial = document.findMaterial(spriteXML.instance_material.@target[0]); + if (material != null) { + material.parse(); + material.used = true; + return sprite.parseSprite3D(material.material); + } else { + return sprite.parseSprite3D(); + } + } else { + document.logger.logNotFoundError(spriteXML.@url[0]) + } + } + var lodXML:XML = profile.instance_lod[0]; + if (lodXML != null) { + var lod:DaeAlternativa3DObject = document.findAlternativa3DObject(lodXML.@url[0]); + if (lod != null) { + return lod.parseLOD(); + } else { + document.logger.logNotFoundError(lodXML.@url[0]); + } + } + } + return null; + } + + /** + * Возвращает анимацию ноды. + */ + public function parseAnimation():Animation { + if (channels == null) { + return null; + } + var channel:DaeChannel = channels[0]; + channel.parse(); + if (channel.animatedParam == DaeChannel.PARAM_MATRIX) { + // Анимация матрицы + var matrixAnimation:MatrixAnimation = new MatrixAnimation(); + matrixAnimation.matrix = channel.track; + return matrixAnimation; + } + // Это не анимация матрицы, значит покомпонентная анимация + var animation:TransformAnimation = new TransformAnimation(); + var count:int = channels.length; + for (var i:int = 0; i < count; i++) { + channel = channels[i]; + channel.parse(); + switch (channel.animatedParam) { + case DaeChannel.PARAM_TRANSLATE: + animation.translation = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_X: + animation.x = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_Y: + animation.y = channel.track; + break; + case DaeChannel.PARAM_TRANSLATE_Z: + animation.z = channel.track; + break; + case DaeChannel.PARAM_ROTATION_X: + animation.rotationX = channel.track; + break; + case DaeChannel.PARAM_ROTATION_Y: + animation.rotationY = channel.track; + break; + case DaeChannel.PARAM_ROTATION_Z: + animation.rotationZ = channel.track; + break; + case DaeChannel.PARAM_SCALE: + animation.scale = channel.track; + break; + case DaeChannel.PARAM_SCALE_X: + animation.scaleX = channel.track; + break; + case DaeChannel.PARAM_SCALE_Y: + animation.scaleY = channel.track; + break; + case DaeChannel.PARAM_SCALE_Z: + animation.scaleZ = channel.track; + break; + } + } + return animation; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeParam.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeParam.as new file mode 100644 index 0000000..57b565d --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeParam.as @@ -0,0 +1,79 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeParam extends DaeElement { + + use namespace collada; + + public function DaeParam(data:XML, document:DaeDocument) { + super(data, document); + } + + public function get ref():String { + var attribute:XML = data.@ref[0]; + return (attribute == null) ? null : attribute.toString(); + } + + public function getFloat():Number { + var floatXML:XML = data.float[0]; + if (floatXML != null) { + return parseNumber(floatXML); + } + return NaN; + } + + public function getFloat4():Array { + var element:XML = data.float4[0]; + var components:Array; + if (element == null) { + element = data.float3[0]; + if (element != null) { + components = parseNumbersArray(element); + components[3] = 1.0; + } + } else { + components = parseNumbersArray(element); + } + return components; + } + + /** + * Возвращает sid параметра с типом surface. Только если тип этого элемента sampler2D и версия коллады 1.4. + */ + public function get surfaceSID():String { + var element:XML = data.sampler2D.source[0]; + return (element == null) ? null : element.text().toString(); + } + + public function get wrap_s():String { + var element:XML = data.sampler2D.wrap_s[0]; + return (element == null) ? null : element.text().toString(); + } + + public function get image():DaeImage { + var surface:XML = data.surface[0]; + var image:DaeImage; + if (surface != null) { + // Collada 1.4 + var init_from:XML = surface.init_from[0]; + if (init_from == null) { + // Error + return null; + } + image = document.findImageByID(init_from.text().toString()); + } else { + // Collada 1.5 + var imageIDXML:XML = data.instance_image.@url[0]; + if (imageIDXML == null) { + // error + return null; + } + image = document.findImage(imageIDXML); + } + return image; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaePrimitive.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaePrimitive.as new file mode 100644 index 0000000..c886d3b --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaePrimitive.as @@ -0,0 +1,269 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.materials.Material; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class DaePrimitive extends DaeElement { + + use namespace collada; + + private var verticesInput:DaeInput; + private var texCoordsInputs:Vector.; + private var inputsStride:int; + + public function DaePrimitive(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + parseInputs(); + return true; + } + + private function parseInputs():void { + texCoordsInputs = new Vector.(); + var inputsList:XMLList = data.input; + var maxInputOffset:int = 0; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "VERTEX" : + if (verticesInput == null) { + verticesInput = input; + } + break; + case "TEXCOORD" : + texCoordsInputs.push(input); + break; + } + } + var offset:int = input.offset; + maxInputOffset = (offset > maxInputOffset) ? offset : maxInputOffset; + } + inputsStride = maxInputOffset + 1; + } + + private function findTexCoordsInput(setNum:int):DaeInput { + for (var i:int = 0, count:int = texCoordsInputs.length; i < count; i++) { + var texCoordsInput:DaeInput = texCoordsInputs[i]; + if (texCoordsInput.setNum == setNum) { + return texCoordsInput; + } + } + return (texCoordsInputs.length > 0) ? texCoordsInputs[0] : null; + } + + private function get type():String { + return data.localName() as String; + } + + /** + * Заполняет заданный меш геометрией этого примитива, используя заданные вершины. + * На вершины накладывается uv маппинг. + * Перед использованием вызвать parse(). + */ + public function fillInMesh(mesh:Mesh, vertices:Vector., instanceMaterial:DaeInstanceMaterial = null):void { + var countXML:XML = data.@count[0]; + if (countXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + var numPrimitives:int = parseInt(countXML.toString(), 10); + var texCoordsInput:DaeInput; + var material:Material; + if (instanceMaterial != null) { + var dmat:DaeMaterial = instanceMaterial.material; + dmat.parse(); + if (dmat.diffuseTexCoords != null) { + texCoordsInput = findTexCoordsInput(instanceMaterial.getBindVertexInputSetNum(dmat.diffuseTexCoords)); + } else { + texCoordsInput = findTexCoordsInput(-1); + } + dmat.used = true; + material = dmat.material; + } else { + texCoordsInput = findTexCoordsInput(-1); + } + if (texCoordsInput != null) { + // Если у вершины index != -1, значит вершина где-то используется и ее нужно сдублировать + // Устанавливаем для такой вершины index в -2 + for each (var vertex:Vertex in vertices) { + while (vertex != null && vertex.index != -1) { + vertex.index = -2; + // Переходим к следующему дубликату + vertex = vertex.value; + } + } + } + var texCoords:Vector.; + var texCoordsStride:int = 1; + var texCoordsOffset:int = 0; + if (texCoordsInput != null) { + var texCoordsSource:DaeSource = texCoordsInput.prepareSource(2); + if (texCoordsSource != null) { + texCoords = texCoordsSource.numbers; + texCoordsStride = texCoordsSource.stride; + texCoordsOffset = texCoordsInput.offset; + } + } + var indicesXML:XML; + var indices:Array; + switch (this.type) { + case "polygons" : { + if (data.ph.length() > 0) { + // Полигоны с дырками не поддерживаются + // document.logger.lo + } + var indicesList:XMLList = data.p; + for (var i:int = 0, count:int = indicesList.length(); i < count; i++) { + indices = parseIntsArray(indicesList[i]); + fillInPolygon(mesh, material, vertices, verticesInput.offset, indices.length/inputsStride, indices, texCoords, texCoordsStride, texCoordsOffset); + } + break; + } + case "polylist" : { + indicesXML = data.p[0]; + if (indicesXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + indices = parseIntsArray(indicesXML); + var vcountsXML:XML = data.vcount[0]; + var vcounts:Array; + if (vcountsXML != null) { + vcounts = parseIntsArray(vcountsXML); + if (vcounts.length < numPrimitives) { + return; + } + fillInPolylist(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, vcounts, texCoords, texCoordsStride, texCoordsOffset); + } else { + fillInPolygon(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, texCoords, texCoordsStride, texCoordsOffset); + } + break; + } + case "triangles" : { + indicesXML = data.p[0]; + if (indicesXML == null) { + document.logger.logNotEnoughDataError(data); + return; + } + indices = parseIntsArray(indicesXML); + fillInTriangles(mesh, material, vertices, verticesInput.offset, numPrimitives, indices, texCoords, texCoordsStride, texCoordsOffset); + break; + } + } + } + + /** + * Добавляет uv координаты вершине или создает новую вершину, если нельзя добавить в эту. + * Новая вершина добавляется в список value этой вершины. + * @return вершина с заданными uv координатами + */ + private function applyUV(mesh:Mesh, vertex:Vertex, texCoords:Vector., index:int):Vertex { + var u:Number = texCoords[index]; + var v:Number = 1 - texCoords[int(index + 1)]; + if (vertex.index == -1) { + // Была без uv координат + vertex.u = u; + vertex.v = v; + vertex.index = index; + return vertex; + } + if (vertex.index == index) { + return vertex; + } else { + // Дублируем вершину, если её index отличается от индекса заданной uv координаты + while (vertex.value != null) { + vertex = vertex.value; + if (vertex.index == index) { + return vertex; + } + } + // Последний элемент, создаем дубликат и возвращаем + vertex.value = mesh.addVertex(vertex.x, vertex.y, vertex.z, u, v); + vertex = vertex.value; + vertex.index = index; + return vertex; + } + } + + /** + * Создает один полигон. + */ + private function fillInPolygon(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numIndices:int, indices:Array, texCoords:Vector., texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + var polygon:Vector. = new Vector.(numIndices); + for (var i:int = 0; i < numIndices; i++) { + var vertex:Vertex = vertices[indices[int(inputsStride*i + verticesOffset)]]; + if (texCoords != null) { + vertex = applyUV(mesh, vertex, texCoords, texCoordsStride*indices[int(inputsStride*i + texCoordsOffset)]); + } + polygon[i] = vertex; + } + mesh.addFace(polygon, material); + } + + private function fillInPolylist(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numFaces:int, indices:Array, vcounts:Array, texCoords:Vector. = null, texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + var polygon:Vector. = new Vector.(); + var polyIndex:int = 0; + for (var i:int = 0; i < numFaces; i++) { + var count:int = vcounts[i]; + if (count >= 3) { + polygon.length = count; + for (var j:int = 0; j < count; j++) { + var vertexIndex:int = inputsStride*(polyIndex + j); + var vertex:Vertex = vertices[indices[int(vertexIndex + verticesOffset)]]; + if (texCoords != null) { + vertex = applyUV(mesh, vertex, texCoords, texCoordsStride*indices[int(vertexIndex + texCoordsOffset)]); + } + polygon[j] = vertex; + } + polyIndex += count; + mesh.addFace(polygon, material); + } + } + } + + private function fillInTriangles(mesh:Mesh, material:Material, vertices:Vector., verticesOffset:int, numFaces:int, indices:Array, texCoords:Vector. = null, texCoordsStride:int = 1, texCoordsOffset:int = 0):void { + for (var i:int = 0; i < numFaces; i++) { + var index:int = 3*inputsStride*i; + var vertexIndex:int = index + verticesOffset; + var a:Vertex = vertices[indices[int(vertexIndex)]]; + var b:Vertex = vertices[indices[int(vertexIndex + inputsStride)]]; + var c:Vertex = vertices[indices[int(vertexIndex + 2*inputsStride)]]; + if (texCoords != null) { + var texIndex:int = index + texCoordsOffset; + a = applyUV(mesh, a, texCoords, texCoordsStride*indices[int(texIndex)]); + b = applyUV(mesh, b, texCoords, texCoordsStride*indices[int(texIndex + inputsStride)]); + c = applyUV(mesh, c, texCoords, texCoordsStride*indices[int(texIndex + 2*inputsStride)]); + } + mesh.addTriFace(a, b, c, material); + } + } + + /** + * Сравнивает вершины, используемые в примитиве с указанными + * Перед использованием вызвать parse(). + */ + public function verticesEquals(otherVertices:DaeVertices):Boolean { + var vertices:DaeVertices = document.findVertices(verticesInput.source); + if (vertices == null) { + document.logger.logNotFoundError(verticesInput.source); + } + return vertices == otherVertices; + } + + public function get materialSymbol():String { + var attr:XML = data.@material[0]; + return (attr == null) ? null : attr.toString(); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSampler.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSampler.as new file mode 100644 index 0000000..aa3ce5e --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSampler.as @@ -0,0 +1,104 @@ +package alternativa.engine3d.loaders.collada { + + import alternativa.engine3d.animation.Track; + import alternativa.engine3d.animation.keys.MatrixKey; + import alternativa.engine3d.animation.keys.PointKey; + import alternativa.engine3d.animation.keys.ValueKey; + + import flash.geom.Matrix3D; + + /** + * @private + */ + public class DaeSampler extends DaeElement { + + use namespace collada; + + private var times:Vector.; + private var values:Vector.; + private var timesStride:int; + private var valuesStride:int; + + public function DaeSampler(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + var inputsList:XMLList = data.input; + + var inputSource:DaeSource; + var outputSource:DaeSource; + for (var i:int = 0, count:int = inputsList.length(); i < count; i++) { + var input:DaeInput = new DaeInput(inputsList[i], document); + var semantic:String = input.semantic; + if (semantic != null) { + switch (semantic) { + case "INPUT" : + inputSource = input.prepareSource(1); + if (inputSource != null) { + times = inputSource.numbers; + timesStride = inputSource.stride; + } + break; + case "OUTPUT" : + outputSource = input.prepareSource(1); + if (outputSource != null) { + values = outputSource.numbers; + valuesStride = outputSource.stride; + } + break; + } + } + } + return true; + } + + public function parseValuesTrack():Track { + if (times != null && values != null && timesStride > 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + track.addKey(new ValueKey(times[int(timesStride*i)], values[int(valuesStride*i)])); + } + track.sortKeys(); + // TODO:: Всякие исключительные ситуации с индексами + return track; + } + return null; + } + + public function parseMatrixTrack():Track { + if (times != null && values != null && timesStride != 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + var index:int = valuesStride*i; + var matrix:Matrix3D = new Matrix3D(Vector.([values[index], values[index + 4], values[index + 8], values[index + 12], + values[index + 1], values[index + 5], values[index + 9], values[index + 13], + values[index + 2], values[index + 6], values[index + 10], values[index + 14], + values[index + 3] ,values[index + 7], values[index + 11], values[index + 15]])); + track.addKey(new MatrixKey(times[i*timesStride], matrix)); + } + track.sortKeys(); + return track; + } + return null; + } + + public function parsePointsTrack():Track { + if (times != null && values != null && timesStride != 0) { + var track:Track = new Track(); + var count:int = times.length/timesStride; + for (var i:int = 0; i < count; i++) { + var index:int = i*valuesStride; + track.addKey(new PointKey(times[i*timesStride], values[index], values[index + 1], values[index + 2])); + } + track.sortKeys(); + return track; + // TODO:: Всякие исключительные ситуации с индексами + } + return null; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSource.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSource.as new file mode 100644 index 0000000..8ee6f55 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeSource.as @@ -0,0 +1,153 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeSource extends DaeElement { + + use namespace collada; + + /** + * Типы массивов + */ + private const FLOAT_ARRAY:String = "float_array"; + private const INT_ARRAY:String = "int_array"; + private const NAME_ARRAY:String = "Name_array"; + /** + * Массив элементов типа Number. + * Перед использованием вызвать parse(). + */ + public var numbers:Vector.; + /** + * Массив элементов типа int. + * Перед использованием вызвать parse(). + */ + public var ints:Vector.; + /** + * Массив элементов типа string. + * Перед использованием вызвать parse(). + */ + + public var names:Vector.; + /** + * Размерность типов в массиве numbers или ints. + * Перед использованием вызвать parse(). + */ + public var stride:int; + + public function DaeSource(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются arrays. + constructArrays(); + } + + private function constructArrays():void { + var children:XMLList = data.children(); + for (var i:int = 0, count:int = children.length(); i < count; i++) { + var child:XML = children[i]; + switch (child.localName()) { + case FLOAT_ARRAY : + case INT_ARRAY : + case NAME_ARRAY : + var array:DaeArray = new DaeArray(child, document); + if (array.id != null) { + document.arrays[array.id] = array; + } + break; + } + } + } + + private function get accessor():XML { + return data.technique_common.accessor[0]; + } + + override protected function parseImplementation():Boolean { + var accessor:XML = this.accessor; + if (accessor != null) { + var arrayXML:XML = accessor.@source[0]; + var array:DaeArray = (arrayXML == null) ? null : document.findArray(arrayXML); + if (array != null) { + var countXML:String = accessor.@count[0]; + if (countXML != null) { + var count:int = parseInt(countXML.toString(), 10); + var offsetXML:XML = accessor.@offset[0]; + var strideXML:XML = accessor.@stride[0]; + var offset:int = (offsetXML == null) ? 0 : parseInt(offsetXML.toString(), 10); + var stride:int = (strideXML == null) ? 1 : parseInt(strideXML.toString(), 10); + array.parse(); + if (array.array.length < (offset + (count*stride))) { + document.logger.logNotEnoughDataError(accessor); + return false; + } + this.stride = parseArray(offset, count, stride, array.array, array.type); + return true; + } + } else { + document.logger.logNotFoundError(arrayXML); + } + } + return false; + } + + private function numValidParams(params:XMLList):int { + var res:int = 0; + for (var i:int = 0, count:int = params.length(); i < count; i++) { + if (params[i].@name[0] != null) { + res++; + } + } + return res; + } + + private function parseArray(offset:int, count:int, stride:int, array:Array, type:String):int { + var params:XMLList = this.accessor.param; + var arrStride:int = Math.max(numValidParams(params), stride); + switch (type) { + case FLOAT_ARRAY: + numbers = new Vector.(int(arrStride*count)); + break; + case INT_ARRAY: + ints = new Vector.(int(arrStride*count)); + break; + case NAME_ARRAY: + names = new Vector.(int(arrStride*count)); + break; + } + var curr:int = 0; + for (var i:int = 0; i < arrStride; i++) { + // Только param, у которого установлен name, должен быть считан + var param:XML = params[i]; + if (param == null || param.hasOwnProperty("@name")) { + var j:int; + switch (type) { + case FLOAT_ARRAY: + for (j = 0; j < count; j++) { + var value:String = array[int(offset + stride*j + i)]; + if (value.indexOf(",") != -1) { + value = value.replace(/,/, "."); + } + numbers[int(arrStride*j + curr)] = parseFloat(value); + } + break; + case INT_ARRAY: + for (j = 0; j < count; j++) { + ints[int(arrStride*j + curr)] = parseInt(array[int(offset + stride*j + i)], 10); + } + break; + case NAME_ARRAY: + for (j = 0; j < count; j++) { + names[int(arrStride*j + curr)] = array[int(offset + stride*j + i)]; + } + break; + + } + curr++; + } + } + return arrStride; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVertices.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVertices.as new file mode 100644 index 0000000..3e69b1c --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVertices.as @@ -0,0 +1,61 @@ +package alternativa.engine3d.loaders.collada { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class DaeVertices extends DaeElement { + + use namespace collada; + + /** + * Источник данных координат вершин. Содержит координаты в массиве numbers. + * Свойство stride источника не меньше трех. + * Перед использованием вызвать parse(). + */ + private var positions:DaeSource; + //private var texCoords:Vector.; + + public function DaeVertices(data:XML, document:DaeDocument) { + super(data, document); + } + + override protected function parseImplementation():Boolean { + // Получаем массив координат вершин + var inputXML:XML = data.input.(@semantic == "POSITION")[0]; + if (inputXML != null) { + positions = (new DaeInput(inputXML, document)).prepareSource(3); + if (positions != null) { + return true; + } + } + return false; + } + + /** + * Создает вершины в меше. У каждой вершины index устанавливается в -1. + * Перед использованием вызвать parse(). + * + * @return вектор вершин и их индексов + */ + public function fillInMesh(mesh:Mesh):Vector. { + var stride:int = positions.stride; + var coords:Vector. = positions.numbers; + var numVerts:int = positions.numbers.length/stride; + var createdVertices:Vector. = new Vector.(numVerts); + var i:int; + for (i = 0; i < numVerts; i++) { + var offset:int = stride*i; + var vertex:Vertex = mesh.addVertex(coords[offset], coords[int(offset + 1)], coords[int(offset + 2)], 0, 0); + vertex.index = -1; + createdVertices[i] = vertex; + } + return createdVertices; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVisualScene.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVisualScene.as new file mode 100644 index 0000000..c23fcdf --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/DaeVisualScene.as @@ -0,0 +1,33 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public class DaeVisualScene extends DaeElement { + + use namespace collada; + + public var nodes:Vector.; + + public function DaeVisualScene(data:XML, document:DaeDocument) { + super(data, document); + + // Внутри объявляются node. + constructNodes(); + } + + public function constructNodes():void { + var nodesList:XMLList = data.node; + var count:int = nodesList.length(); + nodes = new Vector.(count); + for (var i:int = 0; i < count; i++) { + var node:DaeNode = new DaeNode(nodesList[i], document, this); + if (node.id != null) { + document.nodes[node.id] = node; + } + nodes[i] = node; + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/collada.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/collada.as new file mode 100644 index 0000000..4803d66 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/collada/collada.as @@ -0,0 +1,7 @@ +package alternativa.engine3d.loaders.collada { + + /** + * @private + */ + public namespace collada = "http://www.collada.org/2005/11/COLLADASchema"; +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderErrorEvent.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderErrorEvent.as new file mode 100644 index 0000000..3719db1 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderErrorEvent.as @@ -0,0 +1,48 @@ +package alternativa.engine3d.loaders.events { + import flash.events.ErrorEvent; + import flash.events.Event; + + public class LoaderErrorEvent extends ErrorEvent { + + public static const LOADER_ERROR:String = "loaderError"; + + private var _url:String; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param url адрес файла, при загрузке которого произошла проблема + * @param text описание ошибки + */ + public function LoaderErrorEvent(type:String, url:String, text:String) { + super(type); + this.text = text; + _url = url; + } + + /** + * Адрес файла, при загрузке которого произошла проблема + */ + public function get url():String { + return _url; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderErrorEvent(type, _url, text); + } + + /** + * Создаёт строковое представление объекта. + * @return строковое представление объекта + */ + override public function toString():String { + return "[LoaderErrorEvent url=" + _url + ", text=" + text + "]"; + } + + } +} + diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderEvent.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderEvent.as new file mode 100644 index 0000000..6768155 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderEvent.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + + /** + * Событие загрузчиков ресурсов, состоящих из нескольких частей. + */ + public class LoaderEvent extends Event { + /** + * Событие начала загрузки очередной части ресурса. + */ + public static const PART_OPEN:String = "partOpen"; + /** + * Событие окончания загрузки очередной части ресурса. + */ + public static const PART_COMPLETE:String = "partComplete"; + + private var _partsTotal:int; + private var _currentPart:int; + private var _target:Object; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param partsTotal общее количество загружаемых частей + * @param currentPart номер части, к которому относится событие. Нумерация начинается с нуля + * @param target объект, к которому относится событие + */ + public function LoaderEvent(type:String, partsTotal:int, currentPart:int, target:Object = null) { + super(type); + _partsTotal = partsTotal; + _currentPart = currentPart; + _target = target; + } + + /** + * Общее количество загружаемых частей. + */ + public function get partsTotal():int { + return _partsTotal; + } + + /** + * Номер части, к которому относится событие. Нумерация начинается с нуля + */ + public function get currentPart():int { + return _currentPart; + } + + /** + * Объект, содержащийся в событии + */ + override public function get target():Object { + return _target; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderEvent(type, _partsTotal, _currentPart, _target); + } + + /** + * Создаёт строкове представление объекта. + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderEvent type=" + type + ", partsTotal=" + _partsTotal + ", currentPart=" + _currentPart + ", target=" + _target + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderProgressEvent.as b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderProgressEvent.as new file mode 100644 index 0000000..c66f7f8 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/loaders/events/LoaderProgressEvent.as @@ -0,0 +1,73 @@ +package alternativa.engine3d.loaders.events { + import flash.events.Event; + import flash.events.ProgressEvent; + + /** + * Событие прогресса загрузки ресурсов, состоящих из нескольких частей. + */ + public class LoaderProgressEvent extends ProgressEvent { + + /** + * Событие прогресса загрузки очередной части ресурса. + */ + public static const LOADER_PROGRESS:String = "loaderProgress"; + + private var _filesTotal:int; + private var _filesLoaded:int; + private var _totalProgress:Number = 0; + + /** + * Создаёт новый экземпляр. + * @param type тип события + * @param filesTotal общее количество загружаемых файлов + * @param filesLoaded количество полностью загруженных файлов + * @param totalProgress общий прогресс загрузки, выраженный числом в интервале [0, 1] + * @param bytesLoaded количество загруженных байт загружаемого в данный момент файла + * @param bytesTotal объём загружаемого в данный момент файла + */ + public function LoaderProgressEvent(type:String, filesTotal:int, filesLoaded:int, totalProgress:Number = 0, bytesLoaded:uint = 0, bytesTotal:uint = 0) { + super(type, false, false, bytesLoaded, bytesTotal); + _filesTotal = filesTotal; + _filesLoaded = filesLoaded; + _totalProgress = totalProgress; + } + + /** + * Общее количество загружаемых файлов. + */ + public function get filesTotal():int { + return _filesTotal; + } + + /** + * Количество полностью загруженных файлов. + */ + public function get filesLoaded():int { + return _filesLoaded; + } + + /** + * Общий прогресс загрузки, выраженный числом в интервале [0, 1]. + */ + public function get totalProgress():Number { + return _totalProgress; + } + + /** + * Клонирует объект. + * @return клон объекта + */ + override public function clone():Event { + return new LoaderProgressEvent(type, _filesTotal, _filesLoaded, _totalProgress, bytesLoaded, bytesTotal); + } + + /** + * Создаёт строкове представление объекта. + * @return строкове представление объекта + */ + override public function toString():String { + return "[LoaderProgressEvent filesTotal=" + _filesTotal + ", filesLoaded=" + _filesLoaded + ", totalProgress=" + _totalProgress.toFixed(2) + "]"; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/materials/FillMaterial.as b/Alternativa3D7/7.4/alternativa/engine3d/materials/FillMaterial.as new file mode 100644 index 0000000..1bfecca --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/materials/FillMaterial.as @@ -0,0 +1,80 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class FillMaterial extends Material { + + public var color:int; + public var alpha:Number; + public var lineThickness:Number; + public var lineColor:int; + + public function FillMaterial(color:int = 0x7F7F7F, alpha:Number = 1, lineThickness:Number = -1, lineColor:int = 0xFFFFFF) { + this.color = color; + this.alpha = alpha; + this.lineThickness = lineThickness; + this.lineColor = lineColor; + } + + override alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var next:Face; + // Отрисовка + if (lineThickness >= 0) canvas.gfx.lineStyle(lineThickness, lineColor); + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + if (alpha > 0) canvas.gfx.beginFill(color, alpha); + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + numVertices++; + } + if (alpha <= 0) { + vertex = face.wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/vertex.cameraZ, vertex.cameraY*viewSizeY/vertex.cameraZ); + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + override alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var next:Face; + // Отрисовка + if (lineThickness >= 0) canvas.gfx.lineStyle(lineThickness, lineColor); + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + if (alpha > 0) canvas.gfx.beginFill(color, alpha); + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + numVertices++; + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/materials/Material.as b/Alternativa3D7/7.4/alternativa/engine3d/materials/Material.as new file mode 100644 index 0000000..54510a9 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/materials/Material.as @@ -0,0 +1,30 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + + use namespace alternativa3d; + + public class Material { + + public var name:String; + + alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + clearLinks(list); + } + + alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + clearLinks(list); + } + + alternativa3d function clearLinks(list:Face):void { + while (list != null) { + var next:Face = list.processNext; + list.processNext = null; + list = next; + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/materials/TextureMaterial.as b/Alternativa3D7/7.4/alternativa/engine3d/materials/TextureMaterial.as new file mode 100644 index 0000000..3d023f7 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/materials/TextureMaterial.as @@ -0,0 +1,560 @@ +package alternativa.engine3d.materials { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + import flash.display.BitmapData; + import flash.filters.ConvolutionFilter; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace alternativa3d; + + public class TextureMaterial extends Material { + + static private const filter:ConvolutionFilter = new ConvolutionFilter(2, 2, [1, 1, 1, 1], 4, 0, false, true); + static private const matrix:Matrix = new Matrix(); + static private const rect:Rectangle = new Rectangle(); + static private const point:Point = new Point(); + + static protected var drawVertices:Vector. = new Vector.(); + static protected var drawUVTs:Vector. = new Vector.(); + static protected var drawIndices:Vector. = new Vector.(); + + public var diffuseMapURL:String; + public var opacityMapURL:String; + + private var _texture:BitmapData; + + private var _mipMapping:int = 0; + + private var mipMap:Vector.; + private var numMaps:int = 0; + + public var repeat:Boolean = false; + public var smooth:Boolean = true; + + public var resolution:Number = 1; + + public var threshold:Number = 0.01; + + public var correctUV:Boolean = false; + + public function TextureMaterial(texture:BitmapData = null, repeat:Boolean = false, smooth:Boolean = true, mipMapping:int = 0, resolution:Number = 1) { + _texture = texture; + this.repeat = repeat; + this.smooth = smooth; + _mipMapping = mipMapping; + if (_texture != null && _mipMapping > 0) { + calculateMipMaps(); + } + this.resolution = resolution; + } + + public function get texture():BitmapData { + return _texture; + } + + public function set texture(value:BitmapData):void { + if (value != _texture) { + _texture = value; + disposeMipMaps(); + if (value != null && _mipMapping > 0) { + calculateMipMaps(); + } + } + } + + public function get mipMapping():int { + return _mipMapping; + } + + public function set mipMapping(value:int):void { + if (value < 0) value = 0; + if (value != _mipMapping) { + if (_texture != null) { + if (_mipMapping == 0 && value > 0) { + calculateMipMaps(); + } else if (_mipMapping > 0 && value == 0) { + disposeMipMaps(); + } + } + _mipMapping = value; + } + } + + private function disposeMipMaps():void { + for (var i:int = 1; i < numMaps; i++) { + (mipMap[i] as BitmapData).dispose(); + } + mipMap = null; + numMaps = 0; + } + + private function calculateMipMaps(maxLevel:int = 12):void { + mipMap = new Vector.(); + numMaps = 0; + matrix.identity(); + mipMap[numMaps] = _texture; + numMaps++; + filter.preserveAlpha = !_texture.transparent; + var bmp:BitmapData = (_texture.width*_texture.height > 16777215) ? _texture.clone() : new BitmapData(_texture.width, _texture.height, _texture.transparent); + var current:BitmapData = _texture; + var w:Number = rect.width = _texture.width; + var h:Number = rect.height = _texture.height; + while (numMaps <= maxLevel && w > 1 && h > 1 && rect.width > 1 && rect.height > 1) { + bmp.applyFilter(current, rect, point, filter); + rect.width = w >> 1; + rect.height = h >> 1; + matrix.a = rect.width/w; + matrix.d = rect.height/h; + w *= 0.5; + h *= 0.5; + current = new BitmapData(rect.width, rect.height, _texture.transparent, 0); + current.draw(bmp, matrix, null, null, null, false); + mipMap[numMaps] = current; + numMaps++; + } + bmp.dispose(); + } + + override alternativa3d function draw(camera:Camera3D, canvas:Canvas, list:Face, distance:Number):void { + var face:Face; + var next:Face; + var last:Face; + var wrapper:Wrapper; + var vertex:Vertex; + var a:int; + var b:int; + var c:int; + var t:Number; + var f:Number; + var j:int; + var mu:Number; + var mv:Number; + var du:Number; + var dv:Number; + var drawTexture:BitmapData; + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var vertices:Vector. = drawVertices; + var uvts:Vector. = drawUVTs; + var indices:Vector. = drawIndices; + var numVertices:int; + var verticesLength:int; + var uvtsLength:int; + var indicesLength:int; + var numDraws:int = camera.numDraws; + var numPolygons:int = camera.numPolygons; + var numTriangles:int = camera.numTriangles; + // Если нет текстуры, нужно просто расцепить список + if (_texture == null) { + clearLinks(list); + return; + } + // Мипмаппинг + if (_mipMapping < 2) { + numDraws++; + numVertices = 0; + verticesLength = 0; + uvtsLength = 0; + indicesLength = 0; + for (face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + wrapper = face.wrapper; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + a = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + a = vertex.index; + } + wrapper = wrapper.next; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + b = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + b = vertex.index; + } + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + c = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + c = vertex.index; + } + drawIndices[indicesLength] = a; + indicesLength++; + drawIndices[indicesLength] = b; + indicesLength++; + drawIndices[indicesLength] = c; + indicesLength++; + b = c; + numTriangles++; + } + numPolygons++; + } + // Подрезка + vertices.length = verticesLength; + uvts.length = uvtsLength; + indices.length = indicesLength; + // Отрисовка + if (_mipMapping == 0) { + // Без мипмаппинга + drawTexture = _texture; + } else { + // Мипмаппинг по удалённости объекта от камеры + f = camera.focalLength*resolution; + var level:int = (distance >= f) ? (1 + Math.log(distance/f)*1.442695040888963387) : 0; + if (level >= numMaps) level = numMaps - 1; + drawTexture = mipMap[level]; + } + if (correctUV) { + du = -0.5/(drawTexture.width - 1); + dv = -0.5/(drawTexture.height - 1); + mu = 1 - du - du; + mv = 1 - dv - dv; + for (j = 0; j < uvtsLength; j++) { + uvts[j] = uvts[j]*mu + du; j++; + uvts[j] = uvts[j]*mv + dv; j++; + } + } + canvas.gfx.beginBitmapFill(drawTexture, null, repeat, smooth); + canvas.gfx.drawTriangles(vertices, indices, uvts, "none"); + } else { + // Расчёт Z-баунда + var z:Number; + var min:Number = 1e+22; + var max:Number = -1; + for (face = list; face != null; face = face.processNext) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + z = wrapper.vertex.cameraZ; + if (z < min) min = z; + if (z > max) max = z; + } + } + // Расстояние нулевого мипа + f = camera.focalLength*resolution; + // Минимальный и максимальный уровень + var minLevel:int = (min >= f) ? (1 + Math.log(min/f)*1.442695040888963387) : 0; + if (minLevel >= numMaps) minLevel = numMaps - 1; + var maxLevel:int = (max >= f) ? (1 + Math.log(max/f)*1.442695040888963387) : 0; + if (maxLevel >= numMaps) maxLevel = numMaps - 1; + // Рассечение Z-плоскостями начиная с дальних и отрисовка + z = f*Math.pow(2, maxLevel - 1); + var temporaryWrapper:Wrapper; + for (var i:int = maxLevel; i >= minLevel; i--) { + numDraws++; + numVertices = 0; + verticesLength = 0; + uvtsLength = 0; + indicesLength = 0; + var zMin:Number = z - threshold; + var zMax:Number = z + threshold; + for (face = list,list = null,last = null; face != null; face = next) { + next = face.processNext; + face.processNext = null; + wrapper = null; + if (i == minLevel) { + wrapper = face.wrapper; + } else { + var w:Wrapper = face.wrapper; + var az:Number = w.vertex.cameraZ; + w = w.next; + var bz:Number = w.vertex.cameraZ; + w = w.next; + var cz:Number = w.vertex.cameraZ; + w = w.next; + var behind:Boolean = az < zMin || bz < zMin || cz < zMin; + var infront:Boolean = az > zMax || bz > zMax || cz > zMax; + for (; w != null; w = w.next) { + var vz:Number = w.vertex.cameraZ; + if (vz < zMin) { + behind = true; + } else if (vz > zMax) { + infront = true; + } + } + if (!behind) { + wrapper = face.wrapper; + } else if (!infront) { + if (list != null) { + last.processNext = face; + } else { + list = face; + } + last = face; + } else { + var negative:Face = face.create(); + camera.lastFace.next = negative; + camera.lastFace = negative; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + var va:Vertex = w.vertex; + az = va.cameraZ; + for (w = face.wrapper; w != null; w = w.next) { + var vb:Vertex = w.vertex; + bz = vb.cameraZ; + if (az < zMin && bz > zMax || az > zMax && bz < zMin) { + t = (z - az)/(bz - az); + var v:Vertex = vb.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = va.cameraX + (vb.cameraX - va.cameraX)*t; + v.cameraY = va.cameraY + (vb.cameraY - va.cameraY)*t; + v.cameraZ = z; + v.u = va.u + (vb.u - va.u)*t; + v.v = va.v + (vb.v - va.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + wrapper = wNew; + } + wPositive = wNew; + } + if (bz <= zMax) { + wNew = w.create(); + wNew.vertex = vb; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bz >= zMin) { + wNew = w.create(); + wNew.vertex = vb; + if (wPositive != null) { + wPositive.next = wNew; + } else { + wrapper = wNew; + } + wPositive = wNew; + } + va = vb; + az = bz; + } + if (list != null) { + last.processNext = negative; + } else { + list = negative; + } + last = negative; + temporaryWrapper = wrapper; + } + } + if (wrapper != null) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + a = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + a = vertex.index; + } + wrapper = wrapper.next; + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + b = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + b = vertex.index; + } + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.drawID != numDraws) { + t = 1/vertex.cameraZ; + vertices[verticesLength] = vertex.cameraX*viewSizeX*t; + verticesLength++; + vertices[verticesLength] = vertex.cameraY*viewSizeY*t; + verticesLength++; + uvts[uvtsLength] = vertex.u; + uvtsLength++; + uvts[uvtsLength] = vertex.v; + uvtsLength++; + uvts[uvtsLength] = t; + uvtsLength++; + c = numVertices; + vertex.index = numVertices++; + vertex.drawID = numDraws; + } else { + c = vertex.index; + } + drawIndices[indicesLength] = a; + indicesLength++; + drawIndices[indicesLength] = b; + indicesLength++; + drawIndices[indicesLength] = c; + indicesLength++; + b = c; + numTriangles++; + } + numPolygons++; + if (temporaryWrapper != null) { + for (wrapper = temporaryWrapper; wrapper != null; wrapper = wrapper.next) wrapper.vertex = null; + camera.lastWrapper.next = temporaryWrapper; + camera.lastWrapper = wPositive; + temporaryWrapper = null; + } + } + } + // Следующая плоскость + z *= 0.5; + // Подрезка + vertices.length = verticesLength; + uvts.length = uvtsLength; + indices.length = indicesLength; + // Отрисовка + drawTexture = mipMap[i]; + if (correctUV) { + du = -0.5/(drawTexture.width - 1); + dv = -0.5/(drawTexture.height - 1); + mu = 1 - du - du; + mv = 1 - dv - dv; + for (j = 0; j < uvtsLength; j++) { + uvts[j] = uvts[j]*mu + du; j++; + uvts[j] = uvts[j]*mv + dv; j++; + } + } + canvas.gfx.beginBitmapFill(drawTexture, null, repeat, smooth); + canvas.gfx.drawTriangles(vertices, indices, uvts, "none"); + } + } + camera.numDraws = numDraws; + camera.numPolygons = numPolygons; + camera.numTriangles = numTriangles; + } + + override alternativa3d function drawViewAligned(camera:Camera3D, canvas:Canvas, list:Face, distance:Number, a:Number, b:Number, c:Number, d:Number, tx:Number, ty:Number):void { + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var face:Face; + var next:Face; + // Если нет текстуры, нужно просто расцепить список + if (_texture == null) { + clearLinks(list); + return; + } + var drawTexure:BitmapData; + if (_mipMapping == 0) { + // Без мипмаппинга + drawTexure = _texture; + } else { + // Мипмаппинг по удалённости объекта от камеры + var f:Number = camera.focalLength*resolution; + var level:int = (distance >= f) ? (1 + Math.log(distance/f)*1.442695040888963387) : 0; + if (level >= numMaps) level = numMaps - 1; + drawTexure = mipMap[level]; + } + // Коррекция матрицы + var tw:Number = drawTexure.width; + var th:Number = drawTexure.height; + matrix.a = a/tw; + matrix.b = b/tw; + matrix.c = c/th; + matrix.d = d/th; + matrix.tx = tx; + matrix.ty = ty; + // Отрисовка + canvas.gfx.beginBitmapFill(drawTexure, matrix, repeat, smooth); + for (face = list; face != null; face = next) { + next = face.processNext; + face.processNext = null; + var wrapper:Wrapper = face.wrapper; + var vertex:Vertex = wrapper.vertex; + canvas.gfx.moveTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + var numVertices:int = -1; + for (wrapper = wrapper.next; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + canvas.gfx.lineTo(vertex.cameraX*viewSizeX/distance, vertex.cameraY*viewSizeY/distance); + numVertices++; + } + camera.numTriangles += numVertices; + camera.numPolygons++; + } + camera.numDraws++; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/AnimSprite.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/AnimSprite.as new file mode 100644 index 0000000..c80ae52 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/AnimSprite.as @@ -0,0 +1,46 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.materials.Material; + + use namespace alternativa3d; + + /** + * Анимированный спрайт. + * Анимация осуществляется путём переключения изображений, + * хранящихся в списке textures + */ + public class AnimSprite extends Sprite3D { + + /** + * Список кадров изображений + */ + public var materials:Vector.; + /** + * Устанавливаемый кадр + */ + public var frame:uint = 0; + + public function AnimSprite(width:Number = 100, height:Number = 100, materials:Vector. = null) { + super(width, height); + this.materials = materials; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (materials == null) return; + material = materials[frame]; + super.draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (materials == null) return null; + material = materials[frame]; + return super.getGeometry(camera, object); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Axes.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Axes.as new file mode 100644 index 0000000..ef24dff --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Axes.as @@ -0,0 +1,116 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Вспомогательный объект, иллюстрирующий систему координат + */ + public class Axes extends Object3D { + + public var axisLength:Number; + public var lineThickness:Number; + public var dotRadius:Number; + public var textSize:Number; + + public function Axes(axisLength:Number = 30, lineThickness:Number = 0, dotRadius:Number = 2, textSize:Number = 10):void { + this.axisLength = axisLength; + this.lineThickness = lineThickness; + this.dotRadius = dotRadius; + this.textSize = textSize; + boundMinX = -dotRadius; + boundMinY = -dotRadius; + boundMinZ = -dotRadius; + boundMaxX = dotRadius; + boundMaxY = dotRadius; + boundMaxZ = dotRadius; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + /* + var p:Vector. = Vector.([0, 0, 0, axisLength, 0, 0, 0, axisLength, 0, 0, 0, axisLength]); + var d:Vector. = new Vector.(8); + object.transformation.transformVectors(p, p); + + // Центр за камерой + if (p[2] < camera.nearClipping) return; + + Utils3D.projectVectors(camera.projectionMatrix, p, d, new Vector.()); + var size:Number = camera.viewSize/p[2]; + + // Подготовка канваса + var canvas:Canvas = parentCanvas.getChildCanvas(true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + + var gfx:Graphics = canvas.gfx; + var text:TextField; + + // Ось X + if (p[5] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0xFF0000); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[2], d[3]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[2]; + text.y = d[3]; + text.text = "X"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0xFF0000)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Y + if (p[8] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x00FF00); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[4], d[5]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[4]; + text.y = d[5]; + text.text = "Y"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x00FF00)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Ось Z + if (p[11] >= camera.nearClipping) { + gfx.lineStyle(lineThickness*size, 0x0000FF); + gfx.moveTo(d[0], d[1]); + gfx.lineTo(d[6], d[7]); + + text = new TextField(); + text.autoSize = TextFieldAutoSize.LEFT; + text.selectable = false; + text.x = d[6]; + text.y = d[7]; + text.text = "Z"; + text.setTextFormat(new TextFormat("Tahoma", textSize*size, 0x0000FF)); + + canvas.addChild(text); + canvas._numChildren++; + } + + // Начало координат + gfx.lineStyle(); + gfx.beginFill(0xFFFFFF); + gfx.drawCircle(d[0], d[1], dotRadius*size); + */ + //debugDrawBoundRaduis(camera, object, canvas); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Bone.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Bone.as new file mode 100644 index 0000000..6981b3d --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Bone.as @@ -0,0 +1,57 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + + use namespace alternativa3d; + + public class Bone extends Joint { + + public var length:Number; + public var distance:Number; + + // Длина кости + private var lx:Number; + private var ly:Number; + private var lz:Number; + private var ldot:Number; + + public function Bone(length:Number, distance:Number) { + this.length = length; + this.distance = distance; + } + + override alternativa3d function calculateBindingMatrix(parent:Object3D):void { + super.calculateBindingMatrix(parent); + lx = mc*length; + ly = mg*length; + lz = mk*length; + ldot = lx*lx + ly*ly + lz*lz; + } + + public function bindVerticesByDistance(skin:Skin):void { + for (var vertex:Vertex = skin.vertexList; vertex != null; vertex = vertex.next) bindVertexByDistance(vertex); + } + + public function bindVertexByDistance(vertex:Vertex):void { + var vx:Number = vertex.x - md; + var vy:Number = vertex.y - mh; + var vz:Number = vertex.z - ml; + var dot:Number = vx*lx + vy*ly + vz*lz; + if (dot > 0) { + if (ldot > dot) { + dot /= ldot; + vx = vertex.x - md - dot*lx; + vy = vertex.y - mh - dot*ly; + vz = vertex.z - ml - dot*lz; + } else { + vx -= lx; + vy -= ly; + vz -= lz; + } + } + bindVertex(vertex, 1 - Math.sqrt(vx*vx + vy*vy + vz*vz)/distance); + } + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Joint.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Joint.as new file mode 100644 index 0000000..58a3ac0 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Joint.as @@ -0,0 +1,215 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Geometry; + + use namespace alternativa3d; + + public class Joint extends Object3D { + + private var joints:Vector. = new Vector.(); + private var _numJoints:uint = 0; + + alternativa3d var vertexBindingList:VertexBinding; + + // Матрица привязки + private var ba:Number; + private var bb:Number; + private var bc:Number; + private var bd:Number; + private var be:Number; + private var bf:Number; + private var bg:Number; + private var bh:Number; + private var bi:Number; + private var bj:Number; + private var bk:Number; + private var bl:Number; + + alternativa3d function calculateBindingMatrix(parent:Object3D):void { + composeAndAppend(parent); + calculateInverseMatrix(this); + ba = ima; + bb = imb; + bc = imc; + bd = imd; + be = ime; + bf = imf; + bg = img; + bh = imh; + bi = imi; + bj = imj; + bk = imk; + bl = iml; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.calculateBindingMatrix(this); + } + } + + /** + * @private + * Задает матрицу перевода из кости в скин. + */ + alternativa3d function setBindingMatrix(a:Number, b:Number, c:Number, d:Number, e:Number, f:Number, g:Number, h:Number, i:Number, j:Number, k:Number, l:Number):void { + ba = a; + bb = b; + bc = c; + bd = d; + be = e; + bf = f; + bg = g; + bh = h; + bi = i; + bj = j; + bk = k; + bl = l; + } + + alternativa3d function addWeights():void { + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) vertexBinding.vertex.offset += vertexBinding.weight; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.addWeights(); + } + } + + alternativa3d function normalizeWeights():void { + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) vertexBinding.weight /= vertexBinding.vertex.offset; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.normalizeWeights(); + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + composeAndAppend(object); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.draw(camera, this, parentCanvas); + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.BONES) drawBone(camera, canvas, 0xFFFFFF); + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + composeAndAppend(object); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.getGeometry(camera, this); + } + return null; + } + + private function drawBone(camera:Camera3D, canvas:Canvas, color:int):void { + for (var i:int = 0; i < _numJoints; i++) { + var x1:Number = 0; + var y1:Number = 0; + var z1:Number = 0; + var bone:Joint = joints[i]; + var x2:Number = bone.x; + var y2:Number = bone.y; + var z2:Number = bone.z; + var cx1:Number = ma*x1 + mb*y1 + mc*z1 + md; + var cy1:Number = me*x1 + mf*y1 + mg*z1 + mh; + var cz1:Number = mi*x1 + mj*y1 + mk*z1 + ml; + var cx2:Number = ma*x2 + mb*y2 + mc*z2 + md; + var cy2:Number = me*x2 + mf*y2 + mg*z2 + mh; + var cz2:Number = mi*x2 + mj*y2 + mk*z2 + ml; + // Проецирование + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var t1:Number = 1/cz1; + var t2:Number = 1/cz2; + var px1:Number = cx1*viewSizeX*t1; + var py1:Number = cy1*viewSizeY*t1; + var px2:Number = cx2*viewSizeX*t2; + var py2:Number = cy2*viewSizeY*t2; + canvas.gfx.lineStyle(0, color); + canvas.gfx.moveTo(px1, py1); + canvas.gfx.lineTo(px2, py2); + } + } + + alternativa3d function calculateVertices():void { + // Матрица изменений координат в соответствии с изменением положения кости относительно слепка + ima = ma*ba + mb*be + mc*bi; + imb = ma*bb + mb*bf + mc*bj; + imc = ma*bc + mb*bg + mc*bk; + imd = ma*bd + mb*bh + mc*bl + md; + ime = me*ba + mf*be + mg*bi; + imf = me*bb + mf*bf + mg*bj; + img = me*bc + mf*bg + mg*bk; + imh = me*bd + mf*bh + mg*bl + mh; + imi = mi*ba + mj*be + mk*bi; + imj = mi*bb + mj*bf + mk*bj; + imk = mi*bc + mj*bg + mk*bk; + iml = mi*bd + mj*bh + mk*bl + ml; + // Расчёт координат + for (var vertexBinding:VertexBinding = vertexBindingList; vertexBinding != null; vertexBinding = vertexBinding.next) { + var vertex:Vertex = vertexBinding.vertex; + vertex.cameraX += (ima*vertex.x + imb*vertex.y + imc*vertex.z + imd)*vertexBinding.weight; + vertex.cameraY += (ime*vertex.x + imf*vertex.y + img*vertex.z + imh)*vertexBinding.weight; + vertex.cameraZ += (imi*vertex.x + imj*vertex.y + imk*vertex.z + iml)*vertexBinding.weight; + } + } + + public function bindVertex(vertex:Vertex, weight:Number = 1):void { + if (weight > 0) { + var vertexBinding:VertexBinding = new VertexBinding(); + vertexBinding.next = vertexBindingList; + vertexBindingList = vertexBinding; + vertexBinding.vertex = vertex; + vertexBinding.weight = weight; + } + } + + public function addJoint(joint:Joint):void { + joints[_numJoints] = joint; + _numJoints++; + } + + public function removeJoint(joint:Joint):void { + var i:int = joints.indexOf(joint); + if (i < 0) throw new ArgumentError("Joint not found"); + _numJoints--; + var j:int = i + 1; + while (i < _numJoints) { + joints[i] = joints[j]; + i++; + j++; + } + joints.length = _numJoints; + } + + public function get numJoints():uint { + return _numJoints; + } + + public function getJointAt(index:uint):Joint { + return joints[index]; + } + + alternativa3d override function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + composeAndAppend(transformation); + calculateVertices(); + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.updateBounds(bounds, this); + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/LOD.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/LOD.as new file mode 100644 index 0000000..0cc0693 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/LOD.as @@ -0,0 +1,71 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Объект, имеющий набор объектов с разной детализацией. + * При отрисовке, он выбирает в зависимости от расстояния от камеры + * объект с нужной детализацией и отрисовывает его вместо себя. + * Это позволяет получить лучший визуальный результат и большую производительность. + */ + public class LOD extends Object3D { + + /** + * Объекты с разной детализацией + */ + public var lodObjects:Vector.; + /** + * Расстояния до камеры соответствующие объектам с разной детализацией + */ + public var lodDistances:Vector.; + + /** + * @private + */ + private function getLODObject(object:Object3D):Object3D { + //var cameraDistance:Number = object.cameraMatrix.position.length; + var cameraDistance:Number = object.ml; + // Поиск ближайшего лода + var min:Number = 1e+22; + var length:uint = lodObjects.length; + var lod:Object3D; + for (var i:int = 0; i < length; i++) { + var d:Number = Math.abs(cameraDistance - lodDistances[i]); + if (d < min) { + min = d; + lod = lodObjects[i]; + } + } + return lod; + } + + /** + * @private + */ + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + getLODObject(object).draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return getLODObject(object).getGeometry(camera, object); + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + var length:uint = lodObjects.length; + for (var i:int = 0; i < length; i++) { + (lodObjects[i] as Object3D).updateBounds(bounds, transformation); + } + } + + override alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + object.culling = getLODObject(object).cullingInCamera(camera, object, culling); + return object.culling; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Mesh.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Mesh.as new file mode 100644 index 0000000..8f2c95d --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Mesh.as @@ -0,0 +1,3294 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.Material; + + import flash.utils.Dictionary; + import flash.geom.Matrix3D; + import __AS3__.vec.Vector; + + use namespace alternativa3d; + + /** + * Полигональный объект + */ + public class Mesh extends Object3D { + + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 1 - по граням + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 2; + /** + * Режим сортировки полигонов + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - построение динамического BSP при отрисовке + * 3 - проход по предрасчитанному BSP. Для расчёта BSP нужен calculateBSP() + */ + public var sorting:int = 1; + /** + * Геометрическая погрешность при расчёте BSP-дерева + */ + public var threshold:Number = 0.01; + + alternativa3d var faceList:Face; + alternativa3d var vertexList:Vertex; + + alternativa3d var faceTree:Face; + alternativa3d var bspVertexList:Vertex; + + alternativa3d var transformID:int = 0; + + public function addVertex(x:Number, y:Number, z:Number, u:Number, v:Number):Vertex { + var newVertex:Vertex = new Vertex(); + newVertex.x = x; + newVertex.y = y; + newVertex.z = z; + newVertex.u = u; + newVertex.v = v; + if (vertexList != null) { + var vertex:Vertex = vertexList; + while (vertex.next != null) vertex = vertex.next; + vertex.next = newVertex; + } else { + vertexList = newVertex; + } + return newVertex; + } + + public function removeVertex(vertex:Vertex):Vertex { + if (vertex == null) throw new TypeError("Parameter vertex must be non-null."); + var prev:Vertex; + var curr:Vertex = vertexList; + while (curr != null) { + if (curr == vertex) break; + prev = curr; + curr = curr.next; + } + if (curr == null) throw new ArgumentError("Vertex not found."); + var vertexFaces:Vector. = getVertexFaces(vertex); + var len:int = vertexFaces.length; + for (var i:int = 0; i < len; i++) { + removeFace(vertexFaces[i]); + } + if (prev != null) { + prev.next = vertex.next; + } else { + vertexList = vertex.next; + } + vertex.next = null; + return vertex; + } + + public function addFace(vertices:Vector., material:Material = null):Face { + if (vertices == null) throw new TypeError("Parameter vertices must be non-null."); + var verticesLength:int = vertices.length; + if (verticesLength < 3) throw new ArgumentError(verticesLength + " vertices not enough."); + var newFace:Face = new Face(); + newFace.material = material; + var last:Wrapper = null; + for (var i:int = 0; i < verticesLength; i++) { + var newWrapper:Wrapper = new Wrapper(); + var vertex:Vertex = vertices[i]; + if (vertex == null) throw new ArgumentError("Null vertex in vector."); + var v:Vertex = vertexList; + while (v != null) { + if (v == vertex) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + newWrapper.vertex = vertex; + if (last != null) { + last.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + last = newWrapper; + } + if (faceList != null) { + var face:Face = faceList; + while (face.next != null) face = face.next; + face.next = newFace; + } else { + faceList = newFace; + } + newFace.calculateNormal(true); + return newFace; + } + + public function addTriFace(v1:Vertex, v2:Vertex, v3:Vertex, material:Material = null):Face { + if (v1 == null) throw new TypeError("Parameter v1 must be non-null."); + if (v2 == null) throw new TypeError("Parameter v2 must be non-null."); + if (v3 == null) throw new TypeError("Parameter v3 must be non-null."); + var v:Vertex = vertexList; + while (v != null) { + if (v == v1) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + v = vertexList; + while (v != null) { + if (v == v2) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + v = vertexList; + while (v != null) { + if (v == v3) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + var newFace:Face = new Face(); + newFace.material = material; + newFace.wrapper = new Wrapper(); + newFace.wrapper.vertex = v1; + newFace.wrapper.next = new Wrapper(); + newFace.wrapper.next.vertex = v2; + newFace.wrapper.next.next = new Wrapper(); + newFace.wrapper.next.next.vertex = v3; + if (faceList != null) { + var face:Face = faceList; + while (face.next != null) face = face.next; + face.next = newFace; + } else { + faceList = newFace; + } + newFace.calculateNormal(true); + return newFace; + } + + public function addQuadFace(v1:Vertex, v2:Vertex, v3:Vertex, v4:Vertex, material:Material = null):Face { + if (v1 == null) throw new TypeError("Parameter v1 must be non-null."); + if (v2 == null) throw new TypeError("Parameter v2 must be non-null."); + if (v3 == null) throw new TypeError("Parameter v3 must be non-null."); + if (v4 == null) throw new TypeError("Parameter v4 must be non-null."); + var v:Vertex = vertexList; + while (v != null) { + if (v == v1) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + v = vertexList; + while (v != null) { + if (v == v2) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + v = vertexList; + while (v != null) { + if (v == v3) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + v = vertexList; + while (v != null) { + if (v == v4) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertices must be in mesh."); + var newFace:Face = new Face(); + newFace.material = material; + newFace.wrapper = new Wrapper(); + newFace.wrapper.vertex = v1; + newFace.wrapper.next = new Wrapper(); + newFace.wrapper.next.vertex = v2; + newFace.wrapper.next.next = new Wrapper(); + newFace.wrapper.next.next.vertex = v3; + newFace.wrapper.next.next.next = new Wrapper(); + newFace.wrapper.next.next.next.vertex = v4; + if (faceList != null) { + var face:Face = faceList; + while (face.next != null) face = face.next; + face.next = newFace; + } else { + faceList = newFace; + } + newFace.calculateNormal(true); + return newFace; + } + + public function removeFace(face:Face):Face { + if (face == null) throw new TypeError("Parameter face must be non-null."); + var prev:Face; + var curr:Face = faceList; + while (curr != null) { + if (curr == face) break; + prev = curr; + curr = curr.next; + } + if (curr == null) throw new ArgumentError("Face not found."); + if (prev != null) { + prev.next = face.next; + } else { + faceList = face.next; + } + face.next = null; + return face; + } + + public function addGeometryByIndices(vertices:Vector., uvs:Vector., indices:Vector., poly:Boolean = false, material:Material = null):void { + if (vertices == null) throw new TypeError("Parameter vertices must be non-null."); + var i:int, j:int, k:int; + var length:int = vertices.length/3; + if (uvs != null && length != uvs.length/2) throw new ArgumentError("Vertices count and uvs count doesn't match."); + var verts:Vector. = new Vector.(length); + var lastVertex:Vertex; + if (vertexList != null) { + lastVertex = vertexList; + while (lastVertex.next != null) lastVertex = lastVertex.next; + } + for (i = 0, j = 0, k = 0; i < length; i++) { + var newVertex:Vertex = new Vertex(); + newVertex.x = vertices[j]; + j++; + newVertex.y = vertices[j]; + j++; + newVertex.z = vertices[j]; + j++; + if (uvs != null) { + newVertex.u = uvs[k]; + k++; + newVertex.v = uvs[k]; + k++; + } + verts[i] = newVertex; + if (lastVertex != null) { + lastVertex.next = newVertex; + } else { + vertexList = newVertex; + } + lastVertex = newVertex; + } + if (indices != null) { + var indicesLength:int = indices.length; + if (!poly && indicesLength % 3) throw new ArgumentError("Incorrect indices."); + for (i = 0, k = 0; i < indicesLength; i++) { + if (i == k) { + var num:int = poly ? indices[i] : 3; + if (num < 3) throw new ArgumentError(num + " vertices not enough."); + k = poly ? (num + ++i) : (i + num); + if (k > indicesLength) throw new ArgumentError("Incorrect indices."); + } + var index:int = indices[i]; + if (index < 0 || index >= length) throw new RangeError("Index is out of bounds."); + } + var lastFace:Face; + if (faceList != null) { + lastFace = faceList; + while (lastFace.next != null) lastFace = lastFace.next; + } + var lastWrapper:Wrapper; + for (i = 0, k = 0; i < indicesLength;) { + if (i == k) { + k = poly ? (indices[i] + ++i) : (i + 3); + if (lastFace != null) { + lastFace.next = new Face(); + lastFace = lastFace.next; + } else { + faceList = new Face(); + lastFace = faceList; + } + lastWrapper = null; + } + if (lastWrapper != null) { + lastWrapper.next = new Wrapper(); + lastWrapper = lastWrapper.next; + } else { + lastFace.wrapper = new Wrapper(); + lastWrapper = lastFace.wrapper; + } + lastWrapper.vertex = verts[indices[i]]; + if (++i == k) { + lastFace.material = material; + lastFace.calculateNormal(true); + } + } + } + } + + public function getVertexFaces(vertex:Vertex):Vector. { + if (vertex == null) throw new TypeError("Parameter vertex must be non-null."); + var v:Vertex = vertexList; + while (v != null) { + if (v == vertex) break; + v = v.next; + } + if (v == null) throw new ArgumentError("Vertex not found."); + var res:Vector. = new Vector.(); + var len:int = 0; + for (var face:Face = faceList; face != null; face = face.next) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + if (wrapper.vertex == vertex) { + res[len] = face; + len++; + break; + } + } + } + return res; + } + + public function get vertices():Vector. { + var res:Vector. = new Vector.(); + var len:int = 0; + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + res[len] = vertex; + len++; + } + return res; + } + + public function get faces():Vector. { + var res:Vector. = new Vector.(); + var len:int = 0; + for (var face:Face = faceList; face != null; face = face.next) { + res[len] = face; + len++; + } + return res; + } + + public function setMaterialToAllFaces(material:Material = null):void { + for (var face:Face = faceList; face != null; face = face.next) { + face.material = material; + } + if (faceTree != null) { + setMaterialToTree(faceTree, material); + } + } + + private function setMaterialToTree(tree:Face, material:Material):void { + for (var face:Face = tree; face != null; face = face.next) { + face.material = material; + } + if (tree.negative != null) { + setMaterialToTree(tree.negative, material); + } + if (tree.positive != null) { + setMaterialToTree(tree.positive, material); + } + } + + /** + * @param textureWidth + * @param textureHeight + * @param type 0 - по первому ребру, 1 - среднее значение, 2 - минимальное значение, 3 - максимальное значение + * @param matrix трансформация + * @return resolution + */ + public function calculateResolution(textureWidth:int, textureHeight:int, type:int = 1, matrix:Matrix3D = null):Number { + if (faceList != null) { + var object:Object3D; + if (matrix != null) { + object = new Object3D(); + object.setMatrix(matrix); + object.composeMatrix(); + } + var min:Number = 1e+22; + var max:Number = 0; + var sum:Number = 0; + var num:int = 0; + for (var face:Face = faceList; face != null; face = face.next) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var a:Vertex = wrapper.vertex; + var b:Vertex = wrapper.next != null ? wrapper.next.vertex : face.wrapper.vertex; + var dx:Number = (matrix != null) ? (object.ma*(b.x - a.x) + object.mb*(b.y - a.y) + object.mc*(b.z - a.z)) : (b.x - a.x); + var dy:Number = (matrix != null) ? (object.me*(b.x - a.x) + object.mf*(b.y - a.y) + object.mg*(b.z - a.z)) : (b.y - a.y); + var dz:Number = (matrix != null) ? (object.mi*(b.x - a.x) + object.mj*(b.y - a.y) + object.mk*(b.z - a.z)) : (b.z - a.z); + var du:Number = (b.u - a.u)*textureWidth; + var dv:Number = (b.v - a.v)*textureHeight; + var res:Number = Math.sqrt(dx*dx + dy*dy + dz*dz)/Math.sqrt(du*du + dv*dv); + if (res < min) min = res; + if (res > max) max = res; + sum += res; + num++; + if (type == 0) return sum; + } + } + return (type == 1) ? sum/num : ((type == 2) ? min : max); + } + return 1; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + var list:Face; + var vertex:Vertex; + // Сброс итератора трансформаций + if (transformID > 500000000) { + transformID = 0; + for (vertex = vertexList; vertex != null; vertex = vertex.next) vertex.transformID = 0; + for (vertex = bspVertexList; vertex != null; vertex = vertex.next) vertex.transformID = 0; + } + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Отсечение по нормалям + if (sorting == 3) { + if (faceTree == null) return; + list = collectNode(faceTree); + } else { + if (faceList == null) return; + list = backfaceCull(faceList); + } + if (list == null) return; + // Трансформация в камеру + transformID++; + transform(list, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + list = cull(list, culling, camera); + } else { + list = clip(list, culling, camera); + } + if (list == null) return; + } + // Сортировка + if (list.processNext != null) { + if (sorting == 1) { + list = sortByAverageZ(list); + } else if (sorting == 2) { + list = sortByDynamicBSP(list, camera, threshold); + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + for (var face:Face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, object.ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + + protected function transform(list:Face, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):void { + // Трансформация вершин граней + for (var face:Face = list; face != null; face = face.processNext) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + var x:Number = vertex.x; + var y:Number = vertex.y; + var z:Number = vertex.z; + vertex.cameraX = ma*x + mb*y + mc*z + md; + vertex.cameraY = me*x + mf*y + mg*z + mh; + vertex.cameraZ = mi*x + mj*y + mk*z + ml; + vertex.transformID = transformID; + vertex.drawID = 0; + } + } + } + } + + private function collectNode(tree:Face, readyList:Face = null):Face { + if (tree.normalX*imd + tree.normalY*imh + tree.normalZ*iml > tree.offset) { + if (tree.positive != null) readyList = collectNode(tree.positive, readyList); + for (var face:Face = tree; face != null; face = face.next) { + face.processNext = readyList; + readyList = face; + } + if (tree.negative != null) readyList = collectNode(tree.negative, readyList); + } else { + if (tree.negative != null) readyList = collectNode(tree.negative, readyList); + if (tree.positive != null) readyList = collectNode(tree.positive, readyList); + } + return readyList; + } + + private function backfaceCull(list:Face):Face { + var first:Face; + var last:Face; + for (var face:Face = list; face != null; face = face.next) { + if (face.normalX*imd + face.normalY*imh + face.normalZ*iml > face.offset) { + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function cull(list:Face, culling:int, camera:Camera3D):Face { + var first:Face; + var last:Face; + var next:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + if (c1) { + if (az <= near || bz <= near || cz <= near) { + face.processNext = null; + continue; + } + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) { + face.processNext = null; + continue; + } + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) { + face.processNext = null; + continue; + } + } + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function clip(list:Face, culling:int, camera:Camera3D):Face { + var first:Face; + var last:Face; + var next:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var faceCulling:int; + var t:Number; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + faceCulling = 0; + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) { + face.processNext = null; + continue; + } + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) { + face.processNext = null; + continue; + } + } + face.processNext = null; + var newFace:Face = face.create(); + newFace.material = face.material; + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.wrapper = wFirst; + face = newFace; + } + if (first != null) { + last.processNext = face; + } else { + first = face; + } + last = face; + } + if (last != null) { + last.processNext = null; + } + return first; + } + + protected function sortByAverageZ(list:Face):Face { + var num:int; + var sum:Number; + var wrapper:Wrapper; + var left:Face = list; + var right:Face = list.processNext; + while (right != null && right.processNext != null) { + list = list.processNext; + right = right.processNext.processNext; + } + right = list.processNext; + list.processNext = null; + if (left.processNext != null) { + left = sortByAverageZ(left); + } else { + num = 0; + sum = 0; + for (wrapper = left.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + left.distance = sum/num; + } + if (right.processNext != null) { + right = sortByAverageZ(right); + } else { + num = 0; + sum = 0; + for (wrapper = right.wrapper; wrapper != null; wrapper = wrapper.next) { + num++; + sum += wrapper.vertex.cameraZ; + } + right.distance = sum/num; + } + var flag:Boolean = left.distance > right.distance; + if (flag) { + list = left; + left = left.processNext; + } else { + list = right; + right = right.processNext; + } + var last:Face = list; + while (true) { + if (left == null) { + last.processNext = right; + return list; + } else if (right == null) { + last.processNext = left; + return list; + } + if (flag) { + if (left.distance > right.distance) { + last = left; + left = left.processNext; + } else { + last.processNext = right; + last = right; + right = right.processNext; + flag = false; + } + } else { + if (right.distance > left.distance) { + last = right; + right = right.processNext; + } else { + last.processNext = left; + last = left; + left = left.processNext; + flag = true; + } + } + } + return null; + } + + protected function sortByDynamicBSP(list:Face, camera:Camera3D, threshold:Number, result:Face = null):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var splitter:Face = list; + list = splitter.processNext; + // Поиск удовлетворяющей нормали + w = splitter.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + var ax:Number = a.cameraX; + var ay:Number = a.cameraY; + var az:Number = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var normalX:Number = 0; + var normalY:Number = 0; + var normalZ:Number = 1; + var offset:Number = az; + var length:Number = 0; + for (w = w.next; w != null; w = w.next) { + v = w.vertex; + var acx:Number = v.cameraX - ax; + var acy:Number = v.cameraY - ay; + var acz:Number = v.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > threshold) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + break; + } else if (nl > length) { + nl = 1/Math.sqrt(nl); + normalX = nx*nl; + normalY = ny*nl; + normalZ = nz*nl; + offset = ax*normalX + ay*normalY + az*normalZ; + length = nl; + } + } + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var positiveFirst:Face; + var positiveLast:Face; + var next:Face; + for (var face:Face = list; face != null; face = next) { + next = face.processNext; + w = face.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + var ao:Number = a.cameraX*normalX + a.cameraY*normalY + a.cameraZ*normalZ; + var bo:Number = b.cameraX*normalX + b.cameraY*normalY + b.cameraZ*normalZ; + var co:Number = c.cameraX*normalX + c.cameraY*normalY + c.cameraZ*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + var vo:Number = v.cameraX*normalX + v.cameraY*normalY + v.cameraZ*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + splitterLast.processNext = face; + splitterLast = face; + } else { + if (positiveFirst != null) { + positiveLast.processNext = face; + } else { + positiveFirst = face; + } + positiveLast = face; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.processNext = face; + } else { + negativeFirst = face; + } + negativeLast = face; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = face.create(); + negative.material = face.material; + camera.lastFace.next = negative; + camera.lastFace = negative; + var positive:Face = face.create(); + positive.material = face.material; + camera.lastFace.next = positive; + camera.lastFace = positive; + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + //for (w = face.wrapper.next.next; w.next != null; w = w.next); + w = face.wrapper.next.next; + while (w.next != null) w = w.next; + a = w.vertex; + ao = a.offset; + for (w = face.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = a.cameraZ + (b.cameraZ - a.cameraZ)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = w.create(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = w.create(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = w.create(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + if (negativeFirst != null) { + negativeLast.processNext = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + if (positiveFirst != null) { + positiveLast.processNext = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + face.processNext = null; + } + } + if (positiveFirst != null) { + positiveLast.processNext = null; + if (positiveFirst.processNext != null) { + result = sortByDynamicBSP(positiveFirst, camera, threshold, result); + } else { + positiveFirst.processNext = result; + result = positiveFirst; + } + } + splitterLast.processNext = result; + result = splitter; + if (negativeFirst != null) { + negativeLast.processNext = null; + if (negativeFirst.processNext != null) { + result = sortByDynamicBSP(negativeFirst, camera, threshold, result); + } else { + negativeFirst.processNext = result; + result = negativeFirst; + } + } + return result; + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var vertex:Vertex; + // Сброс итератора трансформаций + if (transformID > 500000000) { + transformID = 0; + for (vertex = vertexList; vertex != null; vertex = vertex.next) vertex.transformID = 0; + for (vertex = bspVertexList; vertex != null; vertex = vertex.next) vertex.transformID = 0; + } + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return null; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Инкркмент итератора трансформаций + transformID++; + // Получение клона видимой геометрии + var struct:Face; + if (sorting == 3) { + if (faceTree == null) return null; + struct = calculateFaces(faceTree, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } else { + if (faceList == null) return null; + struct = calculateFaces(faceList, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } + // Зачистка после ремапа + for (vertex = (sorting == 3) ? bspVertexList : vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + // Создание геометрии + if (struct != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = struct; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.ima = ima; + geometry.imb = imb; + geometry.imc = imc; + geometry.imd = imd; + geometry.ime = ime; + geometry.imf = imf; + geometry.img = img; + geometry.imh = imh; + geometry.imi = imi; + geometry.imj = imj; + geometry.imk = imk; + geometry.iml = iml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + protected function calculateFaces(struct:Face, culling:int, camera:Camera3D, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):Face { + var first:Face; + var last:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var t:Number; + // Если не BSP или нода видна + if (sorting != 3 || imd*struct.normalX + imh*struct.normalY + iml*struct.normalZ > struct.offset) { + // Перебор оригинальных граней + for (var face:Face = struct; face != null; face = face.next) { + // Отсечение по нормали + if (sorting != 3 && imd*face.normalX + imh*face.normalY + iml*face.normalZ <= face.offset) continue; + // Трансформация + for (w = face.wrapper; w != null; w = w.next) { + v = w.vertex; + if (v.transformID != transformID) { + ax = v.x; + ay = v.y; + az = v.z; + v.cameraX = ma*ax + mb*ay + mc*az + md; + v.cameraY = me*ax + mf*ay + mg*az + mh; + v.cameraZ = mi*ax + mj*ay + mk*az + ml; + v.transformID = transformID; + } + } + var faceCulling:int = 0; + // Отсечение по пирамиде видимости + if (culling > 0) { + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + // Куллинг + if (clipping == 1) { + if (c1) { + if (az <= near || bz <= near || cz <= near) continue; + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) continue; + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) continue; + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + // Клиппинг + } else { + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) continue; + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) continue; + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) continue; + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) continue; + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) continue; + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) continue; + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + } + } + } + var newFace:Face = face.create(); + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.material = face.material; + var newVertex:Vertex; + if (faceCulling > 0) { + for (w = wFirst; w != null; w = w.next) { + v = w.vertex; + if (v.value != null) { + w.vertex = v.value; + } else if (v.transformID > 0) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + w.vertex = newVertex; + } + } + } else { + wFirst = null; + for (w = face.wrapper; w != null; w = w.next) { + wNew = w.create(); + v = w.vertex; + if (v.value == null) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + } + wNew.vertex = v.value; + if (wFirst != null) { + wLast.next = wNew; + } else { + wFirst = wNew; + } + wLast = wNew; + } + } + newFace.wrapper = wFirst; + if (first != null) { + last.processNext = newFace; + } else { + first = newFace; + } + last = newFace; + } + } + // Если BSP + if (sorting == 3) { + var negative:Face = (struct.negative != null) ? calculateFaces(struct.negative, culling, camera, ma, mb, mc, md, me, mf, mg, mh, mi, mj, mk, ml) : null; + var positive:Face = (struct.positive != null) ? calculateFaces(struct.positive, culling, camera, ma, mb, mc, md, me, mf, mg, mh, mi, mj, mk, ml) : null; + // Если нода видна или есть видимые дочерние ноды + if (first != null || negative != null && positive != null) { + if (first == null) { + // Создание пустой ноды + first = struct.create(); + camera.lastFace.next = first; + camera.lastFace = first; + } + // Расчёт нормали + w = struct.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + if (a.transformID != transformID) { + ax = a.x; + ay = a.y; + az = a.z; + a.cameraX = ma*ax + mb*ay + mc*az + md; + a.cameraY = me*ax + mf*ay + mg*az + mh; + a.cameraZ = mi*ax + mj*ay + mk*az + ml; + a.transformID = transformID; + } + if (b.transformID != transformID) { + ax = b.x; + ay = b.y; + az = b.z; + b.cameraX = ma*ax + mb*ay + mc*az + md; + b.cameraY = me*ax + mf*ay + mg*az + mh; + b.cameraZ = mi*ax + mj*ay + mk*az + ml; + b.transformID = transformID; + } + if (c.transformID != transformID) { + ax = c.x; + ay = c.y; + az = c.z; + c.cameraX = ma*ax + mb*ay + mc*az + md; + c.cameraY = me*ax + mf*ay + mg*az + mh; + c.cameraZ = mi*ax + mj*ay + mk*az + ml; + c.transformID = transformID; + } + ax = a.cameraX; + ay = a.cameraY; + az = a.cameraZ; + var abx:Number = b.cameraX - ax; + var aby:Number = b.cameraY - ay; + var abz:Number = b.cameraZ - az; + var acx:Number = c.cameraX - ax; + var acy:Number = c.cameraY - ay; + var acz:Number = c.cameraZ - az; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + var nl:Number = nx*nx + ny*ny + nz*nz; + if (nl > 0) { + nl = 1/Math.sqrt(length); + nx *= nl; + ny *= nl; + nz *= nl; + } + first.normalX = nx; + first.normalY = ny; + first.normalZ = nz; + first.offset = ax*nx + ay*ny + az*nz; + first.negative = negative; + first.positive = positive; + } else { + first = (negative != null) ? negative : positive; + } + } + return first; + } + + /** + * Расчёт нормалей + * @param normalize Флаг нормализации + */ + public function calculateNormals(normalize:Boolean = false):void { + for (var face:Face = faceList; face != null; face = face.next) { + var w:Wrapper = face.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + var abx:Number = b.x - a.x; + var aby:Number = b.y - a.y; + var abz:Number = b.z - a.z; + var acx:Number = c.x - a.x; + var acy:Number = c.y - a.y; + var acz:Number = c.z - a.z; + var nx:Number = acz*aby - acy*abz; + var ny:Number = acx*abz - acz*abx; + var nz:Number = acy*abx - acx*aby; + if (normalize) { + var length:Number = nx*nx + ny*ny + nz*nz; + if (length > 0.001) { + length = 1/Math.sqrt(length); + nx *= length; + ny *= length; + nz *= length; + } + } + face.normalX = nx; + face.normalY = ny; + face.normalZ = nz; + face.offset = a.x*nx + a.y*ny + a.z*nz; + } + } + + public function optimizeForDynamicBSP(iterations:int = 1):void { + var list:Face = faceList; + var last:Face; + for (var i:int = 0; i < iterations; i++) { + var prev:Face = null; + for (var face:Face = list; face != null; face = face.next) { + var normalX:Number = face.normalX; + var normalY:Number = face.normalY; + var normalZ:Number = face.normalZ; + var offset:Number = face.offset; + var offsetMin:Number = offset - threshold; + var offsetMax:Number = offset + threshold; + var splits:int = 0; + for (var f:Face = list; f != null; f = f.next) { + if (f != face) { + var w:Wrapper = f.wrapper; + var a:Vertex = w.vertex; + w = w.next; + var b:Vertex = w.vertex; + w = w.next; + var c:Vertex = w.vertex; + w = w.next; + var ao:Number = a.x*normalX + a.y*normalY + a.z*normalZ; + var bo:Number = b.x*normalX + b.y*normalY + b.z*normalZ; + var co:Number = c.x*normalX + c.y*normalY + c.z*normalZ; + var behind:Boolean = ao < offsetMin || bo < offsetMin || co < offsetMin; + var infront:Boolean = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + var v:Vertex = w.vertex; + var vo:Number = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + if (infront) break; + } else if (vo > offsetMax) { + infront = true; + if (behind) break; + } + } + if (infront && behind) { + splits++; + if (splits > i) break; + } + } + } + if (f == null) { + if (prev != null) { + prev.next = face.next; + } else { + list = face.next; + } + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + } else { + prev = face; + } + } + if (list == null) break; + } + if (last != null) { + last.next = list; + } + } + + /** + * Расчёт локального BSP-дерева + * @param splitAnalysis Флаг сплит-анализа. + * Если он включен, дерево построится с наименьшим количеством распилов, но построение будет медленнее + */ + public function calculateBSP(splitAnalysis:Boolean = false):void { + var first:Face; + var last:Face; + var face:Face; + var wrapper:Wrapper; + for (face = faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.material = face.material; + var lastWrapper:Wrapper = null; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + var vertex:Vertex = wrapper.vertex; + if (vertex.value == null) { + var newVertex:Vertex = new Vertex(); + newVertex.next = bspVertexList; + bspVertexList = newVertex; + newVertex.x = vertex.x; + newVertex.y = vertex.y; + newVertex.z = vertex.z; + newVertex.u = vertex.u; + newVertex.v = vertex.v; + vertex.value = newVertex; + } + newWrapper.vertex = vertex.value; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + newFace.calculateBestSequenceAndNormal(); + if (first != null) { + last.next = newFace; + } else { + first = newFace; + } + last = newFace; + } + // Зануление мапы + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + wrapper.vertex.value = null; + } + } + // Построение дерева + faceTree = (first != null) ? ((first.next != null) ? createNode(first, splitAnalysis) : first) : null; + } + + private function createNode(list:Face, splitAnalysis:Boolean):Face { + var w:Wrapper; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var v:Vertex; + var behind:Boolean; + var infront:Boolean; + var ao:Number; + var bo:Number; + var co:Number; + var vo:Number; + var normalX:Number; + var normalY:Number; + var normalZ:Number; + var offset:Number; + var offsetMin:Number; + var offsetMax:Number; + var splitter:Face = list; + if (splitAnalysis) { + var bestSplits:int = 2147483647; + for (var face:Face = list; face != null; face = face.next) { + normalX = face.normalX; + normalY = face.normalY; + normalZ = face.normalZ; + offset = face.offset; + offsetMin = offset - threshold; + offsetMax = offset + threshold; + var splits:int = 0; + for (var f:Face = list; f != null; f = f.next) { + if (f != face) { + w = f.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + ao = a.x*normalX + a.y*normalY + a.z*normalZ; + bo = b.x*normalX + b.y*normalY + b.z*normalZ; + co = c.x*normalX + c.y*normalY + c.z*normalZ; + behind = ao < offsetMin || bo < offsetMin || co < offsetMin; + infront = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + vo = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + if (infront) break; + } else if (vo > offsetMax) { + infront = true; + if (behind) break; + } + } + if (infront && behind) { + splits++; + if (splits >= bestSplits) break; + } + } + } + if (splits < bestSplits) { + splitter = face; + bestSplits = splits; + if (bestSplits == 0) break; + } + } + } + var negativeFirst:Face; + var negativeLast:Face; + var splitterLast:Face = splitter; + var splitterNext:Face = splitter.next; + var positiveFirst:Face; + var positiveLast:Face; + normalX = splitter.normalX; + normalY = splitter.normalY; + normalZ = splitter.normalZ; + offset = splitter.offset; + offsetMin = offset - threshold; + offsetMax = offset + threshold; + while (list != null) { + if (list != splitter) { + var next:Face = list.next; + w = list.wrapper; + a = w.vertex; + w = w.next; + b = w.vertex; + w = w.next; + c = w.vertex; + w = w.next; + ao = a.x*normalX + a.y*normalY + a.z*normalZ; + bo = b.x*normalX + b.y*normalY + b.z*normalZ; + co = c.x*normalX + c.y*normalY + c.z*normalZ; + behind = ao < offsetMin || bo < offsetMin || co < offsetMin; + infront = ao > offsetMax || bo > offsetMax || co > offsetMax; + for (; w != null; w = w.next) { + v = w.vertex; + vo = v.x*normalX + v.y*normalY + v.z*normalZ; + if (vo < offsetMin) { + behind = true; + } else if (vo > offsetMax) { + infront = true; + } + v.offset = vo; + } + if (!behind) { + if (!infront) { + if (list.normalX*normalX + list.normalY*normalY + list.normalZ*normalZ > 0) { + splitterLast.next = list; + splitterLast = list; + } else { + if (negativeFirst != null) { + negativeLast.next = list; + } else { + negativeFirst = list; + } + negativeLast = list; + } + } else { + if (positiveFirst != null) { + positiveLast.next = list; + } else { + positiveFirst = list; + } + positiveLast = list; + } + } else if (!infront) { + if (negativeFirst != null) { + negativeLast.next = list; + } else { + negativeFirst = list; + } + negativeLast = list; + } else { + a.offset = ao; + b.offset = bo; + c.offset = co; + var negative:Face = new Face(); + var positive:Face = new Face(); + var wNegative:Wrapper = null; + var wPositive:Wrapper = null; + var wNew:Wrapper; + w = list.wrapper.next.next; + while (w.next != null) { + w = w.next; + } + a = w.vertex; + ao = a.offset; + for (w = list.wrapper; w != null; w = w.next) { + b = w.vertex; + bo = b.offset; + if (ao < offsetMin && bo > offsetMax || ao > offsetMax && bo < offsetMin) { + var t:Number = (offset - ao)/(bo - ao); + v = new Vertex(); + v.next = bspVertexList; + bspVertexList = v; + v.x = a.x + (b.x - a.x)*t; + v.y = a.y + (b.y - a.y)*t; + v.z = a.z + (b.z - a.z)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = new Wrapper(); + wNew.vertex = v; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + wNew = new Wrapper(); + wNew.vertex = v; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + if (bo <= offsetMax) { + wNew = new Wrapper(); + wNew.vertex = b; + if (wNegative != null) { + wNegative.next = wNew; + } else { + negative.wrapper = wNew; + } + wNegative = wNew; + } + if (bo >= offsetMin) { + wNew = new Wrapper(); + wNew.vertex = b; + if (wPositive != null) { + wPositive.next = wNew; + } else { + positive.wrapper = wNew; + } + wPositive = wNew; + } + a = b; + ao = bo; + } + negative.material = list.material; + negative.calculateBestSequenceAndNormal(); + if (negativeFirst != null) { + negativeLast.next = negative; + } else { + negativeFirst = negative; + } + negativeLast = negative; + positive.material = list.material; + positive.calculateBestSequenceAndNormal(); + if (positiveFirst != null) { + positiveLast.next = positive; + } else { + positiveFirst = positive; + } + positiveLast = positive; + } + list = next; + } else { + list = splitterNext; + } + } + if (negativeFirst != null) { + negativeLast.next = null; + splitter.negative = (negativeFirst.next != null) ? createNode(negativeFirst, splitAnalysis) : negativeFirst; + } else { + splitter.negative = null; + } + if (positiveFirst != null) { + positiveLast.next = null; + splitter.positive = (positiveFirst.next != null) ? createNode(positiveFirst, splitAnalysis) : positiveFirst; + } else { + splitter.positive = null; + } + splitterLast.next = null; + return splitter; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (transformation != null) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + } else { + vertex.cameraX = vertex.x; + vertex.cameraY = vertex.y; + vertex.cameraZ = vertex.z; + } + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + /** + * Копирование свойств другого меша. Осторожно, свойства будут иметь прямые ссылки на свойства копируемого меша. + * @param source Объект копирования + */ + public function copyFrom(source:Mesh):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + colorTransform = source.colorTransform; + filters = source.filters; + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + clipping = source.clipping; + sorting = source.sorting; + + faceList = source.faceList; + vertexList = source.vertexList; + faceTree = source.faceTree; + bspVertexList = source.bspVertexList; + } + + override public function clone():Object3D { + var mesh:Mesh = new Mesh(); + mesh.cloneBaseProperties(this); + mesh.clipping = clipping; + mesh.sorting = sorting; + var vertex:Vertex; + // Клонирование вершин + var lastVertex:Vertex; + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + var newVertex:Vertex = new Vertex(); + newVertex.x = vertex.x; + newVertex.y = vertex.y; + newVertex.z = vertex.z; + newVertex.u = vertex.u; + newVertex.v = vertex.v; + vertex.value = newVertex; + if (lastVertex != null) { + lastVertex.next = newVertex; + } else { + mesh.vertexList = newVertex; + } + lastVertex = newVertex; + } + // Клонирование граней + var lastFace:Face; + for (var face:Face = faceList; face != null; face = face.next) { + var newFace:Face = new Face(); + newFace.material = face.material; + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + // Клонирование обёрток + var lastWrapper:Wrapper = null; + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var newWrapper:Wrapper = new Wrapper(); + newWrapper.vertex = wrapper.vertex.value; + if (lastWrapper != null) { + lastWrapper.next = newWrapper; + } else { + newFace.wrapper = newWrapper; + } + lastWrapper = newWrapper; + } + if (lastFace != null) { + lastFace.next = newFace; + } else { + mesh.faceList = newFace; + } + lastFace = newFace; + } + // Сброс после ремапа + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + return mesh; + } + + public function generateClass(className:String = "GeneratedMesh", packageName:String = "", textureName:String = null):String { + + /*var header:String = "package" + ((packageName != "") ? (" " + packageName + " ") : " ") + "{\r\r"; + + var importSet:Object = new Object(); + importSet["__AS3__.vec.Vector"] = true; + importSet["alternativa.engine3d.core.Mesh"] = true; + + var footer:String = "\t\t}\r\t}\r}"; + + var classHeader:String = "\tpublic class "+ className + " extends Mesh {\r\r"; + + var constructor:String = "\t\tpublic function " + className + "() {\r"; + + constructor += "\t\t\tnumVertices = " + numVertices +";\r"; + constructor += "\t\t\tnumFaces = " + numFaces +";\r"; + constructor += "\t\t\tvertices = Vector.(["; + var length:uint = numVertices*3; + var n:int = 0; + + var i:int; + + for (i = 0; i < length; i++) { + constructor += vertices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + constructor += "\t\t\tindices = Vector.(["; + length = numFaces*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += indices[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + + constructor += "\t\t\tuvts = Vector.(["; + length = numVertices*3; + n = 0; + for (i = 0; i < length; i++) { + constructor += uvts[i]; + if (i != length - 1) { + constructor += ", "; + if (n++ > 48) { + constructor += "\r\t\t\t\t"; + n = 0; + } + } + } + constructor += "]);\r"; + + var embeds:String = ""; + if (textureName != null) { + importSet["flash.display.BitmapData"] = true; + var bmpName:String = textureName.charAt(0).toUpperCase() + textureName.substr(1); + embeds += "\t\t[Embed(source=\"" + textureName + "\")] private static const bmp" + bmpName + ":Class;\r"; + embeds += "\t\tprivate static const " + textureName + ":BitmapData = new bmp" + bmpName + "().bitmapData;\r\r"; + constructor += "\t\t\ttexture = " + textureName + ";\r\r"; + } + + constructor += "\t\t\tclipping = " + clipping +";\r"; + constructor += "\t\t\trepeatTexture = " + (repeatTexture ? "true" : "false") +";\r\r"; + + constructor += "\t\t\tmatrix.rawData = Vector.([" + matrix.rawData + "]);\r"; + if (_boundBox != null) { + importSet["alternativa.engine3d.bounds.BoundBox"] = true; + constructor += "\t\t\t_boundBox = new BoundBox(" + _boundBox.minX + ", " + _boundBox.minY + ", " + _boundBox.minZ + ", " + _boundBox.maxX + ", " + _boundBox.maxY + ", " + _boundBox.maxZ + ");\r"; + } + + var imports:String = ""; + + var importArray:Array = new Array(); + for (var key:* in importSet) { + importArray.push(key); + } + importArray.sort(); + + var newLine:Boolean = false; + length = importArray.length; + for (i = 0; i < length; i++) { + var pack:String = importArray[i]; + var current:String = pack.substr(0, pack.indexOf(".")); + imports += (current != prev && prev != null) ? "\r" : ""; + imports += "\timport " + pack + ";\r"; + var prev:String = current; + newLine = true; + } + imports += newLine ? "\r" : ""; + + return header + imports + classHeader + embeds + constructor + footer;*/ + return "Method is not realized"; + } + + /** + * Объединение вершин с одинаковыми координатами и uv + * @param distanceThreshold Погрешность, в пределах которой координаты считаются одинаковыми + * @param uvThreshold Погрешность, в пределах которой UV-координаты считаются одинаковыми + */ + public function weldVertices(distanceThreshold:Number = 0, uvThreshold:Number = 0):void { + // Заполнение массива вершин + transformID++; + var face:Face; + var wrapper:Wrapper; + var vertex:Vertex; + var vertices:Vector. = new Vector.(); + var verticesLength:int = 0; + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + vertices[verticesLength] = vertex; + verticesLength++; + vertex.transformID = transformID; + } + } + } + // Группировка + group(vertices, 0, verticesLength, 0, distanceThreshold, uvThreshold, new Vector.()); + // Замена вершин + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.value != null) { + wrapper.vertex = vertex.value; + } + } + } + // Создание нового списка вершин + vertexList = null; + transformID++; + for (face = faceList; face != null; face = face.next) { + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + vertex = wrapper.vertex; + if (vertex.transformID != transformID) { + vertex.next = vertexList; + vertexList = vertex; + vertex.transformID = transformID; + } + } + } + // Здесь может быть удаление дубликатов из меша + } + + private function group(vertices:Vector., begin:int, end:int, depth:int, distanceThreshold:Number, uvThreshold:Number, stack:Vector.):void { + var i:int; + var j:int; + var vertex:Vertex; + var threshold:Number; + switch (depth) { + case 0: // x + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.x; + } + threshold = distanceThreshold; + break; + case 1: // y + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.y; + } + threshold = distanceThreshold; + break; + case 2: // z + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.z; + } + threshold = distanceThreshold; + break; + case 3: // u + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.u; + } + threshold = uvThreshold; + break; + case 4: // v + for (i = begin; i < end; i++) { + vertex = vertices[i]; + vertex.offset = vertex.v; + } + threshold = uvThreshold; + break; + } + // Сортировка + stack[0] = begin; + stack[1] = end - 1; + var index:int = 2; + while (index > 0) { + index--; + var r:int = stack[index]; + j = r; + index--; + var l:int = stack[index]; + i = l; + vertex = vertices[(r + l) >> 1]; + var median:Number = vertex.offset; + while (i <= j) { + var left:Vertex = vertices[i]; + while (left.offset > median) { + i++; + left = vertices[i]; + } + var right:Vertex = vertices[j]; + while (right.offset < median) { + j--; + right = vertices[j]; + } + if (i <= j) { + vertices[i] = right; + vertices[j] = left; + i++; + j--; + } + } + if (l < j) { + stack[index] = l; + index++; + stack[index] = j; + index++; + } + if (i < r) { + stack[index] = i; + index++; + stack[index] = r; + index++; + } + } + // Разбиение на группы дальше + i = begin; + vertex = vertices[i]; + for (j = i + 1; j < end; j++) { + var compared:Vertex = vertices[j]; + if (vertex.offset - compared.offset > threshold) { + if (depth < 4 && j - i > 1) { + group(vertices, i, j, depth + 1, distanceThreshold, uvThreshold, stack); + } + i = j; + vertex = vertices[i]; + } else if (depth == 4) { + compared.value = vertex; + } + } + if (depth < 4 && j - i > 1) { + group(vertices, i, j, depth + 1, distanceThreshold, uvThreshold, stack); + } + } + + /** + * Объединение соседних граней, находящихся в одной плоскости + * @param angleThreshold Допустимый угол в радианах между нормалями, чтобы считать, что объединяемые грани в одной плоскости + * @param uvThreshold Допустимая разница uv-координат, чтобы считать, что объединяемые грани состыковываются по UV + * @param convexThreshold Величина, уменьшающая допустимый угол между смежными рёбрами объединяемых граней + */ + public function weldFaces(angleThreshold:Number = 0, uvThreshold:Number = 0, convexThreshold:Number = 0, pairWeld:Boolean = false):void { + var i:int; + var j:int; + var key:*; + var sibling:Face; + var face:Face; + var next:Face; + var wp:Wrapper; + var sp:Wrapper; + var w:Wrapper; + var s:Wrapper; + var wn:Wrapper; + var sn:Wrapper; + var wm:Wrapper; + var sm:Wrapper; + var vertex:Vertex; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var abx:Number; + var aby:Number; + var abz:Number; + var abu:Number; + var abv:Number; + var acx:Number; + var acy:Number; + var acz:Number; + var acu:Number; + var acv:Number; + var nx:Number; + var ny:Number; + var nz:Number; + var nl:Number; + var dictionary:Dictionary; + // Последняя грань в результирующем списке + var last:Face; + // Погрешность + var digitThreshold:Number = 0.001; + angleThreshold = Math.cos(angleThreshold) - digitThreshold; + uvThreshold += digitThreshold; + convexThreshold = Math.cos(Math.PI - convexThreshold) - digitThreshold; + // Грани + var faces:Dictionary = new Dictionary(); + // Карта соответствий vertex:faces(dictionary) + var map:Dictionary = new Dictionary(); + for (face = faceList,faceList = null; face != null; face = next) { + next = face.next; + face.next = null; + // Расчёт нормали + a = face.wrapper.vertex; + b = face.wrapper.next.vertex; + c = face.wrapper.next.next.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + nl = nx*nx + ny*ny + nz*nz; + if (nl > digitThreshold) { + nl = 1/Math.sqrt(nl); + nx *= nl; + ny *= nl; + nz *= nl; + face.normalX = nx; + face.normalY = ny; + face.normalZ = nz; + face.offset = a.x*nx + a.y*ny + a.z*nz; + faces[face] = true; + for (wn = face.wrapper; wn != null; wn = wn.next) { + vertex = wn.vertex; + dictionary = map[vertex]; + if (dictionary == null) { + dictionary = new Dictionary(); + map[vertex] = dictionary; + } + dictionary[face] = true; + } + } + } + // Остров + var island:Vector. = new Vector.(); + // Соседи текущей грани + var siblings:Dictionary = new Dictionary(); + // Грани, которые точно не входят в текущий остров + var unfit:Dictionary = new Dictionary(); + while (true) { + // Получение первой попавшейся грани + face = null; + for (key in faces) { + face = key; + delete faces[key]; + break; + } + if (face == null) break; + // Создани острова + var num:int = 0; + island[num] = face; + num++; + a = face.wrapper.vertex; + b = face.wrapper.next.vertex; + c = face.wrapper.next.next.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + abu = b.u - a.u; + abv = b.v - a.v; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + acu = c.u - a.u; + acv = c.v - a.v; + nx = face.normalX; + ny = face.normalY; + nz = face.normalZ; + // Нахождение матрицы uv-трансформации + var det:Number = -nx*acy*abz + acx*ny*abz + nx*aby*acz - abx*ny*acz - acx*aby*nz + abx*acy*nz; + var ima:Number = (-ny*acz + acy*nz)/det; + var imb:Number = (nx*acz - acx*nz)/det; + var imc:Number = (-nx*acy + acx*ny)/det; + var imd:Number = (a.x*ny*acz - nx*a.y*acz - a.x*acy*nz + acx*a.y*nz + nx*acy*a.z - acx*ny*a.z)/det; + var ime:Number = (ny*abz - aby*nz)/det; + var imf:Number = (-nx*abz + abx*nz)/det; + var img:Number = (nx*aby - abx*ny)/det; + var imh:Number = (nx*a.y*abz - a.x*ny*abz + a.x*aby*nz - abx*a.y*nz - nx*aby*a.z + abx*ny*a.z)/det; + var ma:Number = abu*ima + acu*ime; + var mb:Number = abu*imb + acu*imf; + var mc:Number = abu*imc + acu*img; + var md:Number = abu*imd + acu*imh + a.u; + var me:Number = abv*ima + acv*ime; + var mf:Number = abv*imb + acv*imf; + var mg:Number = abv*imc + acv*img; + var mh:Number = abv*imd + acv*imh + a.v; + for (key in unfit) { + delete unfit[key]; + } + for (i = 0; i < num; i++) { + face = island[i]; + for (key in siblings) { + delete siblings[key]; + } + // Сбор потенциальных соседей грани + for (w = face.wrapper; w != null; w = w.next) { + for (key in map[w.vertex]) { + if (faces[key] && !unfit[key]) { + siblings[key] = true; + } + } + } + for (key in siblings) { + sibling = key; + // Если совпадают по нормалям + if (nx*sibling.normalX + ny*sibling.normalY + nz*sibling.normalZ >= angleThreshold) { + for (s = sibling.wrapper; s != null; s = s.next) { + vertex = s.vertex; + var du:Number = ma*vertex.x + mb*vertex.y + mc*vertex.z + md - vertex.u; + var dv:Number = me*vertex.x + mf*vertex.y + mg*vertex.z + mh - vertex.v; + if (du > uvThreshold || du < -uvThreshold || dv > uvThreshold || dv < -uvThreshold) break; + } + // Если совпадают по UV + if (s == null) { + // Проверка на соседство + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // Добавление в остров + if (w != null) { + island[num] = sibling; + num++; + delete faces[sibling]; + } + } else { + unfit[sibling] = true; + } + } else { + unfit[sibling] = true; + } + } + } + // Если в острове только одна грань + if (num == 1) { + face = island[0]; + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + // Объединение острова + } else { + while (true) { + var weld:Boolean = false; + // Перебор граней острова + for (i = 0; i < num - 1; i++) { + face = island[i]; + if (face != null) { + // Попытки объединить текущую грань с остальными + for (j = 1; j < num; j++) { + sibling = island[j]; + if (sibling != null) { + // Поиск общего ребра + for (w = face.wrapper; w != null; w = w.next) { + wn = (w.next != null) ? w.next : face.wrapper; + for (s = sibling.wrapper; s != null; s = s.next) { + sn = (s.next != null) ? s.next : sibling.wrapper; + if (w.vertex == sn.vertex && wn.vertex == s.vertex) break; + } + if (s != null) break; + } + // Если ребро найдено + if (w != null) { + // Расширение граней объединеия + while (true) { + wm = (wn.next != null) ? wn.next : face.wrapper; + //for (sp = sibling.wrapper; sp.next != s && sp.next != null; sp = sp.next); + sp = sibling.wrapper; + while (sp.next != s && sp.next != null) sp = sp.next; + if (wm.vertex == sp.vertex) { + wn = wm; + s = sp; + } else break; + } + while (true) { + //for (wp = face.wrapper; wp.next != w && wp.next != null; wp = wp.next); + wp = face.wrapper; + while (wp.next != w && wp.next != null) wp = wp.next; + sm = (sn.next != null) ? sn.next : sibling.wrapper; + if (wp.vertex == sm.vertex) { + w = wp; + sn = sm; + } else break; + } + // Первый перегиб + a = w.vertex; + b = sm.vertex; + c = wp.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Второй перегиб + a = s.vertex; + b = wm.vertex; + c = sp.vertex; + abx = b.x - a.x; + aby = b.y - a.y; + abz = b.z - a.z; + acx = c.x - a.x; + acy = c.y - a.y; + acz = c.z - a.z; + nx = acz*aby - acy*abz; + ny = acx*abz - acz*abx; + nz = acy*abx - acx*aby; + if (nx < digitThreshold && nx > -digitThreshold && ny < digitThreshold && ny > -digitThreshold && nz < digitThreshold && nz > -digitThreshold) { + if (abx*acx + aby*acy + abz*acz > 0) continue; + } else { + if (face.normalX*nx + face.normalY*ny + face.normalZ*nz < 0) continue; + } + nl = 1/Math.sqrt(abx*abx + aby*aby + abz*abz); + abx *= nl; + aby *= nl; + abz *= nl; + nl = 1/Math.sqrt(acx*acx + acy*acy + acz*acz); + acx *= nl; + acy *= nl; + acz *= nl; + if (abx*acx + aby*acy + abz*acz < convexThreshold) continue; + // Объединение + weld = true; + var newFace:Face = new Face(); + newFace.material = face.material; + newFace.normalX = face.normalX; + newFace.normalY = face.normalY; + newFace.normalZ = face.normalZ; + newFace.offset = face.offset; + // Здесь может быть удаление промежуточных вершин из меша + wm = null; + for (; wn != w; wn = (wn.next != null) ? wn.next : face.wrapper) { + sm = new Wrapper(); + sm.vertex = wn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + for (; sn != s; sn = (sn.next != null) ? sn.next : sibling.wrapper) { + sm = new Wrapper(); + sm.vertex = sn.vertex; + if (wm != null) { + wm.next = sm; + } else { + newFace.wrapper = sm; + } + wm = sm; + } + island[i] = newFace; + island[j] = null; + face = newFace; + // Если, то собираться будет парами, иначе к одной прицепляется максимально (это чуть бустрее) + if (pairWeld) break; + } + } + } + } + } + if (!weld) break; + } + // Сбор объединённых граней + for (i = 0; i < num; i++) { + face = island[i]; + if (face != null) { + // Определение лучшей последовательности вершин + face.calculateBestSequenceAndNormal(); + // Добавление + if (last != null) { + last.next = face; + } else { + faceList = face; + } + last = face; + } + } + } + } + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Occluder.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Occluder.as new file mode 100644 index 0000000..6c9fe37 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Occluder.as @@ -0,0 +1,499 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + /** + * Полигональный объект-перекрытие. + * Объекты, которые он перекрывает от видимости камеры, исключаются из отрисовки. + * Сам окклюдер не отрисовывается. + * Должен быть конвексным + */ + public class Occluder extends Object3D { + + alternativa3d var faceList:Face; + alternativa3d var edgeList:Edge; + alternativa3d var vertexList:Vertex; + + /** + * Минимальное отношение площади перекрытия окклюдером вьюпорта к площади вьюпорта (от 0 до 1) + * Если окклюдер перекрывает больше, он помещается в очередь и учитывается + * при дальнейшей отрисовке в пределах кадра, иначе игнорируется + */ + public var minSize:Number = 0; + + /** + * Копирование геометрии меша + * @param source Объект копирования + * Меш, геометрия которого копируется, обязан быть конвексным, иначе окклюдер будет некорректно работать + */ + public function copyFrom(source:Mesh):void { + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + faceList = source.faceList; + vertexList = source.vertexList; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (transformation != null) { + vertex.cameraX = transformation.ma*vertex.x + transformation.mb*vertex.y + transformation.mc*vertex.z + transformation.md; + vertex.cameraY = transformation.me*vertex.x + transformation.mf*vertex.y + transformation.mg*vertex.z + transformation.mh; + vertex.cameraZ = transformation.mi*vertex.x + transformation.mj*vertex.y + transformation.mk*vertex.z + transformation.ml; + } else { + vertex.cameraX = vertex.x; + vertex.cameraY = vertex.y; + vertex.cameraZ = vertex.z; + } + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + /** + * Расчёт рёбер по имеющимся вершинам и граням + */ + public function calculateEdges():void { + var face:Face; + var wrapper:Wrapper; + var edge:Edge; + // Построение рёбер + edgeList = null; + for (face = faceList; face != null; face = face.next) { + // Расчёт нормали + face.calculateBestSequenceAndNormal(); + // Перебор отрезков грани + var a:Vertex; + var b:Vertex; + for (wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next,a = b) { + a = wrapper.vertex; + b = (wrapper.next != null) ? wrapper.next.vertex : face.wrapper.vertex; + // Перебор созданных рёбер + for (edge = edgeList; edge != null; edge = edge.next) { + // Если некорректная геометрия + if (edge.a == a && edge.b == b) { + trace("Incorrect face joint"); + } + // Если найдено созданное ребро с такими вершинами + if (edge.a == b && edge.b == a) break; + } + if (edge != null) { + edge.right = face; + } else { + edge = new Edge(); + edge.a = a; + edge.b = b; + edge.left = face; + edge.next = edgeList; + edgeList = edge; + } + } + } + // Проверка на валидность + for (edge = edgeList; edge != null; edge = edge.next) { + var abx:Number = edge.b.x - edge.a.x; + var aby:Number = edge.b.y - edge.a.y; + var abz:Number = edge.b.z - edge.a.z; + var crx:Number = edge.right.normalZ*edge.left.normalY - edge.right.normalY*edge.left.normalZ; + var cry:Number = edge.right.normalX*edge.left.normalZ - edge.right.normalZ*edge.left.normalX; + var crz:Number = edge.right.normalY*edge.left.normalX - edge.right.normalX*edge.left.normalY; + // Если перегиб внутрь + if (abx*crx + aby*cry + abz*crz < 0) { + trace("Geometry is non convex"); + } + // Если ребро с одной гранью + if (edge.left == null || edge.right == null) { + trace("Geometry is non whole"); + } + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (faceList == null || edgeList == null) return; + var canvas:Canvas; + var debug:int; + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Определение видимости граней + var cameraInside:Boolean = true; + for (var face:Face = faceList; face != null; face = face.next) { + if (face.normalX*imd + face.normalY*imh + face.normalZ*iml > face.offset) { + face.distance = 1; + cameraInside = false; + } else { + face.distance = 0; + } + } + if (cameraInside) return; + // Подготовка окклюдера в камере + var occluder:Vertex; + var num:int = 0; + var occludeAll:Boolean = true; + var culling:int = object.culling; + var viewSizeX:Number = camera.viewSizeX; + var viewSizeY:Number = camera.viewSizeY; + var a:Vertex; + var b:Vertex; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var t:Number; + // Расчёт контура + for (var edge:Edge = edgeList; edge != null; edge = edge.next) { + // Если ребро в описывающем контуре + if (edge.left.distance != edge.right.distance) { + // Определение направления (против часовой) + if (edge.left.distance > 0) { + a = edge.a; + b = edge.b; + } else { + a = edge.b; + b = edge.a; + } + // Трансформация в камеру + ax = object.ma*a.x + object.mb*a.y + object.mc*a.z + object.md; + ay = object.me*a.x + object.mf*a.y + object.mg*a.z + object.mh; + az = object.mi*a.x + object.mj*a.y + object.mk*a.z + object.ml; + bx = object.ma*b.x + object.mb*b.y + object.mc*b.z + object.md; + by = object.me*b.x + object.mf*b.y + object.mg*b.z + object.mh; + bz = object.mi*b.x + object.mj*b.y + object.mk*b.z + object.ml; + // Клиппинг + if (culling > 0) { + if (az <= -ax && bz <= -bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -bx && az <= -ax) { + t = (ax + az)/(ax + az - bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ax && bz <= bx) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > bx && az <= ax) { + t = (az - ax)/(az - ax + bx - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= -ay && bz <= -by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > -by && az <= -ay) { + t = (ay + az)/(ay + az - by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + if (az <= ay && bz <= by) { + if (occludeAll && by*ax - bx*ay > 0) occludeAll = false; + continue; + } else if (bz > by && az <= ay) { + t = (az - ay)/(az - ay + by - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + az += (bz - az)*t; + } else if (bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + bz = az + (bz - az)*t; + } + occludeAll = false; + } + // Создание новой плоскости + a = a.create(); + a.next = occluder; + num++; + occluder = a; + // Плоскость + occluder.cameraX = bz*ay - by*az; + occluder.cameraY = bx*az - bz*ax; + occluder.cameraZ = by*ax - bx*ay; + // Ребро (для перевода в систему контейнера) + occluder.x = ax; + occluder.y = ay; + occluder.z = az; + occluder.u = bx; + occluder.v = by; + occluder.offset = bz; + } + } + // Если контур не нулевой + if (occluder != null) { + // Проверка размера на экране + if (minSize > 0) { + // Проецирование рёбер контура + var projected:Vertex = Vertex.createList(num); + for (a = occluder,b = projected; a != null; a = a.next,b = b.next) { + // Проецтрование + b.x = a.x*viewSizeX/a.z; + b.y = a.y*viewSizeY/a.z; + b.u = a.u*viewSizeX/a.offset; + b.v = a.v*viewSizeY/a.offset; + // Расчёт левой нормали + b.cameraX = b.y - b.v; + b.cameraY = b.u - b.x; + b.offset = b.cameraX*b.x + b.cameraY*b.y; + } + // Клиппинг рамки вьюпорта по рёбрам контура + var frame:Vertex; + if (culling > 0) { + if (culling & 4) { + ax = -camera.viewSizeX; + ay = -camera.viewSizeY; + bx = -camera.viewSizeX; + by = camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 8) { + ax = camera.viewSizeX; + ay = camera.viewSizeY; + bx = camera.viewSizeX; + by = -camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 16) { + ax = camera.viewSizeX; + ay = -camera.viewSizeY; + bx = -camera.viewSizeX; + by = -camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + if (culling & 32) { + ax = -camera.viewSizeX; + ay = camera.viewSizeY; + bx = camera.viewSizeX; + by = camera.viewSizeY; + for (a = projected; a != null; a = a.next) { + az = ax*a.cameraX + ay*a.cameraY - a.offset; + bz = bx*a.cameraX + by*a.cameraY - a.offset; + if (az < 0 || bz < 0) { + if (az >= 0 && bz < 0) { + t = az/(az - bz); + ax += (bx - ax)*t; + ay += (by - ay)*t; + } else if (az < 0 && bz >= 0) { + t = az/(az - bz); + bx = ax + (bx - ax)*t; + by = ay + (by - ay)*t; + } + } else break; + } + if (a == null) { + b = occluder.create(); + b.next = frame; + frame = b; + frame.x = ax; + frame.y = ay; + frame.u = bx; + frame.v = by; + } + } + } + // Нахождение площади перекрытия + var square:Number = 0; + az = projected.x; + bz = projected.y; + a = projected; + while (a.next != null) a = a.next; + for (a.next = frame,a = projected; a != null; a = a.next) { + square += (a.u - az)*(a.y - bz) - (a.v - bz)*(a.x - az); + if (a.next == null) break; + } + // Зачистка + a.next = Vertex.collector; + Vertex.collector = projected; + // Если площадь меньше заданной + if (square/(camera.viewSizeX*camera.viewSizeY*8) < minSize) { + // Зачистка + a = occluder; + while (a.next != null) a = a.next; + a.next = Vertex.collector; + Vertex.collector = occluder; + return; + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) { + for (a = occluder; a != null; a = a.next) { + ax = a.x*viewSizeX/a.z; + ay = a.y*viewSizeY/a.z; + bx = a.u*viewSizeX/a.offset; + by = a.v*viewSizeY/a.offset; + canvas.gfx.moveTo(ax, ay); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(ax + (bx - ax)*0.8, ay + (by - ay)*0.8); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(bx, by); + } + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Добавление окклюдера в камеру + camera.occluders[camera.numOccluders] = occluder; + camera.numOccluders++; + // Если окклюдер перекрывает весь экран + } else if (occludeAll) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) { + t = 1.5; + canvas.gfx.moveTo(-viewSizeX + t, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(-viewSizeX + t, viewSizeY*0.6); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(-viewSizeX + t, viewSizeY - t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(viewSizeX*0.6, viewSizeY - t); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(viewSizeX - t, viewSizeY - t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(viewSizeX - t, -viewSizeY*0.6); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(viewSizeX - t, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0x0000FF); + canvas.gfx.lineTo(-viewSizeX*0.6, -viewSizeY + t); + canvas.gfx.lineStyle(3, 0xFF0000); + canvas.gfx.lineTo(-viewSizeX + t, -viewSizeY + t); + } + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + camera.clearOccluders(); + camera.occludedAll = true; + } + } + + } +} + +import alternativa.engine3d.core.Face; +import alternativa.engine3d.core.Vertex; + +class Edge { + + public var next:Edge; + + public var a:Vertex; + public var b:Vertex; + + public var left:Face; + public var right:Face; + +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Reference.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Reference.as new file mode 100644 index 0000000..6ca5856 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Reference.as @@ -0,0 +1,45 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + + use namespace alternativa3d; + + /** + * Объект-ссылка. + * Может ссылаться на любой трёхмерный объект, в том числе контейнер с любой вложенностью или Reference. + * При отрисовке он отрисовывает вместо себя объект, + * на который ссылается, подставляя только свою трансформацию, alpha, blendMode, colorTransform и filters. + */ + public class Reference extends Object3D { + + /** + * Объект, который подставляется при отрисовке вместо себя + */ + public var referenceObject:Object3D; + + public function Reference(referenceObject:Object3D = null) { + this.referenceObject = referenceObject; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + referenceObject.draw(camera, object, parentCanvas); + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + return referenceObject.getGeometry(camera, object); + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + referenceObject.updateBounds(bounds, transformation); + } + + override alternativa3d function cullingInCamera(camera:Camera3D, object:Object3D, culling:int):int { + object.culling = referenceObject.cullingInCamera(camera, object, culling); + return object.culling; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Skin.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Skin.as new file mode 100644 index 0000000..bf4b519 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Skin.as @@ -0,0 +1,835 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + + use namespace alternativa3d; + + public class Skin extends Mesh { + + private var joints:Vector. = new Vector.(); + private var _numJoints:uint = 0; + + public function calculateBindingMatrices():void { + ma = 1; + mb = 0; + mc = 0; + md = 0; + me = 0; + mf = 1; + mg = 0; + mh = 0; + mi = 0; + mj = 0; + mk = 1; + ml = 0; + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.calculateBindingMatrix(this); + } + } + + public function normalizeWeights():void { + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) vertex.offset = 0; + var joint:Joint; + for (var i:int = 0; i < _numJoints; i++) { + joint = joints[i]; + joint.addWeights(); + } + for (i = 0; i < _numJoints; i++) { + joint = joints[i]; + joint.normalizeWeights(); + } + } + + public function addJoint(joint:Joint):void { + joints[_numJoints++] = joint; + } + + public function removeJoint(joint:Joint):void { + var i:int = joints.indexOf(joint); + if (i < 0) throw new ArgumentError("Joint not found"); + _numJoints--; + var j:int = i + 1; + while (i < _numJoints) { + joints[i] = joints[j]; + i++; + j++; + } + joints.length = _numJoints; + } + + public function get numJoints():uint { + return _numJoints; + } + + public function getJointAt(index:uint):Joint { + return joints[index]; + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + var canvas:Canvas; + var debug:int; + var list:Face; + var vertex:Vertex; + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return; + culling = 0; + } + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + vertex.drawID = 0; + } + // Расчёт координат вершин + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.draw(camera, object, parentCanvas); + } + // Отсечение по нормалям + var last:Face; + for (var face:Face = faceList; face != null; face = face.next) { + var a:Vertex = face.wrapper.vertex; + var b:Vertex = face.wrapper.next.vertex; + var c:Vertex = face.wrapper.next.next.vertex; + var abx:Number = b.cameraX - a.cameraX; + var aby:Number = b.cameraY - a.cameraY; + var abz:Number = b.cameraZ - a.cameraZ; + var acx:Number = c.cameraX - a.cameraX; + var acy:Number = c.cameraY - a.cameraY; + var acz:Number = c.cameraZ - a.cameraZ; + if ((acz*aby - acy*abz)*a.cameraX + (acx*abz - acz*abx)*a.cameraY + (acy*abx - acx*aby)*a.cameraZ < 0) { + if (list != null) { + last.processNext = face; + } else { + list = face; + } + last = face; + } + } + if (last != null) { + last.processNext = null; + } + if (list == null) return; + + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + list = cull(list, culling, camera); + } else { + list = clip(list, culling, camera); + } + if (list == null) return; + } + // Сортировка + if (list.processNext != null) { + if (sorting == 1) { + list = sortByAverageZ(list); + } else if (sorting == 2) { + list = sortByDynamicBSP(list, camera, threshold); + } + } + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, list, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + for (face = list; face != null; face = next) { + var next:Face = face.processNext; + // Если конец списка или смена материала + if (next == null || next.material != list.material) { + // Разрыв на стыке разных материалов + face.processNext = null; + // Если материал для части списка не пустой + if (list.material != null) { + // Отрисовка + list.material.draw(camera, canvas, list, object.ml); + } else { + // Разрыв связей + while (list != null) { + face = list.processNext; + list.processNext = null; + list = face; + } + } + list = next; + } + } + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + } + // Расчёт координат вершин + if (transformation == null) { + ma = 1; + mb = 0; + mc = 0; + md = 0; + me = 0; + mf = 1; + mg = 0; + mh = 0; + mi = 0; + mj = 0; + mk = 1; + ml = 0; + transformation = this; + } + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.updateBounds(bounds, transformation); + } + // Расширение баунда + for (var vertex:Vertex = vertexList; vertex != null; vertex = vertex.next) { + if (vertex.cameraX < bounds.boundMinX) bounds.boundMinX = vertex.cameraX; + if (vertex.cameraX > bounds.boundMaxX) bounds.boundMaxX = vertex.cameraX; + if (vertex.cameraY < bounds.boundMinY) bounds.boundMinY = vertex.cameraY; + if (vertex.cameraY > bounds.boundMaxY) bounds.boundMaxY = vertex.cameraY; + if (vertex.cameraZ < bounds.boundMinZ) bounds.boundMinZ = vertex.cameraZ; + if (vertex.cameraZ > bounds.boundMaxZ) bounds.boundMaxZ = vertex.cameraZ; + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + var vertex:Vertex; + // Коррекция куллинга + var culling:int = object.culling; + if (clipping == 0) { + if (culling & 1) return null; + culling = 0; + } + // Расчёт инверсной матрицы камеры + calculateInverseMatrix(object); + // Обнуление вершин + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.cameraX = 0; + vertex.cameraY = 0; + vertex.cameraZ = 0; + vertex.transformID = 1; + //trace(vertex.transformID); + vertex.drawID = 0; + } + // Расчёт координат вершин + for (var i:int = 0; i < _numJoints; i++) { + var joint:Joint = joints[i]; + joint.getGeometry(camera, object); + } + // Получение клона видимой геометрии + var struct:Face; + if (sorting == 3) { + return null; + } else { + if (faceList == null) return null; + struct = calculateFaces(faceList, culling, camera, object.ma, object.mb, object.mc, object.md, object.me, object.mf, object.mg, object.mh, object.mi, object.mj, object.mk, object.ml); + } + // Зачистка после ремапа + for (vertex = vertexList; vertex != null; vertex = vertex.next) { + vertex.value = null; + } + // Создание геометрии + if (struct != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = struct; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.ima = ima; + geometry.imb = imb; + geometry.imc = imc; + geometry.imd = imd; + geometry.ime = ime; + geometry.imf = imf; + geometry.img = img; + geometry.imh = imh; + geometry.imi = imi; + geometry.imj = imj; + geometry.imk = imk; + geometry.iml = iml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + override protected function calculateFaces(struct:Face, culling:int, camera:Camera3D, ma:Number, mb:Number, mc:Number, md:Number, me:Number, mf:Number, mg:Number, mh:Number, mi:Number, mj:Number, mk:Number, ml:Number):Face { + var first:Face; + var last:Face; + var a:Vertex; + var b:Vertex; + var c:Vertex; + var d:Wrapper; + var v:Vertex; + var w:Wrapper; + var wFirst:Wrapper; + var wLast:Wrapper; + var wNext:Wrapper; + var wNew:Wrapper; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var bz:Number; + var cx:Number; + var cy:Number; + var cz:Number; + var c1:Boolean = (culling & 1) > 0; + var c2:Boolean = (culling & 2) > 0; + var c4:Boolean = (culling & 4) > 0; + var c8:Boolean = (culling & 8) > 0; + var c16:Boolean = (culling & 16) > 0; + var c32:Boolean = (culling & 32) > 0; + var near:Number = camera.nearClipping; + var far:Number = camera.farClipping; + var needX:Boolean = c4 || c8; + var needY:Boolean = c16 || c32; + var t:Number; + // Перебор оригинальных граней + for (var face:Face = struct; face != null; face = face.next) { + // Отсечение по нормали + d = face.wrapper; + a = d.vertex; + d = d.next; + b = d.vertex; + d = d.next; + c = d.vertex; + d = d.next; + var abx:Number = b.cameraX - a.cameraX; + var aby:Number = b.cameraY - a.cameraY; + var abz:Number = b.cameraZ - a.cameraZ; + var acx:Number = c.cameraX - a.cameraX; + var acy:Number = c.cameraY - a.cameraY; + var acz:Number = c.cameraZ - a.cameraZ; + if ((acz*aby - acy*abz)*a.cameraX + (acx*abz - acz*abx)*a.cameraY + (acy*abx - acx*aby)*a.cameraZ >= 0) continue; + var faceCulling:int = 0; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (needX) { + ax = a.cameraX; + bx = b.cameraX; + cx = c.cameraX; + } + if (needY) { + ay = a.cameraY; + by = b.cameraY; + cy = c.cameraY; + } + az = a.cameraZ; + bz = b.cameraZ; + cz = c.cameraZ; + // Куллинг + if (clipping == 1) { + if (c1) { + if (az <= near || bz <= near || cz <= near) continue; + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) break; + } + if (w != null) continue; + } + if (c2 && az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) break; + } + if (w == null) continue; + } + if (c4 && az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c8 && az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) break; + } + if (w == null) continue; + } + if (c16 && az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + if (c32 && az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) break; + } + if (w == null) continue; + } + // Клиппинг + } else { + if (c1) { + if (az <= near && bz <= near && cz <= near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ > near) { + faceCulling |= 1; + break; + } + } + if (w == null) continue; + } else if (az > near && bz > near && cz > near) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ <= near) { + faceCulling |= 1; + break; + } + } + } else { + faceCulling |= 1; + } + } + if (c2) { + if (az >= far && bz >= far && cz >= far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ < far) { + faceCulling |= 2; + break; + } + } + if (w == null) continue; + } else if (az < far && bz < far && cz < far) { + for (w = d; w != null; w = w.next) { + if (w.vertex.cameraZ >= far) { + faceCulling |= 2; + break; + } + } + } else { + faceCulling |= 2; + } + } + if (c4) { + if (az <= -ax && bz <= -bx && cz <= -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX < v.cameraZ) { + faceCulling |= 4; + break; + } + } + if (w == null) continue; + } else if (az > -ax && bz > -bx && cz > -cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraX >= v.cameraZ) { + faceCulling |= 4; + break; + } + } + } else { + faceCulling |= 4; + } + } + if (c8) { + if (az <= ax && bz <= bx && cz <= cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX < v.cameraZ) { + faceCulling |= 8; + break; + } + } + if (w == null) continue; + } else if (az > ax && bz > bx && cz > cx) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraX >= v.cameraZ) { + faceCulling |= 8; + break; + } + } + } else { + faceCulling |= 8; + } + } + if (c16) { + if (az <= -ay && bz <= -by && cz <= -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY < v.cameraZ) { + faceCulling |= 16; + break; + } + } + if (w == null) continue; + } else if (az > -ay && bz > -by && cz > -cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (-v.cameraY >= v.cameraZ) { + faceCulling |= 16; + break; + } + } + } else { + faceCulling |= 16; + } + } + if (c32) { + if (az <= ay && bz <= by && cz <= cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY < v.cameraZ) { + faceCulling |= 32; + break; + } + } + if (w == null) continue; + } else if (az > ay && bz > by && cz > cy) { + for (w = d; w != null; w = w.next) { + v = w.vertex; + if (v.cameraY >= v.cameraZ) { + faceCulling |= 32; + break; + } + } + } else { + faceCulling |= 32; + } + } + if (faceCulling > 0) { + wFirst = null; + wLast = null; + w = face.wrapper; + while (w != null) { + wNew = w.create(); + wNew.vertex = w.vertex; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + w = w.next; + } + // Клиппинг по передней стороне + if (faceCulling & 1) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz > near && az <= near || bz <= near && az > near) { + t = (near - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > near) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по задней стороне + if (faceCulling & 2) { + a = wLast.vertex; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bz = b.cameraZ; + if (bz < far && az >= far || bz >= far && az < far) { + t = (far - az)/(bz - az); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz < far) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + az = bz; + } + if (wFirst == null) continue; + } + // Клиппинг по левой стороне + if (faceCulling & 4) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > -bx && az <= -ax || bz <= -bx && az > -ax) { + t = (ax + az)/(ax + az - bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по правой стороне + if (faceCulling & 8) { + a = wLast.vertex; + ax = a.cameraX; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + bx = b.cameraX; + bz = b.cameraZ; + if (bz > bx && az <= ax || bz <= bx && az > ax) { + t = (az - ax)/(az - ax + bx - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > bx) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ax = bx; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по верхней стороне + if (faceCulling & 16) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > -by && az <= -ay || bz <= -by && az > -ay) { + t = (ay + az)/(ay + az - by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > -by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + // Клипинг по нижней стороне + if (faceCulling & 32) { + a = wLast.vertex; + ay = a.cameraY; + az = a.cameraZ; + for (w = wFirst,wFirst = null,wLast = null; w != null; w = wNext) { + wNext = w.next; + b = w.vertex; + by = b.cameraY; + bz = b.cameraZ; + if (bz > by && az <= ay || bz <= by && az > ay) { + t = (az - ay)/(az - ay + by - bz); + v = b.create(); + camera.lastVertex.next = v; + camera.lastVertex = v; + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = az + (bz - az)*t; + v.u = a.u + (b.u - a.u)*t; + v.v = a.v + (b.v - a.v)*t; + wNew = w.create(); + wNew.vertex = v; + if (wFirst != null) wLast.next = wNew; else wFirst = wNew; + wLast = wNew; + } + if (bz > by) { + if (wFirst != null) wLast.next = w; else wFirst = w; + wLast = w; + w.next = null; + } else { + w.vertex = null; + w.next = Wrapper.collector; + Wrapper.collector = w; + } + a = b; + ay = by; + az = bz; + } + if (wFirst == null) continue; + } + } + } + } + var newFace:Face = face.create(); + camera.lastFace.next = newFace; + camera.lastFace = newFace; + newFace.material = face.material; + var newVertex:Vertex; + if (faceCulling > 0) { + for (w = wFirst; w != null; w = w.next) { + v = w.vertex; + if (v.value != null) { + w.vertex = v.value; + } else if (v.transformID > 0) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + w.vertex = newVertex; + } + } + } else { + wFirst = null; + for (w = face.wrapper; w != null; w = w.next) { + wNew = w.create(); + v = w.vertex; + if (v.value == null) { + newVertex = v.create(); + camera.lastVertex.next = newVertex; + camera.lastVertex = newVertex; + newVertex.cameraX = v.cameraX; + newVertex.cameraY = v.cameraY; + newVertex.cameraZ = v.cameraZ; + newVertex.u = v.u; + newVertex.v = v.v; + v.value = newVertex; + } + wNew.vertex = v.value; + if (wFirst != null) { + wLast.next = wNew; + } else { + wFirst = wNew; + } + wLast = wNew; + } + } + newFace.wrapper = wFirst; + if (first != null) { + last.processNext = newFace; + } else { + first = newFace; + } + last = newFace; + } + return first; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/SkyBox.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/SkyBox.as new file mode 100644 index 0000000..47d5680 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/SkyBox.as @@ -0,0 +1,118 @@ +package alternativa.engine3d.objects { + + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Clipping; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Sorting; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.TextureMaterial; + + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace alternativa3d; + + public class SkyBox extends Mesh { + + static public const LEFT:String = "left"; + static public const RIGHT:String = "right"; + static public const BACK:String = "back"; + static public const FRONT:String = "front"; + static public const BOTTOM:String = "bottom"; + static public const TOP:String = "top"; + + private var leftFace:Face; + private var rightFace:Face; + private var backFace:Face; + private var frontFace:Face; + private var bottomFace:Face; + private var topFace:Face; + + public function SkyBox(size:Number, left:TextureMaterial, right:TextureMaterial, back:TextureMaterial, front:TextureMaterial, bottom:TextureMaterial, top:TextureMaterial, uvPadding:Number = 0) { + + size *= 0.5; + + var a:Vertex = addVertex(-size, -size, size, uvPadding, uvPadding); + var b:Vertex = addVertex(-size, -size, -size, uvPadding, 1 - uvPadding); + var c:Vertex = addVertex(-size, size, -size, 1 - uvPadding, 1 - uvPadding); + var d:Vertex = addVertex(-size, size, size, 1 - uvPadding, uvPadding); + leftFace = addQuadFace(a, b, c, d, left); + + a = addVertex(size, size, size, uvPadding, uvPadding); + b = addVertex(size, size, -size, uvPadding, 1 - uvPadding); + c = addVertex(size, -size, -size, 1 - uvPadding, 1 - uvPadding); + d = addVertex(size, -size, size, 1 - uvPadding, uvPadding); + rightFace = addQuadFace(a, b, c, d, right); + + a = addVertex(size, -size, size, uvPadding, uvPadding); + b = addVertex(size, -size, -size, uvPadding, 1 - uvPadding); + c = addVertex(-size, -size, -size, 1 - uvPadding, 1 - uvPadding); + d = addVertex(-size, -size, size, 1 - uvPadding, uvPadding); + backFace = addQuadFace(a, b, c, d, back); + + a = addVertex(-size, size, size, uvPadding, uvPadding); + b = addVertex(-size, size, -size, uvPadding, 1 - uvPadding); + c = addVertex(size, size, -size, 1 - uvPadding, 1 - uvPadding); + d = addVertex(size, size, size, 1 - uvPadding, uvPadding); + frontFace = addQuadFace(a, b, c, d, front); + + a = addVertex(-size, size, -size, uvPadding, uvPadding); + b = addVertex(-size, -size, -size, uvPadding, 1 - uvPadding); + c = addVertex(size, -size, -size, 1 - uvPadding, 1 - uvPadding); + d = addVertex(size, size, -size, 1 - uvPadding, uvPadding); + bottomFace = addQuadFace(a, b, c, d, bottom); + + a = addVertex(-size, -size, size, uvPadding, uvPadding); + b = addVertex(-size, size, size, uvPadding, 1 - uvPadding); + c = addVertex(size, size, size, 1 - uvPadding, 1 - uvPadding); + d = addVertex(size, -size, size, 1 - uvPadding, uvPadding); + topFace = addQuadFace(a, b, c, d, top); + + calculateBounds(); + calculateNormals(true); + + clipping = Clipping.FACE_CLIPPING; + sorting = Sorting.NONE; + } + + public function getSide(side:String):Face { + switch (side) { + case LEFT: + return leftFace; + case RIGHT: + return rightFace; + case BACK: + return backFace; + case FRONT: + return frontFace; + case BOTTOM: + return bottomFace; + case TOP: + return topFace; + } + return null; + } + + public function transformUV(side:String, matrix:Matrix):void { + var face:Face = getSide(side); + if (face != null) { + for (var wrapper:Wrapper = face.wrapper; wrapper != null; wrapper = wrapper.next) { + var vertex:Vertex = wrapper.vertex; + var res:Point = matrix.transformPoint(new Point(vertex.u, vertex.v)); + vertex.u = res.x; + vertex.v = res.y; + } + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + object.culling &= ~3; + super.draw(camera, object, parentCanvas); + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/Sprite3D.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/Sprite3D.as new file mode 100644 index 0000000..26915af --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/Sprite3D.as @@ -0,0 +1,585 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Camera3D; + import alternativa.engine3d.core.Canvas; + import alternativa.engine3d.core.Debug; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Geometry; + import alternativa.engine3d.core.Object3D; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.core.Wrapper; + import alternativa.engine3d.materials.Material; + import flash.display.Sprite; + import flash.geom.Matrix3D; + + use namespace alternativa3d; + + /** + * Плоский, всегда развёрнутый к камере трёхмерный объект + */ + public class Sprite3D extends Object3D { + + /** + * Материал + */ + public var material:Material; + /** + * Режим сортировки на случай конфликта + * 0 - без сортировки + * 1 - сортировка по средним Z + * 2 - построение динамического BSP при отрисовке + * 3 - проход по предрасчитанному BSP + */ + public var sorting:int = 0; + /** + * X точки привязки + */ + public var originX:Number = 0.5; + /** + * Y точки привязки + */ + public var originY:Number = 0.5; + /** + * Режим отсечения объекта по пирамиде видимости камеры. + * 0 - весь объект + * 2 - клиппинг граней по пирамиде видимости камеры + */ + public var clipping:int = 2; + /** + * Угол поворота в радианах в плоскости экрана + */ + public var rotation:Number = 0; + /** + * Ширина + */ + public var width:Number; + /** + * Высота + */ + public var height:Number; + /** + * Зависимость размера на экране от удалённости от камеры + */ + public var perspectiveScale:Boolean = true; + + // Текстурная матрица + static private var tma:Number; + static private var tmb:Number; + static private var tmc:Number; + static private var tmd:Number; + static private var tmtx:Number; + static private var tmty:Number; + + public function Sprite3D(width:Number = 100, height:Number = 100, material:Material = null) { + this.width = width; + this.height = height; + this.material = material; + } + + public function calculateResolution(textureWidth:int, textureHeight:int, type:int = 1, matrix:Matrix3D = null):Number { + var w:Number = width; + var h:Number = height; + if (matrix != null) { + var object:Object3D = new Object3D(); + object.setMatrix(matrix); + object.composeMatrix(); + var scale:Number = (Math.sqrt(object.ma*object.ma + object.me*object.me + object.mi*object.mi) + Math.sqrt(object.mb*object.mb + object.mf*object.mf + object.mj*object.mj) + Math.sqrt(object.mc*object.mc + object.mg*object.mg + object.mk*object.mk))/3; + w *= scale; + h *= scale; + } + w /= textureWidth; + h /= textureHeight; + if (type == 0) { + return w; + } else if (type == 1) { + return (w + h)/2; + } else if (type == 2) { + return (w < h) ? w : h; + } else { + return (w > h) ? w : h; + } + } + + override alternativa3d function draw(camera:Camera3D, object:Object3D, parentCanvas:Canvas):void { + if (material == null) return; + var canvas:Canvas; + var debug:int; + var face:Face = calculateFace(camera, object); + if (face != null) { + // Дебаг + if (camera.debug && (debug = camera.checkInDebug(this)) > 0) { + canvas = parentCanvas.getChildCanvas(object, true, false); + if (debug & Debug.EDGES) Debug.drawEdges(camera, canvas, face, 0xFFFFFF); + if (debug & Debug.BOUNDS) Debug.drawBounds(camera, canvas, object, boundMinX, boundMinY, boundMinZ, boundMaxX, boundMaxY, boundMaxZ); + } + // Отрисовка + canvas = parentCanvas.getChildCanvas(object, true, false, object.alpha, object.blendMode, object.colorTransform, object.filters); + material.drawViewAligned(camera, canvas, face, object.ml, tma, tmb, tmc, tmd, tmtx, tmty); + } + } + + override alternativa3d function getGeometry(camera:Camera3D, object:Object3D):Geometry { + if (material == null) return null; + var face:Face = calculateFace(camera, object); + if (face != null) { + var geometry:Geometry = Geometry.create(); + geometry.interactiveObject = object; + geometry.faceStruct = face; + geometry.ma = object.ma; + geometry.mb = object.mb; + geometry.mc = object.mc; + geometry.md = object.md; + geometry.me = object.me; + geometry.mf = object.mf; + geometry.mg = object.mg; + geometry.mh = object.mh; + geometry.mi = object.mi; + geometry.mj = object.mj; + geometry.mk = object.mk; + geometry.ml = object.ml; + geometry.alpha = object.alpha; + geometry.blendMode = object.blendMode; + geometry.colorTransform = object.colorTransform; + geometry.filters = object.filters; + geometry.sorting = sorting; + geometry.viewAligned = true; + geometry.tma = tma; + geometry.tmb = tmb; + geometry.tmc = tmc; + geometry.tmd = tmd; + geometry.tmtx = tmtx; + geometry.tmty = tmty; + if (camera.debug) geometry.debug = camera.checkInDebug(this); + return geometry; + } else { + return null; + } + } + + private function calculateFace(camera:Camera3D, object:Object3D):Face { + var culling:int = object.culling & 60; + var z:Number = object.ml; + var size:Number; + var ax:Number; + var ay:Number; + var az:Number; + var bx:Number; + var by:Number; + var cx:Number; + var cy:Number; + var dx:Number; + var dy:Number; + var first:Vertex; + var last:Vertex; + // Выход по ближнему или дальнему расстоянию отсечения + if (z <= camera.nearClipping || z >= camera.farClipping) return null; + // Проекция + var projectionX:Number = camera.viewSizeX/z; + var projectionY:Number = camera.viewSizeY/z; + var projectionZ:Number = camera.focalLength/z; + // Учёт искажения матрицы камеры под 90 градусов + var perspectiveScaleX:Number = camera.focalLength/camera.viewSizeX; + var perspectiveScaleY:Number = camera.focalLength/camera.viewSizeY; + // Нахождение среднего размера спрайта + ax = object.ma/perspectiveScaleX; + ay = object.me/perspectiveScaleY; + az = object.mi; + size = Math.sqrt(ax*ax + ay*ay + az*az); + ax = object.mb/perspectiveScaleX; + ay = object.mf/perspectiveScaleY; + az = object.mj; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = object.mc/perspectiveScaleX; + ay = object.mg/perspectiveScaleY; + az = object.mk; + size += Math.sqrt(ax*ax + ay*ay + az*az); + size /= 3; + // Учёт флага масштабирования + if (!perspectiveScale) size /= projectionZ; + // Если не задано вращение + if (rotation == 0) { + // Размеры спрайта в матрице камеры + var cameraWidth:Number = size*width*perspectiveScaleX; + var cameraHeight:Number = size*height*perspectiveScaleY; + ax = object.md - originX*cameraWidth; + ay = object.mh - originY*cameraHeight; + cx = ax + cameraWidth; + cy = ay + cameraHeight; + // Подготовка смещения матрицы отрисовки + tmtx = ax*projectionX; + tmty = ay*projectionY; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (ax > z || ay > z || cx < -z || cy < -z) return null; + if (clipping == 2) { + if (ax < -z) ax = -z; + if (ay < -z) ay = -z; + if (cx > z) cx = z; + if (cy > z) cy = z; + } + } + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax; + last.cameraY = cy; + last.cameraZ = z; + last = last.next; + last.cameraX = cx; + last.cameraY = cy; + last.cameraZ = z; + last = last.next; + last.cameraX = cx; + last.cameraY = ay; + last.cameraZ = z; + // Подготовка матрицы отрисовки + tma = size*projectionZ*width; + tmb = 0; + tmc = 0; + tmd = size*projectionZ*height; + } else { + // Расчёт векторов ширины и высоты + var sin:Number = -Math.sin(rotation)*size; + var cos:Number = Math.cos(rotation)*size; + var cameraWidthX:Number = cos*width*perspectiveScaleX; + var cameraWidthY:Number = -sin*width*perspectiveScaleY; + var cameraHeightX:Number = sin*height*perspectiveScaleX; + var cameraHeightY:Number = cos*height*perspectiveScaleY; + ax = object.md - originX*cameraWidthX - originY*cameraHeightX; + ay = object.mh - originX*cameraWidthY - originY*cameraHeightY; + bx = ax + cameraHeightX; + by = ay + cameraHeightY; + cx = ax + cameraWidthX + cameraHeightX; + cy = ay + cameraWidthY + cameraHeightY; + dx = ax + cameraWidthX; + dy = ay + cameraWidthY; + // Подготовка смещения матрицы отрисовки + tmtx = ax*projectionX; + tmty = ay*projectionY; + // Отсечение по пирамиде видимости + if (culling > 0) { + if (clipping == 1) { + if ((culling & 4) && z <= -ax && z <= -bx && z <= -cx && z <= -dx) return null; + if ((culling & 8) && z <= ax && z <= bx && z <= cx && z <= dx) return null; + if ((culling & 16) && z <= -ay && z <= -by && z <= -cy && z <= -dy) return null; + if ((culling & 32) && z <= ay && z <= by && z <= cy && z <= dy) return null; + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + } else { + if (culling & 4) { + if (z <= -ax && z <= -bx && z <= -cx && z <= -dx) { + return null; + } else if (z > -ax && z > -bx && z > -cx && z > -dx) { + culling &= 59; + } + } + if (culling & 8) { + if (z <= ax && z <= bx && z <= cx && z <= dx) { + return null; + } else if (z > ax && z > bx && z > cx && z > dx) { + culling &= 55; + } + } + if (culling & 16) { + if (z <= -ay && z <= -by && z <= -cy && z <= -dy) { + return null; + } else if (z > -ay && z > -by && z > -cy && z > -dy) { + culling &= 47; + } + } + if (culling & 32) { + if (z <= ay && z <= by && z <= cy && z <= dy) { + return null; + } else if (z > ay && z > by && z > cy && z > dy) { + culling &= 31; + } + } + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + if (culling > 0) { + var t:Number; + var a:Vertex; + var b:Vertex; + var v:Vertex; + var next:Vertex; + // Клиппинг по левой стороне + if (culling & 4) { + a = last; + ax = a.cameraX; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + bx = b.cameraX; + if (z > -bx && z <= -ax || z <= -bx && z > -ax) { + t = (ax + z)/(ax - bx); + v = b.create(); + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > -bx) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ax = bx; + } + if (first == null) return null; + } + // Клиппинг по правой стороне + if (culling & 8) { + a = last; + ax = a.cameraX; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + bx = b.cameraX; + if (z > bx && z <= ax || z <= bx && z > ax) { + t = (z - ax)/(bx - ax); + v = b.create(); + v.cameraX = ax + (bx - ax)*t; + v.cameraY = a.cameraY + (b.cameraY - a.cameraY)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > bx) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ax = bx; + } + if (first == null) return null; + } + // Клиппинг по верхней стороне + if (culling & 16) { + a = last; + ay = a.cameraY; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + by = b.cameraY; + if (z > -by && z <= -ay || z <= -by && z > -ay) { + t = (ay + z)/(ay - by); + v = b.create(); + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > -by) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ay = by; + } + if (first == null) return null; + } + // Клиппинг по нижней стороне + if (culling & 32) { + a = last; + ay = a.cameraY; + for (b = first,first = null,last = null; b != null; b = next) { + next = b.next; + by = b.cameraY; + if (z > by && z <= ay || z <= by && z > ay) { + t = (z - ay)/(by - ay); + v = b.create(); + v.cameraX = a.cameraX + (b.cameraX - a.cameraX)*t; + v.cameraY = ay + (by - ay)*t; + v.cameraZ = z; + if (first != null) last.next = v; else first = v; + last = v; + } + if (z > by) { + if (first != null) last.next = b; else first = b; + last = b; + b.next = null; + } else { + b.next = Vertex.collector; + Vertex.collector = b; + } + a = b; + ay = by; + } + if (first == null) return null; + } + } + } + } else { + // Создание вершин + first = Vertex.createList(4); + last = first; + last.cameraX = ax; + last.cameraY = ay; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraHeightX; + last.cameraY = ay + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX + cameraHeightX; + last.cameraY = ay + cameraWidthY + cameraHeightY; + last.cameraZ = z; + last = last.next; + last.cameraX = ax + cameraWidthX; + last.cameraY = ay + cameraWidthY; + last.cameraZ = z; + } + // Подготовка матрицы отрисовки + tma = cos*projectionZ*width; + tmb = -sin*projectionZ*width; + tmc = sin*projectionZ*height; + tmd = cos*projectionZ*height; + } + // Отправка на отложенное удаление + camera.lastVertex.next = first; + camera.lastVertex = last; + // Создание грани + var face:Face = Face.create(); + face.material = material; + camera.lastFace.next = face; + camera.lastFace = face; + var wrapper:Wrapper = Wrapper.create(); + face.wrapper = wrapper; + wrapper.vertex = first; + for (first = first.next; first != null; first = first.next) { + wrapper.next = wrapper.create(); + wrapper = wrapper.next; + wrapper.vertex = first; + } + return face; + } + + override alternativa3d function updateBounds(bounds:Object3D, transformation:Object3D = null):void { + // Расчёт локального радиуса + var w:Number = ((originX >= 0.5) ? originX : (1 - originX))*width; + var h:Number = ((originY >= 0.5) ? originY : (1 - originY))*height; + var radius:Number = Math.sqrt(w*w + h*h); + var cx:Number = 0; + var cy:Number = 0; + var cz:Number = 0; + if (transformation != null) { + // Нахождение среднего размера спрайта + var ax:Number = transformation.ma; + var ay:Number = transformation.me; + var az:Number = transformation.mi; + var size:Number = Math.sqrt(ax*ax + ay*ay + az*az); + ax = transformation.mb; + ay = transformation.mf; + az = transformation.mj; + size += Math.sqrt(ax*ax + ay*ay + az*az); + ax = transformation.mc; + ay = transformation.mg; + az = transformation.mk; + size += Math.sqrt(ax*ax + ay*ay + az*az); + radius *= size/3; + cx = transformation.md; + cy = transformation.mh; + cz = transformation.ml; + } + if (cx - radius < bounds.boundMinX) bounds.boundMinX = cx - radius; + if (cx + radius > bounds.boundMaxX) bounds.boundMaxX = cx + radius; + if (cy - radius < bounds.boundMinY) bounds.boundMinY = cy - radius; + if (cy + radius > bounds.boundMaxY) bounds.boundMaxY = cy + radius; + if (cz - radius < bounds.boundMinZ) bounds.boundMinZ = cz - radius; + if (cz + radius > bounds.boundMaxZ) bounds.boundMaxZ = cz + radius; + } + + public function copyFrom(source:Sprite3D):void { + name = source.name; + visible = source.visible; + alpha = source.alpha; + blendMode = source.blendMode; + colorTransform = source.colorTransform; + filters = source.filters; + x = source.x; + y = source.y; + z = source.z; + rotationX = source.rotationX; + rotationY = source.rotationY; + rotationZ = source.rotationZ; + scaleX = source.scaleX; + scaleY = source.scaleY; + scaleZ = source.scaleZ; + boundMinX = source.boundMinX; + boundMinY = source.boundMinY; + boundMinZ = source.boundMinZ; + boundMaxX = source.boundMaxX; + boundMaxY = source.boundMaxY; + boundMaxZ = source.boundMaxZ; + + clipping = source.clipping; + sorting = source.sorting; + + material = source.material; + originX = source.originX; + originY = source.originY; + rotation = source.rotation; + perspectiveScale = source.perspectiveScale; + } + + override public function clone():Object3D { + var sprite:Sprite3D = new Sprite3D(width, height, material); + sprite.cloneBaseProperties(this); + sprite.clipping = clipping; + sprite.sorting = sorting; + sprite.originX = originX; + sprite.originY = originY; + sprite.rotation = rotation; + sprite.perspectiveScale = perspectiveScale; + return sprite; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/objects/VertexBinding.as b/Alternativa3D7/7.4/alternativa/engine3d/objects/VertexBinding.as new file mode 100644 index 0000000..6917181 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/objects/VertexBinding.as @@ -0,0 +1,12 @@ +package alternativa.engine3d.objects { + import alternativa.engine3d.core.Vertex; + + public class VertexBinding { + + public var next:VertexBinding; + + public var vertex:Vertex; + public var weight:Number = 0; + + } +} \ No newline at end of file diff --git a/Alternativa3D7/7.4/alternativa/engine3d/primitives/Box.as b/Alternativa3D7/7.4/alternativa/engine3d/primitives/Box.as new file mode 100644 index 0000000..c2cf9a3 --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/primitives/Box.as @@ -0,0 +1,194 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.core.Face; + import alternativa.engine3d.core.Vertex; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + public class Box extends Mesh { + + public function Box(width:Number = 100, length:Number = 100, height:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1, heightSegments:uint = 1, reverse:Boolean = false) { + + var wp:int = widthSegments + 1; + var lp:int = lengthSegments + 1; + var hp:int = heightSegments + 1; + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var hh:Number = height*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var hd:Number = 1/heightSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var hs:Number = height/heightSegments; + var x:int; + var y:int; + var z:int; + + var v:int = 0; + + var vertices:Vector. = new Vector.(); + var face:Face; + + // Нижняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v++] = addVertex(x*ws - wh, y*ls - lh, -hh, (widthSegments - x)*wd, (lengthSegments - y)*ld); + } + } + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + face = addQuadFace(vertices[(x + 1)*lp + y + 1], vertices[x*lp + y + 1], vertices[x*lp + y], vertices[(x + 1)*lp + y]); + face.normalZ = 1; + face.offset = -hh; + } else { + face = addQuadFace(vertices[(x + 1)*lp + y + 1], vertices[(x + 1)*lp + y], vertices[x*lp + y], vertices[x*lp + y + 1]); + face.normalZ = -1; + face.offset = hh; + } + face.normalX = 0; + face.normalY = 0; + } + } + } + var o:uint = wp*lp; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v++] = addVertex(x*ws - wh, y*ls - lh, hh, x*wd, (lengthSegments - y)*ld); + } + } + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + if (x < widthSegments && y < lengthSegments) { + if (reverse) { + face = addQuadFace(vertices[o + x*lp + y + 1], vertices[o + (x + 1)*lp + y + 1], vertices[o + (x + 1)*lp + y], vertices[o + x*lp + y]); + face.normalZ = -1; + face.offset = -hh; + } else { + face = addQuadFace(vertices[o + x*lp + y], vertices[o + (x + 1)*lp + y], vertices[o + (x + 1)*lp + y + 1], vertices[o + x*lp + y + 1]); + face.normalZ = 1; + face.offset = hh; + } + face.normalX = 0; + face.normalY = 0; + } + } + } + o += wp*lp; + + // Передняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(x*ws - wh, -lh, z*hs - hh, x*wd, (heightSegments - z)*hd); + } + } + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + x*hp + z + 1], vertices[o + (x + 1)*hp + z + 1], vertices[o + (x + 1)*hp + z], vertices[o + x*hp + z]); + face.normalY = 1; + face.offset = -lh; + } else { + face = addQuadFace(vertices[o + x*hp + z], vertices[o + (x + 1)*hp + z], vertices[o + (x + 1)*hp + z + 1], vertices[o + x*hp + z + 1]); + face.normalY = -1; + face.offset = lh; + } + face.normalX = 0; + face.normalZ = 0; + } + } + } + o += wp*hp; + + // Задняя грань + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(x*ws - wh, lh, z*hs - hh, (widthSegments - x)*wd, (heightSegments - z)*hd); + } + } + for (x = 0; x < wp; x++) { + for (z = 0; z < hp; z++) { + if (x < widthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + (x + 1)*hp + z], vertices[o + (x + 1)*hp + z + 1], vertices[o + x*hp + z + 1], vertices[o + x*hp + z]); + face.normalY = -1; + face.offset = -lh; + } else { + face = addQuadFace(vertices[o + x*hp + z], vertices[o + x*hp + z + 1], vertices[o + (x + 1)*hp + z + 1], vertices[o + (x + 1)*hp + z]); + face.normalY = 1; + face.offset = lh; + } + face.normalX = 0; + face.normalZ = 0; + } + } + } + o += wp*hp; + + // Левая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(-wh, y*ls - lh, z*hs - hh, (lengthSegments - y)*ld, (heightSegments - z)*hd); + } + } + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + (y + 1)*hp + z], vertices[o + (y + 1)*hp + z + 1], vertices[o + y*hp + z + 1], vertices[o + y*hp + z]); + face.normalX = 1; + face.offset = -wh; + } else { + face = addQuadFace(vertices[o + y*hp + z], vertices[o + y*hp + z + 1], vertices[o + (y + 1)*hp + z + 1], vertices[o + (y + 1)*hp + z]); + face.normalX = -1; + face.offset = wh; + } + face.normalY = 0; + face.normalZ = 0; + } + } + } + o += lp*hp; + + // Правая грань + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + vertices[v++] = addVertex(wh, y*ls - lh, z*hs - hh, y*ld, (heightSegments - z)*hd); + } + } + for (y = 0; y < lp; y++) { + for (z = 0; z < hp; z++) { + if (y < lengthSegments && z < heightSegments) { + if (reverse) { + face = addQuadFace(vertices[o + y*hp + z + 1], vertices[o + (y + 1)*hp + z + 1], vertices[o + (y + 1)*hp + z], vertices[o + y*hp + z]); + face.normalX = -1; + face.offset = -wh; + } else { + face = addQuadFace(vertices[o + y*hp + z], vertices[o + (y + 1)*hp + z], vertices[o + (y + 1)*hp + z + 1], vertices[o + y*hp + z + 1]); + face.normalX = 1; + face.offset = wh; + } + face.normalY = 0; + face.normalZ = 0; + } + } + } + + // Установка границ + boundMinX = -wh; + boundMinY = -lh; + boundMinZ = -hh; + boundMaxX = wh; + boundMaxY = lh; + boundMaxZ = hh; + } + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/primitives/GeoSphere.as b/Alternativa3D7/7.4/alternativa/engine3d/primitives/GeoSphere.as new file mode 100644 index 0000000..ace371a --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/primitives/GeoSphere.as @@ -0,0 +1,251 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class GeoSphere extends Mesh { + + public function GeoSphere(radius:Number = 100, segments:uint = 2, reverse:Boolean = false) { + + /* + const sections:uint = 20; + + var i:uint; + + var theta:Number; + var sin:Number; + var cos:Number; + // z расстояние до нижней и верхней крышки полюса + var subz:Number = 4.472136E-001*radius; + // радиус на расстоянии subz + var subrad:Number = 2*subz; + + var v:uint = 0; + + var f:uint = sections*segments*segments; + + addVertex(0, 0, radius, 0, 0); + // vertices[v++] = 0; + // vertices[v++] = 0; + // vertices[v++] = radius; + + // Создание вершин верхней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*2*i/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + addVertex(subrad*cos, subrad*sin, subz, 0, 0); + // vertices[v++] = subrad*cos; + // vertices[v++] = subrad*sin; + // vertices[v++] = subz; + } + // Создание вершин нижней крышки + for (i = 0; i < 5; i++) { + theta = Math.PI*((i << 1) + 1)/5; + sin = Math.sin(theta); + cos = Math.cos(theta); + addVertex(subrad*cos, subrad*sin, -subz, 0, 0); + // vertices[v++] = subrad*cos; + // vertices[v++] = subrad*sin; + // vertices[v++] = -subz; + } + + addVertex(0, 0, -radius, 0, 0); + // vertices[v++] = 0; + // vertices[v++] = 0; + // vertices[v++] = -radius; + + if (segments > 1) { + for (i = 1; i < 6; i++) { + v = interpolate(0, i, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i % 5 + 1, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, i + 5, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i, (i + 3) % 5 + 6, segments, v); + } + for (i = 1; i < 6; i++) { + v = interpolate(i + 5, i % 5 + 6, segments, v); + } + for (i = 6; i < 11; i++) { + v = interpolate(11, i, segments, v); + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + f*(segments - 1) + i, 12 + (f + 1) % 5*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + (f + 15)*(segments - 1) + i, 12 + (f + 10)*(segments - 1) + i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 15)*(segments - 1) + segments - 2 - i, 12 + (f + 10)*(segments - 1) + segments - 2 - i, i + 1, v); + } + } + for (f = 0; f < 5; f++) { + for (i = 1; i <= segments - 2; i++) { + v = interpolate(12 + ((f + 1) % 5 + 25)*(segments - 1) + i, 12 + (f + 25)*(segments - 1) + i, i + 1, v); + } + } + } + + // for (i = 0; i < numVertices; i++) { + // var j:uint = i*3; + // uvts[j] = Math.atan2(vertices[j + 1], vertices[j])/(Math.PI*2); + // uvts[j] = 0.5 + (reverse ? -uvts[j] : uvts[j]); + // uvts[j + 1] = 0.5 + Math.asin(vertices[j + 2]/radius)/Math.PI; + // } + + var num:uint = 0; + for (f = 0; f <= sections - 1; f++) { + for (var row:uint = 0; row <= segments - 1; row++) { + for (var column:uint = 0; column <= row; column++) { + var a:uint = findVertices(segments, f, row, column); + var b:uint = findVertices(segments, f, row + 1, column); + var c:uint = findVertices(segments, f, row + 1, column + 1); + + if (reverse) { + indices[num++] = a; + indices[num++] = c; + indices[num++] = b; + } else { + indices[num++] = a; + indices[num++] = b; + indices[num++] = c; + } + + if (column < row) { + var d:uint = findVertices(segments, f, row, column + 1); + if (reverse) { + indices[num++] = a; + indices[num++] = d; + indices[num++] = c; + } else { + indices[num++] = a; + indices[num++] = c; + indices[num++] = d; + } + } + } + } + } + + boundMinX = boundMinY = boundMinZ = -radius; + boundMaxX = boundMaxY = boundMaxZ = radius; + } + + private function interpolate(a:uint, b:uint, num:uint, v:uint):uint { + a *= 3; + b *= 3; + var ax:Number = vertices[a]; + var ay:Number = vertices[a + 1]; + var az:Number = vertices[a + 2]; + var bx:Number = vertices[b]; + var by:Number = vertices[b + 1]; + var bz:Number = vertices[b + 2]; + var cos:Number = (ax*bx + ay*by + az*bz)/(ax*ax + ay*ay + az*az); + cos = (cos < -1) ? -1 : ((cos > 1) ? 1 : cos); + var theta:Number = Math.acos(cos); + var sin:Number = Math.sin(theta); + for (var e:uint = 1; e < num; e++) { + var theta1:Number = theta*e/num; + var theta2:Number = theta*(num - e)/num; + var st1:Number = Math.sin(theta1); + var st2:Number = Math.sin(theta2); + vertices[v++] = (ax*st2 + bx*st1)/sin; + vertices[v++] = (ay*st2 + by*st1)/sin; + vertices[v++] = (az*st2 + bz*st1)/sin; + } + return v; + //*/ + } + + /* + private function findVertices(segments:uint, section:uint, row:uint, column:uint):uint { + if (row == 0) { + if (section < 5) { + return (0); + } + if (section > 14) { + return (11); + } + return (section - 4); + } + if (row == segments && column == 0) { + if (section < 5) { + return (section + 1); + } + if (section < 10) { + return ((section + 4) % 5 + 6); + } + if (section < 15) { + return ((section + 1) % 5 + 1); + } + return ((section + 1) % 5 + 6); + } + if (row == segments && column == segments) { + if (section < 5) { + return ((section + 1) % 5 + 1); + } + if (section < 10) { + return (section + 1); + } + if (section < 15) { + return (section - 9); + } + return (section - 9); + } + if (row == segments) { + if (section < 5) { + return (12 + (5 + section)*(segments - 1) + column - 1); + } + if (section < 10) { + return (12 + (20 + (section + 4) % 5)*(segments - 1) + column - 1); + } + if (section < 15) { + return (12 + (section - 5)*(segments - 1) + segments - 1 - column); + } + return (12 + (5 + section)*(segments - 1) + segments - 1 - column); + } + if (column == 0) { + if (section < 5) { + return (12 + section*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 15)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + ((section + 1) % 5 + 15)*(segments - 1) + segments - 1 - row); + } + return (12 + ((section + 1) % 5 + 25)*(segments - 1) + row - 1); + } + if (column == row) { + if (section < 5) { + return (12 + (section + 1) % 5*(segments - 1) + row - 1); + } + if (section < 10) { + return (12 + (section % 5 + 10)*(segments - 1) + row - 1); + } + if (section < 15) { + return (12 + (section % 5 + 10)*(segments - 1) + segments - row - 1); + } + return (12 + (section % 5 + 25)*(segments - 1) + row - 1); + } + return (12 + 30*(segments - 1) + section*(segments - 1)*(segments - 2)/2 + (row - 1)*(row - 2)/2 + column - 1); + } + + //*/ + + } +} diff --git a/Alternativa3D7/7.4/alternativa/engine3d/primitives/Plane.as b/Alternativa3D7/7.4/alternativa/engine3d/primitives/Plane.as new file mode 100644 index 0000000..bb34daf --- /dev/null +++ b/Alternativa3D7/7.4/alternativa/engine3d/primitives/Plane.as @@ -0,0 +1,66 @@ +package alternativa.engine3d.primitives { + import alternativa.engine3d.alternativa3d; + import alternativa.engine3d.objects.Mesh; + + use namespace alternativa3d; + + /** + * @private + */ + public class Plane extends Mesh { + + /** + * + * @param width + * @param length + * @param widthSegments + * @param lengthSegments + */ + public function Plane(width:Number = 100, length:Number = 100, widthSegments:uint = 1, lengthSegments:uint = 1) { + /* + var wp:uint = widthSegments + 1; + var lp:uint = lengthSegments + 1; + + createEmptyGeometry(wp*lp, (widthSegments*lengthSegments) << 2); + + var wh:Number = width*0.5; + var lh:Number = length*0.5; + var wd:Number = 1/widthSegments; + var ld:Number = 1/lengthSegments; + var ws:Number = width/widthSegments; + var ls:Number = length/lengthSegments; + var x:uint; + var y:uint; + var z:uint; + + var v:uint = 0; + var f:uint = 0; + + // Верхняя грань + for (x = 0; x < wp; x++) { + for (y = 0; y < lp; y++) { + vertices[v] = x*ws - wh; + uvts[v++] = x*wd; + vertices[v] = y*ls - lh; + uvts[v++] = (lengthSegments - y)*ld; + vertices[v++] = 0; + + if (x < widthSegments && y < lengthSegments) { + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + + indices[f++] = x*lp + y; + indices[f++] = (x + 1)*lp + y + 1; + indices[f++] = x*lp + y + 1; + } + } + } + // Установка границ + _boundBox = new BoundBox(); + _boundBox.setSize(-wh, -lh, 0, wh, lh, 0); + */ + } + + } +}