public void Curl(PointF curlPos, PointF curlDir, double radius) { // First add some 'helper' lines used for development. if (DRAW_CURL_POSITION) { mBufCurlPositionLines.Position(0); mBufCurlPositionLines.Put(curlPos.X); mBufCurlPositionLines.Put(curlPos.Y - 1.0f); mBufCurlPositionLines.Put(curlPos.X); mBufCurlPositionLines.Put(curlPos.Y + 1.0f); mBufCurlPositionLines.Put(curlPos.X - 1.0f); mBufCurlPositionLines.Put(curlPos.Y); mBufCurlPositionLines.Put(curlPos.X + 1.0f); mBufCurlPositionLines.Put(curlPos.Y); mBufCurlPositionLines.Put(curlPos.X); mBufCurlPositionLines.Put(curlPos.Y); mBufCurlPositionLines.Put(curlPos.X + curlDir.X * 2); mBufCurlPositionLines.Put(curlPos.Y + curlDir.Y * 2); mBufCurlPositionLines.Position(0); } // Actual 'curl' implementation starts here. mBufVertices.Position(0); mBufColors.Position(0); if (DRAW_TEXTURE) { mBufTexCoords.Position(0); } // Calculate curl angle from direction. double curlAngle = Math.Acos(curlDir.X); curlAngle = curlDir.Y > 0 ? -curlAngle : curlAngle; // Initiate rotated rectangle which's is translated to curlPos and // rotated so that curl direction heads to right (1,0). Vertices are // ordered in ascending order based on x -coordinate at the same time. // And using y -coordinate in very rare case in which two vertices have // same x -coordinate. mArrTempVertices.AddAll(mArrRotatedVertices); mArrRotatedVertices.Clear(); for (int i = 0; i < 4; ++i) { Vertex v = mArrTempVertices.Remove(0); v.Set(mRectangle[i]); v.Translate(-curlPos.X, -curlPos.Y); v.RotateZ(-curlAngle); int j = 0; for (; j < mArrRotatedVertices.Size(); ++j) { Vertex v2 = mArrRotatedVertices.Get(j); if (v.mPosX > v2.mPosX) { break; } if (v.mPosX == v2.mPosX && v.mPosY > v2.mPosY) { break; } } mArrRotatedVertices.Add(j, v); } // Rotated rectangle lines/vertex indices. We need to find bounding // lines for rotated rectangle. After sorting vertices according to // their x -coordinate we don't have to worry about vertices at indices // 0 and 1. But due to inaccuracy it's possible vertex 3 is not the // opposing corner from vertex 0. So we are calculating distance from // vertex 0 to vertices 2 and 3 - and altering line indices if needed. // Also vertices/lines are given in an order first one has x -coordinate // at least the latter one. This property is used in getIntersections to // see if there is an intersection. int[][] lines = new int[][] { new int[] { 0, 1 }, new int[] { 0, 2 }, new int[] { 1, 3 }, new int[] { 2, 3 } }; { // TODO: There really has to be more 'easier' way of doing this - // not including extensive use of sqrt. Vertex v0 = mArrRotatedVertices.Get(0); Vertex v2 = mArrRotatedVertices.Get(2); Vertex v3 = mArrRotatedVertices.Get(3); double dist2 = Math.Sqrt((v0.mPosX - v2.mPosX) * (v0.mPosX - v2.mPosX) + (v0.mPosY - v2.mPosY) * (v0.mPosY - v2.mPosY)); double dist3 = Math.Sqrt((v0.mPosX - v3.mPosX) * (v0.mPosX - v3.mPosX) + (v0.mPosY - v3.mPosY) * (v0.mPosY - v3.mPosY)); if (dist2 > dist3) { lines[1][1] = 3; lines[2][1] = 2; } } mVerticesCountFront = mVerticesCountBack = 0; if (DRAW_SHADOW) { mArrTempShadowVertices.AddAll(mArrDropShadowVertices); mArrTempShadowVertices.AddAll(mArrSelfShadowVertices); mArrDropShadowVertices.Clear(); mArrSelfShadowVertices.Clear(); } // Length of 'curl' curve. double curlLength = Math.PI * radius; // Calculate scan lines. // TODO: Revisit this code one day. There is room for optimization here. mArrScanLines.Clear(); if (mMaxCurlSplits > 0) { mArrScanLines.Add((double)0); } for (int i = 1; i < mMaxCurlSplits; ++i) { mArrScanLines.Add((-curlLength * i) / (mMaxCurlSplits - 1)); } // As mRotatedVertices is ordered regarding x -coordinate, adding // this scan line produces scan area picking up vertices which are // rotated completely. One could say 'until infinity'. mArrScanLines.Add(mArrRotatedVertices.Get(3).mPosX - 1); // Start from right most vertex. Pretty much the same as first scan area // is starting from 'infinity'. double scanXmax = mArrRotatedVertices.Get(0).mPosX + 1; for (int i = 0; i < mArrScanLines.Size(); ++i) { // Once we have scanXmin and scanXmax we have a scan area to start // working with. double scanXmin = mArrScanLines.Get(i); // First iterate 'original' rectangle vertices within scan area. for (int j = 0; j < mArrRotatedVertices.Size(); ++j) { Vertex v = mArrRotatedVertices.Get(j); // Test if vertex lies within this scan area. // TODO: Frankly speaking, can't remember why equality check was // added to both ends. Guessing it was somehow related to case // where radius=0f, which, given current implementation, could // be handled much more effectively anyway. if (v.mPosX >= scanXmin && v.mPosX <= scanXmax) { // Pop out a vertex from temp vertices. Vertex n = mArrTempVertices.Remove(0); n.Set(v); // This is done solely for triangulation reasons. Given a // rotated rectangle it has max 2 vertices having // intersection. Array <Vertex> intersections2 = GetIntersections( mArrRotatedVertices, lines, n.mPosX); // In a sense one could say we're adding vertices always in // two, positioned at the ends of intersecting line. And for // triangulation to work properly they are added based on y // -coordinate. And this if-else is doing it for us. if (intersections2.Size() == 1 && intersections2.Get(0).mPosY > v.mPosY) { // In case intersecting vertex is higher add it first. mArrOutputVertices.AddAll(intersections2); mArrOutputVertices.Add(n); } else if (intersections2.Size() <= 1) { // Otherwise add original vertex first. mArrOutputVertices.Add(n); mArrOutputVertices.AddAll(intersections2); } else { // There should never be more than 1 intersecting // vertex. But if it happens as a fallback simply skip // everything. mArrTempVertices.Add(n); mArrTempVertices.AddAll(intersections2); } } } // Search for scan line intersections. Array <Vertex> intersections = GetIntersections(mArrRotatedVertices, lines, scanXmin); // We expect to get 0 or 2 vertices. In rare cases there's only one // but in general given a scan line intersecting rectangle there // should be 2 intersecting vertices. if (intersections.Size() == 2) { // There were two intersections, add them based on y // -coordinate, higher first, lower last. Vertex v1 = intersections.Get(0); Vertex v2 = intersections.Get(1); if (v1.mPosY < v2.mPosY) { mArrOutputVertices.Add(v2); mArrOutputVertices.Add(v1); } else { mArrOutputVertices.AddAll(intersections); } } else if (intersections.Size() != 0) { // This happens in a case in which there is a original vertex // exactly at scan line or something went very much wrong if // there are 3+ vertices. What ever the reason just return the // vertices to temp vertices for later use. In former case it // was handled already earlier once iterating through // mRotatedVertices, in latter case it's better to avoid doing // anything with them. mArrTempVertices.AddAll(intersections); } // Add vertices found during this iteration to vertex etc buffers. while (mArrOutputVertices.Size() > 0) { Vertex v = mArrOutputVertices.Remove(0); mArrTempVertices.Add(v); // Local texture front-facing flag. bool textureFront; // Untouched vertices. if (i == 0) { textureFront = true; mVerticesCountFront++; } // 'Completely' rotated vertices. else if (i == mArrScanLines.Size() - 1 || curlLength == 0) { v.mPosX = -(curlLength + v.mPosX); v.mPosZ = 2 * radius; v.mPenumbraX = -v.mPenumbraX; textureFront = false; mVerticesCountBack++; } // Vertex lies within 'curl'. else { // Even though it's not obvious from the if-else clause, // here v.mPosX is between [-curlLength, 0]. And we can do // calculations around a half cylinder. double rotY = Math.PI * (v.mPosX / curlLength); v.mPosX = radius * Math.Sin(rotY); v.mPosZ = radius - (radius * Math.Cos(rotY)); v.mPenumbraX *= Math.Cos(rotY); // Map color multiplier to [.1f, 1f] range. v.mColorFactor = (float)(.1f + .9f * Math.Sqrt(Math .Sin(rotY) + 1)); if (v.mPosZ >= radius) { textureFront = false; mVerticesCountBack++; } else { textureFront = true; mVerticesCountFront++; } } // We use local textureFront for flipping backside texture // locally. Plus additionally if mesh is in flip texture mode, // we'll make the procedure "backwards". Also, until this point, // texture coordinates are within [0, 1] range so we'll adjust // them to final texture coordinates too. if (textureFront != mFlipTexture) { v.mTexX *= mTextureRectFront.Right; v.mTexY *= mTextureRectFront.Bottom; v.mColor = mTexturePage.GetColor(CurlPage.SIDE_FRONT); } else { v.mTexX *= mTextureRectBack.Right; v.mTexY *= mTextureRectBack.Bottom; v.mColor = mTexturePage.GetColor(CurlPage.SIDE_BACK); } // Move vertex back to 'world' coordinates. v.RotateZ(curlAngle); v.Translate(curlPos.X, curlPos.Y); AddVertex(v); // Drop shadow is cast 'behind' the curl. if (DRAW_SHADOW && v.mPosZ > 0 && v.mPosZ <= radius) { ShadowVertex sv = mArrTempShadowVertices.Remove(0); sv.mPosX = v.mPosX; sv.mPosY = v.mPosY; sv.mPosZ = v.mPosZ; sv.mPenumbraX = (v.mPosZ / 2) * -curlDir.X; sv.mPenumbraY = (v.mPosZ / 2) * -curlDir.Y; sv.mPenumbraColor = v.mPosZ / radius; int idx = (mArrDropShadowVertices.Size() + 1) / 2; mArrDropShadowVertices.Add(idx, sv); } // Self shadow is cast partly over mesh. if (DRAW_SHADOW && v.mPosZ > radius) { ShadowVertex sv = mArrTempShadowVertices.Remove(0); sv.mPosX = v.mPosX; sv.mPosY = v.mPosY; sv.mPosZ = v.mPosZ; sv.mPenumbraX = ((v.mPosZ - radius) / 3) * v.mPenumbraX; sv.mPenumbraY = ((v.mPosZ - radius) / 3) * v.mPenumbraY; sv.mPenumbraColor = (v.mPosZ - radius) / (2 * radius); int idx = (mArrSelfShadowVertices.Size() + 1) / 2; mArrSelfShadowVertices.Add(idx, sv); } } // Switch scanXmin as scanXmax for next iteration. scanXmax = scanXmin; } mBufVertices.Position(0); mBufColors.Position(0); if (DRAW_TEXTURE) { mBufTexCoords.Position(0); } // Add shadow Vertices. if (DRAW_SHADOW) { mBufShadowColors.Position(0); mBufShadowVertices.Position(0); mDropShadowCount = 0; for (int i = 0; i < mArrDropShadowVertices.Size(); ++i) { ShadowVertex sv = mArrDropShadowVertices.Get(i); mBufShadowVertices.Put((float)sv.mPosX); mBufShadowVertices.Put((float)sv.mPosY); mBufShadowVertices.Put((float)sv.mPosZ); mBufShadowVertices.Put((float)(sv.mPosX + sv.mPenumbraX)); mBufShadowVertices.Put((float)(sv.mPosY + sv.mPenumbraY)); mBufShadowVertices.Put((float)sv.mPosZ); for (int j = 0; j < 4; ++j) { double color = SHADOW_OUTER_COLOR[j] + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) * sv.mPenumbraColor; mBufShadowColors.Put((float)color); } mBufShadowColors.Put(SHADOW_OUTER_COLOR); mDropShadowCount += 2; } mSelfShadowCount = 0; for (int i = 0; i < mArrSelfShadowVertices.Size(); ++i) { ShadowVertex sv = mArrSelfShadowVertices.Get(i); mBufShadowVertices.Put((float)sv.mPosX); mBufShadowVertices.Put((float)sv.mPosY); mBufShadowVertices.Put((float)sv.mPosZ); mBufShadowVertices.Put((float)(sv.mPosX + sv.mPenumbraX)); mBufShadowVertices.Put((float)(sv.mPosY + sv.mPenumbraY)); mBufShadowVertices.Put((float)sv.mPosZ); for (int j = 0; j < 4; ++j) { double color = SHADOW_OUTER_COLOR[j] + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) * sv.mPenumbraColor; mBufShadowColors.Put((float)color); } mBufShadowColors.Put(SHADOW_OUTER_COLOR); mSelfShadowCount += 2; } mBufShadowColors.Position(0); mBufShadowVertices.Position(0); } }
public void DrawGeometry(Microsoft.Xna.Framework.Graphics.GraphicsDevice device, Effect effect) { //Effect.CurrentTechnique = Effect.Techniques[0]; if (SkelBones == null) { ReloadSkeleton(); } effect.Parameters["SkelBindings"].SetValue(SkelBones); lock (Bindings) { foreach (var pass in effect.CurrentTechnique.Passes) { foreach (var binding in Bindings) { if (binding.Texture != null) { var tex = binding.Texture.Get(device); effect.Parameters["MeshTex"].SetValue(tex); } else { effect.Parameters["MeshTex"].SetValue((Texture2D)null); } pass.Apply(); binding.Mesh.Draw(device); } } } //skip drawing shadows if we're drawing id if (LightPositions == null || effect.CurrentTechnique == effect.Techniques[1]) { return; } if (ShadBuf == null) { var shadVerts = new ShadowVertex[] { new ShadowVertex(new Vector3(-1, 0, -1), 25), new ShadowVertex(new Vector3(-1, 0, 1), 25), new ShadowVertex(new Vector3(1, 0, 1), 25), new ShadowVertex(new Vector3(1, 0, -1), 25), new ShadowVertex(new Vector3(-1, 0, -1), 19), new ShadowVertex(new Vector3(-1, 0, 1), 19), new ShadowVertex(new Vector3(1, 0, 1), 19), new ShadowVertex(new Vector3(1, 0, -1), 19) }; for (int i = 0; i < shadVerts.Length; i++) { shadVerts[i].Position *= 1f; } int[] shadInd = new int[] { 2, 1, 0, 2, 0, 3, 6, 5, 4, 6, 4, 7 }; ShadBuf = new VertexBuffer(device, typeof(ShadowVertex), shadVerts.Length, BufferUsage.None); ShadBuf.SetData(shadVerts); ShadIBuf = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, shadInd.Length, BufferUsage.None); ShadIBuf.SetData(shadInd); } foreach (var light in LightPositions) { //effect.Parameters["FloorHeight"].SetValue((float)(Math.Floor(Position.Y/2.95)*2.95 + 0.05)); effect.Parameters["LightPosition"].SetValue(light); var oldTech = effect.CurrentTechnique; effect.CurrentTechnique = effect.Techniques[4]; effect.CurrentTechnique.Passes[0].Apply(); device.DepthStencilState = DepthStencilState.DepthRead; device.SetVertexBuffer(ShadBuf); device.Indices = ShadIBuf; device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4); effect.CurrentTechnique = oldTech; device.DepthStencilState = DepthStencilState.Default; } DrawHeadObject(device, effect); }