protected void GenerateVertices( Vector3[] offsets, Billboard bb ) { int color = Root.Instance.ConvertColor( bb.Color ); // Texcoords Debug.Assert( bb.UseTexcoordRect || bb.TexcoordIndex < this.textureCoords.Count ); RectangleF r = bb.UseTexcoordRect ? bb.TexcoordRect : this.textureCoords[ bb.TexcoordIndex ]; if ( this.pointRendering ) { unsafe { float* posPtr = (float*)this.lockPtr.ToPointer(); int* colPtr = (int*)posPtr; // Single vertex per billboard, ignore offsets // position posPtr[ this.ptrOffset++ ] = bb.Position.x; posPtr[ this.ptrOffset++ ] = bb.Position.y; posPtr[ this.ptrOffset++ ] = bb.Position.z; colPtr[ this.ptrOffset++ ] = color; // No texture coords in point rendering } } else if ( this.allDefaultRotation || bb.Rotation == 0 ) { unsafe { float* posPtr = (float*)this.lockPtr.ToPointer(); int* colPtr = (int*)posPtr; float* texPtr = (float*)posPtr; // Left-top // Positions posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Left; texPtr[ this.ptrOffset++ ] = r.Top; // Right-top // Positions posPtr[ this.ptrOffset++ ] = offsets[ 1 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 1 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 1 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Right; texPtr[ this.ptrOffset++ ] = r.Top; // Left-bottom // Positions posPtr[ this.ptrOffset++ ] = offsets[ 2 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 2 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 2 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Left; texPtr[ this.ptrOffset++ ] = r.Bottom; // Right-bottom // Positions posPtr[ this.ptrOffset++ ] = offsets[ 3 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 3 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 3 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Right; texPtr[ this.ptrOffset++ ] = r.Bottom; } } else if ( this.rotationType == BillboardRotationType.Vertex ) { // TODO: Cache axis when billboard type is BillboardType.Point or // BillboardType.PerpendicularCommon Vector3 axis = ( offsets[ 3 ] - offsets[ 0 ] ).Cross( offsets[ 2 ] - offsets[ 1 ] ); axis.Normalize(); Quaternion rotation = Quaternion.FromAngleAxis( bb.rotationInRadians, axis ); Vector3 pt; unsafe { float* posPtr = (float*)this.lockPtr.ToPointer(); int* colPtr = (int*)posPtr; float* texPtr = (float*)posPtr; // Left-top // Positions pt = rotation * offsets[ 0 ]; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Left; texPtr[ this.ptrOffset++ ] = r.Top; // Right-top // Positions pt = rotation * offsets[ 1 ]; posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x; posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y; posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Right; texPtr[ this.ptrOffset++ ] = r.Top; // Left-bottom // Positions pt = rotation * offsets[ 2 ]; posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x; posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y; posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Left; texPtr[ this.ptrOffset++ ] = r.Bottom; // Right-bottom // Positions pt = rotation * offsets[ 3 ]; posPtr[ this.ptrOffset++ ] = pt.x + bb.Position.x; posPtr[ this.ptrOffset++ ] = pt.y + bb.Position.y; posPtr[ this.ptrOffset++ ] = pt.z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = r.Right; texPtr[ this.ptrOffset++ ] = r.Bottom; } } else { float cos_rot = Utility.Cos( bb.rotationInRadians ); float sin_rot = Utility.Sin( bb.rotationInRadians ); float width = ( r.Right - r.Left ) / 2; float height = ( r.Bottom - r.Top ) / 2; float mid_u = r.Left + width; float mid_v = r.Top + height; float cos_rot_w = cos_rot * width; float cos_rot_h = cos_rot * height; float sin_rot_w = sin_rot * width; float sin_rot_h = sin_rot * height; unsafe { float* posPtr = (float*)this.lockPtr.ToPointer(); int* colPtr = (int*)posPtr; float* texPtr = (float*)posPtr; // Left-top // Positions posPtr[ this.ptrOffset++ ] = offsets[ 0 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 0 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = mid_u - cos_rot_w + sin_rot_h; texPtr[ this.ptrOffset++ ] = mid_v - sin_rot_w - cos_rot_h; // Right-top // Positions posPtr[ this.ptrOffset++ ] = offsets[ 1 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 1 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 1 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = mid_u + cos_rot_w + sin_rot_h; texPtr[ this.ptrOffset++ ] = mid_v + sin_rot_w - cos_rot_h; // Left-bottom // Positions posPtr[ this.ptrOffset++ ] = offsets[ 2 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 2 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 2 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = mid_u - cos_rot_w - sin_rot_h; texPtr[ this.ptrOffset++ ] = mid_v - sin_rot_w + cos_rot_h; // Right-bottom // Positions posPtr[ this.ptrOffset++ ] = offsets[ 3 ].x + bb.Position.x; posPtr[ this.ptrOffset++ ] = offsets[ 3 ].y + bb.Position.y; posPtr[ this.ptrOffset++ ] = offsets[ 3 ].z + bb.Position.z; // Color colPtr[ this.ptrOffset++ ] = color; // Texture coords texPtr[ this.ptrOffset++ ] = mid_u + cos_rot_w - sin_rot_h; texPtr[ this.ptrOffset++ ] = mid_v + sin_rot_w + cos_rot_h; } } }
/// <summary> /// Generates billboard corners. /// </summary> /// <remarks>Billboard param only required for type OrientedSelf</remarks> protected virtual void GenerateBillboardAxes( ref Vector3 x, ref Vector3 y, Billboard bb ) { // If we're using accurate facing, recalculate camera direction per BB if ( this.accurateFacing && ( this.billboardType == BillboardType.Point || this.billboardType == BillboardType.OrientedCommon || this.billboardType == BillboardType.OrientedSelf ) ) { // cam -> bb direction this.camDir = bb.Position - this.camPos; this.camDir.Normalize(); } switch ( this.billboardType ) { case BillboardType.Point: if ( this.accurateFacing ) { // Point billboards will have 'up' based on but not equal to cameras y = this.camQ * Vector3.UnitY; x = this.camDir.Cross( y ); x.Normalize(); y = x.Cross( this.camDir ); // both normalised already } else { // Get camera axes for X and Y (depth is irrelevant) x = this.camQ * Vector3.UnitX; y = this.camQ * Vector3.UnitY; } break; case BillboardType.OrientedCommon: // Y-axis is common direction // X-axis is cross with camera direction y = this.commonDirection; x = this.camDir.Cross( y ); x.Normalize(); break; case BillboardType.OrientedSelf: // Y-axis is direction // X-axis is cross with camera direction // Scale direction first y = bb.Direction; x = this.camDir.Cross( y ); x.Normalize(); break; case BillboardType.PerpendicularCommon: // X-axis is up-vector cross common direction // Y-axis is common direction cross X-axis x = this.commonUpVector.Cross( this.commonDirection ); y = this.commonDirection.Cross( x ); break; case BillboardType.PerpendicularSelf: // X-axis is up-vector cross own direction // Y-axis is own direction cross X-axis x = this.commonUpVector.Cross( bb.Direction ); x.Normalize(); y = bb.Direction.Cross( x ); // both should be normalised break; } #if NOT // Default behavior is that billboards are in local node space // so orientation of camera (in world space) must be reverse-transformed // into node space to generate the axes Quaternion invTransform = parentNode.DerivedOrientation.Inverse(); Quaternion camQ = Quaternion.Zero; switch (billboardType) { case BillboardType.Point: // Get camera world axes for X and Y (depth is irrelevant) camQ = camera.DerivedOrientation; // Convert into billboard local space camQ = invTransform * camQ; x = camQ * Vector3.UnitX; y = camQ * Vector3.UnitY; break; case BillboardType.OrientedCommon: // Y-axis is common direction // X-axis is cross with camera direction y = commonDirection; y.Normalize(); // Convert into billboard local space camQ = invTransform * camQ; x = camQ * camera.DerivedDirection.Cross(y); x.Normalize(); break; case BillboardType.OrientedSelf: // Y-axis is direction // X-axis is cross with camera direction y = billboard.Direction; // Convert into billboard local space camQ = invTransform * camQ; x = camQ * camera.DerivedDirection.Cross(y); x.Normalize(); break; case BillboardType.PerpendicularCommon: // X-axis is common direction cross common up vector // Y-axis is coplanar with common direction and common up vector x = commonDirection.Cross(commonUpVector); x.Normalize(); y = x.Cross(commonDirection); y.Normalize(); break; case BillboardType.PerpendicularSelf: // X-axis is direction cross common up vector // Y-axis is coplanar with direction and common up vector x = billboard.Direction.Cross(commonUpVector); x.Normalize(); y = x.Cross(billboard.Direction); y.Normalize(); break; } #endif }
/// <summary> /// Determines whether the supplied billboard is visible in the camera or not. /// </summary> /// <param name="camera"></param> /// <param name="billboard"></param> /// <returns></returns> protected bool IsBillboardVisible( Camera camera, Billboard billboard ) { // if not culling each one, return true always if ( !this.cullIndividual ) { return true; } // get the world matrix of this billboard set this.GetWorldTransforms( this.world ); // get the center of the bounding sphere this.sphere.Center = this.world[ 0 ] * billboard.Position; // calculate the radius of the bounding sphere for the billboard if ( billboard.HasOwnDimensions ) { this.sphere.Radius = Utility.Max( billboard.Width, billboard.Height ); } else { this.sphere.Radius = Utility.Max( this.defaultParticleWidth, this.defaultParticleHeight ); } // finally, see if the sphere is visible in the camera return camera.IsObjectVisible( this.sphere ); }
internal void InjectBillboard( Billboard bb ) { // Skip if not visible (NB always true if not bounds checking individual billboards) if ( !this.IsBillboardVisible( this.currentCamera, bb ) ) { return; } if ( !this.pointRendering && ( this.billboardType == BillboardType.OrientedSelf || this.billboardType == BillboardType.PerpendicularSelf || ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) ) ) { // Have to generate axes & offsets per billboard this.GenerateBillboardAxes( ref this.camX, ref this.camY, bb ); } // If they're all the same size or we're point rendering if ( this.allDefaultSize || this.pointRendering ) { /* No per-billboard checking, just blast through. Saves us an if clause every billboard which may make a difference. */ if ( !this.pointRendering && ( this.billboardType == BillboardType.OrientedSelf || this.billboardType == BillboardType.PerpendicularSelf || ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) ) ) { this.GenerateVertexOffsets( this.leftOff, this.rightOff, this.topOff, this.bottomOff, this.defaultParticleWidth, this.defaultParticleHeight, ref this.camX, ref this.camY, this.vOffset ); } this.GenerateVertices( this.vOffset, bb ); } else // not all default size and not point rendering { Vector3[] vOwnOffset = new Vector3[ 4 ]; // If it has own dimensions, or self-oriented, gen offsets if ( this.billboardType == BillboardType.OrientedSelf || this.billboardType == BillboardType.PerpendicularSelf || bb.HasOwnDimensions || ( this.accurateFacing && this.billboardType != BillboardType.PerpendicularCommon ) ) { // Generate using own dimensions this.GenerateVertexOffsets( this.leftOff, this.rightOff, this.topOff, this.bottomOff, bb.Width, bb.Height, ref this.camX, ref this.camY, vOwnOffset ); // Create vertex data this.GenerateVertices( vOwnOffset, bb ); } else // Use default dimension, already computed before the loop, for faster creation { this.GenerateVertices( this.vOffset, bb ); } } // Increment visibles this.numVisibleBillboards++; }
protected void RemoveBillboard( Billboard bill ) { int index = this.activeBillboards.IndexOf( bill ); Debug.Assert( index >= 0, "Billboard is not in the active list" ); RemoveBillboard( index ); }
public override void UpdateRenderQueue(RenderQueue queue, List<Particle> currentParticles, bool cullIndividually) { billboardSet.CullIndividual = cullIndividually; // Update billboard set geometry billboardSet.BeginBillboards(); Billboard bb = new Billboard(); foreach (Particle p in currentParticles) { bb.Position = p.Position; if (billboardSet.BillboardType == BillboardType.OrientedSelf || billboardSet.BillboardType == BillboardType.PerpendicularSelf) { // Normalise direction vector bb.Direction = p.Direction; bb.Direction.Normalize(); } bb.Color = p.Color; bb.rotationInRadians = p.rotationInRadians; bb.HasOwnDimensions = p.HasOwnDimensions; if (bb.HasOwnDimensions) { bb.width = p.Width; bb.height = p.Height; } billboardSet.InjectBillboard(bb); } billboardSet.EndBillboards(); // Update the queue billboardSet.UpdateRenderQueue(queue); }