/// <summary> /// Submit a billboard to be drawn. This must be done /// every frame or Draw() will do nothing. /// </summary> /// <param name="camera">Camera looking into scene.</param> /// <param name="node">BillboardNode</param> public void AddBatch(Camera camera, BillboardNode node) { // Create billboard matrix Matrix constrainedBillboard = Matrix.CreateConstrainedBillboard( Vector3.Zero, -camera.TransformedReference, Vector3.Up, null, null); // Create billboard transform Matrix transform = Matrix.CreateScale(node.Size.X, node.Size.Y, 1f) * constrainedBillboard * node.Matrix; // Calc distance between node and camera float distance = Vector3.Distance( node.TransformedBounds.Center, camera.Position); // Add to batch BillboardBatchItem batchItem = new BillboardBatchItem( distance, node.TextureKey, transform, node.LightIntensity); batch.Add(batchItem); }
/// <summary> /// Adds RMB scenery flats to block node. /// </summary> /// <param name="block">DFBlock</param> /// <param name="blockNode">BlockNode.</param> /// <param name="sceneryArchive">Scenery texture archive index.</param> private void AddRMBSceneryFlats(ref DFBlock block, BlockNode blockNode, int sceneryArchive) { // Flags TextureManager.TextureCreateFlags flags = TextureManager.TextureCreateFlags.Dilate | TextureManager.TextureCreateFlags.PreMultiplyAlpha; if (Core.GraphicsProfile == GraphicsProfile.HiDef) { flags |= TextureManager.TextureCreateFlags.MipMaps; } // Add block scenery for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { // Get scenery item DFBlock.RmbGroundScenery scenery = block.RmbBlock.FldHeader.GroundData.GroundScenery[x, y]; // Ignore 0 as this appears to be a marker/waypoint of some kind if (scenery.TextureRecord > 0) { // Load flat int textureKey; Vector2 startSize; Vector2 finalSize; if (true == LoadDaggerfallFlat( sceneryArchive, scenery.TextureRecord, flags, out textureKey, out startSize, out finalSize)) { // Calcuate position Vector3 position = new Vector3( x * tileSide, (finalSize.Y / 2) - 4, -rmbSide + y * tileSide); // Create billboard node BillboardNode billboardNode = new BillboardNode( BillboardNode.BillboardType.ClimateScenery, textureKey, finalSize); billboardNode.Position = position; blockNode.Add(billboardNode); } } } } }
/// <summary> /// Adds miscellaneous RMB flats to block node. /// </summary> /// <param name="block">DFBlock</param> /// <param name="blockNode">BlockNode.</param> private void AddRMBMiscFlats(ref DFBlock block, BlockNode blockNode) { // Iterate through all misc flat records foreach (DFBlock.RmbBlockFlatObjectRecord obj in block.RmbBlock.MiscFlatObjectRecords) { // Get flat type BillboardNode.BillboardType billboardType = GetFlatType(obj.TextureArchive); // Flags TextureManager.TextureCreateFlags flags = TextureManager.TextureCreateFlags.Dilate | TextureManager.TextureCreateFlags.PreMultiplyAlpha; if (Core.GraphicsProfile == GraphicsProfile.HiDef) { flags |= TextureManager.TextureCreateFlags.MipMaps; } // Load flat int textureKey; Vector2 startSize; Vector2 finalSize; if (true == LoadDaggerfallFlat( obj.TextureArchive, obj.TextureRecord, flags, out textureKey, out startSize, out finalSize)) { // Calcuate position Vector3 position = new Vector3( obj.XPos, -obj.YPos + (finalSize.Y / 2) - 4, -rmbSide + -obj.ZPos); // Create billboard node BillboardNode billboardNode = new BillboardNode( billboardType, textureKey, finalSize); billboardNode.Position = position; blockNode.Add(billboardNode); // Add point light node if (billboardType == BillboardNode.BillboardType.Light) { AddPointLight(position, PointLightNode.ExteriorRadius, blockNode); } } } }
/// <summary> /// Adds RDB flat to scene node. /// </summary> /// <param name="obj">RdbObject.</param> /// <param name="blockNode">BlockNode.</param> private void AddRDBFlat(DFBlock.RdbObject obj, BlockNode blockNode) { // Get flat type BillboardNode.BillboardType billboardType = GetFlatType(obj.Resources.FlatResource.TextureArchive); // Add light if needed // Load flat int textureKey; Vector2 startSize; Vector2 finalSize; if (true == LoadDaggerfallFlat( obj.Resources.FlatResource.TextureArchive, obj.Resources.FlatResource.TextureRecord, TextureManager.TextureCreateFlags.Dilate | TextureManager.TextureCreateFlags.PreMultiplyAlpha, out textureKey, out startSize, out finalSize)) { // Foliage (TEXTURE.500 and up) do not seem to use scaling // in dungeons. Revert scaling. if (obj.Resources.FlatResource.TextureArchive > 499) { finalSize = startSize; } // Calcuate position Vector3 position = new Vector3( obj.XPos, -obj.YPos, -obj.ZPos); // Create billboard node BillboardNode billboardNode = new BillboardNode( billboardType, textureKey, finalSize); billboardNode.Position = position; blockNode.Add(billboardNode); // Add point light node if (billboardType == BillboardNode.BillboardType.Light) { AddPointLight(position, PointLightNode.DungeonRadius, blockNode); } } }
/// <summary> /// Batch a billboard node for rendering. /// </summary> /// <param name="node">BillboardNode.</param> protected void BatchBillboardNode(BillboardNode node) { // Batch billboard billboardManager.AddBatch(camera, node); }
/// <summary> /// Update node. /// This method requires some clean-up and optimisation. /// Currently rebuilding matrices on every update whether node /// has changed or not. This is done for simplicity and will /// be improved later. /// </summary> /// <param name="node">SceneNode.</param> /// <param name="matrix">Cumulative Matrix.</param> /// <returns>Transformed and merged BoundingSphere.</returns> /// <param name="elapsedTime">Elapsed time since last frame.</param> private BoundingSphere UpdateNode(SceneNode node, Matrix matrix, TimeSpan elapsedTime) { Matrix cumulativeMatrix = Matrix.Identity; // Create node transforms Matrix rotationX = Matrix.CreateRotationX(node.Rotation.X); Matrix rotationY = Matrix.CreateRotationY(node.Rotation.Y); Matrix rotationZ = Matrix.CreateRotationZ(node.Rotation.Z); Matrix translation = Matrix.CreateTranslation(node.Position); // Create action transforms Matrix actionTranslation = Matrix.Identity; if (node.Action.Enabled) { // Progress actions if (node.Action.ActionState == SceneNode.ActionState.RunningForwards) { // Progress action node.Action.RunTime += elapsedTime.Milliseconds; if (node.Action.RunTime >= node.Action.Duration) { node.Action.RunTime = node.Action.Duration; node.Action.ActionState = SceneNode.ActionState.End; } } else if (node.Action.ActionState == SceneNode.ActionState.RunningBackwards) { // Progress action node.Action.RunTime -= elapsedTime.Milliseconds; if (node.Action.RunTime <= 0) { node.Action.RunTime = 0; node.Action.ActionState = SceneNode.ActionState.Start; } } float scale = (float)node.Action.RunTime / (float)node.Action.Duration; float xrot = node.Action.Rotation.X * scale; float yrot = node.Action.Rotation.Y * scale; float zrot = node.Action.Rotation.Z * scale; float xtrn = node.Action.Translation.X * scale; float ytrn = node.Action.Translation.Y * scale; float ztrn = node.Action.Translation.Z * scale; // Create action transforms Matrix actionRotationX = Matrix.CreateRotationX(xrot); Matrix actionRotationY = Matrix.CreateRotationY(yrot); Matrix actionRotationZ = Matrix.CreateRotationZ(zrot); actionTranslation = Matrix.CreateTranslation(xtrn, ytrn, ztrn); // Apply action transforms Matrix.Multiply(ref cumulativeMatrix, ref actionRotationY, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref actionRotationX, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref actionRotationZ, out cumulativeMatrix); } // Apply node transforms. // Rotation order is Y*X*Z which seems to be correct in all observed cases. Matrix.Multiply(ref cumulativeMatrix, ref rotationY, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref rotationX, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref rotationZ, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref actionTranslation, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref translation, out cumulativeMatrix); Matrix.Multiply(ref cumulativeMatrix, ref matrix, out cumulativeMatrix); // Transform bounds BoundingSphere bounds = node.LocalBounds; Vector3.Transform(ref bounds.Center, ref cumulativeMatrix, out bounds.Center); // Update child nodes foreach (SceneNode child in node.Children) { bounds = BoundingSphere.CreateMerged( bounds, UpdateNode(child, cumulativeMatrix, elapsedTime)); } // Animate a light node if (node is PointLightNode) { PointLightNode pointLightNode = (PointLightNode)node; pointLightNode.AnimTimer += elapsedTime.Milliseconds; if (pointLightNode.AnimTimer > 60) { pointLightNode.AnimTimer = 0; pointLightNode.AnimScale = MathHelper.Clamp( pointLightNode.AnimScale + (float)(rnd.NextDouble() - 0.5f) * 0.15f, 0.70f, 1.0f); } } // Calculate point lighting on a billboard node. // The light and billboard must have the same parent or // they will not be compared. // TODO: Find a better way to do this. if (node is BillboardNode) { BillboardNode billboardNode = (BillboardNode)node; billboardNode.LightIntensity = 0f; foreach (SceneNode child in node.Parent.Children) { if (child is PointLightNode) { PointLightNode pointLightNode = (PointLightNode)child; if (pointLightNode.TransformedBounds.Intersects(billboardNode.TransformedBounds)) { float distance = Vector3.Distance( pointLightNode.TransformedBounds.Center, billboardNode.TransformedBounds.Center); float attenuation = MathHelper.Clamp( 1.0f - distance / pointLightNode.Radius, 0, 1); billboardNode.LightIntensity = MathHelper.Clamp( billboardNode.LightIntensity + attenuation, 0, 1); } } } } // Store transformed bounds node.TransformedBounds = bounds; // Store cumulative matrix node.Matrix = cumulativeMatrix; return(bounds); }