Common_CustomRenderers_VBNet\App_Code\SimpleRenderer3D.vb
' Copyright 2010 ESRI ' ' All rights reserved under the copyright laws of the United States ' and applicable international laws, treaties, and conventions. ' ' You may freely redistribute and use this sample code, with or ' without modification, provided you include the original copyright ' notice and use restrictions. ' ' See the use restrictions. ' Imports Microsoft.VisualBasic Imports System Namespace ESRI.ADF.Samples.Renderers ''' <summary> ''' Renderer that extrudes polygons and polylines based on a height attribute ''' and renders the elements as 2�D. ''' </summary> ''' <remarks> ''' Note that this renderer doesn't fully implement 3D rendering techniques and some ''' artifacts might occur, but instead tries to do a simplified 3D-like rendering. ''' Features being parsed into this renderer should be sent in back-to-front (top-to-bottom) ''' to prevent overlapping features. ''' </remarks> <System.Serializable> _ Public Class SimpleRenderer3D Inherits ESRI.ADF.Samples.Renderers.RendererBase #Region "Constructors" Public Sub New() End Sub #End Region #Region "Properties - FillColor, OutlineColor, Transparency, Ambiance, HeightColumnName, MaxHeight, ScaleHeight, LightDirection" Private fillColor_Renamed As System.Drawing.Color = System.Drawing.Color.White ''' <summary> ''' Fill color on each surface ''' </summary> <System.ComponentModel.Description("Fill color on each surface.")> _ Public Property FillColor() As System.Drawing.Color Get Return fillColor_Renamed End Get Set fillColor_Renamed = Value End Set End Property Private outlineColor_Renamed As System.Drawing.Color = System.Drawing.Color.Gray ''' <summary> ''' Outline color around each surface. Set to transparent or empty for no outline. ''' </summary> <System.ComponentModel.Description("Outline color around each surface. Set to transparent or empty for no outline.")> _ Public Property OutlineColor() As System.Drawing.Color Get Return outlineColor_Renamed End Get Set outlineColor_Renamed = Value End Set End Property Private transparency_Renamed As Integer = 25 ''' <summary> ''' Transparency of the fill ''' </summary> <System.ComponentModel.DefaultValue(25), System.ComponentModel.Description("Transparency of the fill")> _ Public Property Transparency() As Integer Get Return transparency_Renamed End Get Set transparency_Renamed = Value End Set End Property Private ambience_Renamed As Double = 0.25 ''' <summary> ''' Ambience of the 3D renderer. ''' 0 = no ambient light, 1 = full ambience ''' </summary> <System.ComponentModel.DefaultValue(0.25), System.ComponentModel.Description("Ambience of the 3D renderer. 0 = no ambient light, 1 = full ambience")> _ Public Property Ambience() As Double Get Return ambience_Renamed End Get Set If ambience_Renamed < 0 OrElse ambience_Renamed > 1 Then Throw New System.ArgumentOutOfRangeException("Ambience", "Ambience must be between 0 and 1") End If ambience_Renamed = Value End Set End Property Private heightColumnName_Renamed As String ''' <summary> ''' Name of the column to use for extrusion height ''' </summary> <System.ComponentModel.DefaultValue(""), System.ComponentModel.Description("Name of the column to use for extrusion height")> _ Public Property HeightColumnName() As String Get Return heightColumnName_Renamed End Get Set heightColumnName_Renamed = Value End Set End Property Public maxHeight_Renamed As Double = 50 ''' <summary> ''' Maximum feature extrusion height (in pixels) ''' </summary> <System.ComponentModel.DefaultValue(50), System.ComponentModel.Description("Maximum feature extrusion height (in pixels)")> _ Public Property MaxHeight() As Double Get Return MaxHeight End Get Set(ByVal value As Double) maxHeight_Renamed = value End Set End Property Private scaleHeight_Renamed As Double = 1.0 ''' <summary> ''' Scale factor to apply to the value specified by the feature's extrusion height column ''' </summary> <System.ComponentModel.DefaultValue(1.0), System.ComponentModel.Description("Scale factor to apply to the value specified by the feature's extrusion height column")> _ Public Property ScaleHeight() As Double Get Return scaleHeight_Renamed End Get Set(ByVal value As Double) scaleHeight_Renamed = Value End Set End Property Private lightDirection_Renamed As Double = 45 / 180 * System.Math.PI 'Light direction: north-east ''' <summary> ''' Direction of the light rays used for calculating shading. 0 is north, 90 is east, etc. ''' </summary> ''' <remarks> ''' <para>Line segments perpendicular to the rays will be the color of the symbol, and ''' the more they are facing away from the light, the darker they will look.</para> ''' North = 0;<br/> ''' East = 90;<br/> ''' South = 180;<br/> ''' West = 270;<br/> ''' North-West = 315;<br/> ''' North-East = 45;<br/> ''' </remarks> <System.ComponentModel.DefaultValue(45), System.ComponentModel.Description("Direction of the light rays used for calculating shading. 0 is north, 90 is east, etc.")> _ Public Property LightDirection() As Double Get Return lightDirection_Renamed / System.Math.PI * 180 End Get Set(ByVal value As Double) lightDirection_Renamed = Value / 180 * System.Math.PI End Set End Property #End Region #Region "IRenderer Members - GetAllSymbols, Render" ''' <summary> ''' Generates the fill symbol used to symbolize feature surfaces and adds it to the ''' collection of symbols used by the renderer. ''' </summary> ''' <param name="symbols">The list of symbols used by the renderer</param> Public Overrides Sub GetAllSymbols(ByVal symbols As System.Collections.Generic.List(Of ESRI.ArcGIS.ADF.Web.Display.Symbol.FeatureSymbol)) ' Create a fill symbol and apply the renderer's fill color, outline color, and transparency Dim simpleFillSymbol As ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleFillSymbol = New ESRI.ArcGIS.ADF.Web.Display.Symbol.SimpleFillSymbol(Me.FillColor, Me.OutlineColor, ESRI.ArcGIS.ADF.Web.Display.Symbol.PolygonFillType.Solid) simpleFillSymbol.Transparency = Me.Transparency ' Add the fill symbol to the symbols used by the renderer symbols.Add(simpleFillSymbol) End Sub ''' <summary> ''' Main part of the IRenderer interface, within which a feature encapsulating the specified DataRow is to be ''' rendered on the specified graphics surface. The geometry instance has already been transformed to screen ''' coordinate, so we don't have to worry about that here. ''' </summary> ''' <param name="row">row containing the feature's data</param> ''' <param name="graphics">GDI+ surface on which to render the feature</param> ''' <param name="geometryColumn">column containing the feature's geometry</param> Public Overrides Sub Render(ByVal row As System.Data.DataRow, ByVal graphics As System.Drawing.Graphics, ByVal geometryColumn As System.Data.DataColumn) ' Validate method input If row Is Nothing OrElse graphics Is Nothing OrElse geometryColumn Is Nothing Then Return End If ' Validate input geometry. The renderer does not support points Dim geometry As ESRI.ArcGIS.ADF.Web.Geometry.Geometry = TryCast(row(geometryColumn), ESRI.ArcGIS.ADF.Web.Geometry.Geometry) If geometry Is Nothing OrElse TypeOf geometry Is ESRI.ArcGIS.ADF.Web.Geometry.Point Then Return End If ' Initialize the extrusion height with the renderer's specified maximum height Dim extrusionHeight As Double = Me.maxHeight_Renamed ' Get the extrusion height column attribute, if possible If (Not String.IsNullOrEmpty(HeightColumnName)) AndAlso row.Table.Columns.Contains(HeightColumnName) Then Double.TryParse(row(HeightColumnName).ToString(), extrusionHeight) End If extrusionHeight *= Me.ScaleHeight ' Apply the extrusion height scale factor ' If the scaled height is beyond the maximum height, clip it to the maximum If extrusionHeight > maxHeight_Renamed Then extrusionHeight = Me.maxHeight_Renamed End If ' Draw the feature Me.drawGraphic(geometry, graphics, extrusionHeight) End Sub #End Region #Region "Rendering - drawGraphics, drawPolyline, getLineSegements, drawPolygon, drawLineSegment, LineSegment" ' Renders the specified geometry on the specified GDI+ surface with the specified extrusion height Private Sub drawGraphic(ByVal adfGeometry As ESRI.ArcGIS.ADF.Web.Geometry.Geometry, ByVal graphics As System.Drawing.Graphics, ByVal height As Double) ' Check whether the passed-in geometry is an envelope and, if so, convert it to a polygon If TypeOf adfGeometry Is ESRI.ArcGIS.ADF.Web.Geometry.Envelope Then Dim polygon As ESRI.ArcGIS.ADF.Web.Geometry.Polygon = New ESRI.ArcGIS.ADF.Web.Geometry.Polygon() polygon.Rings.Add(New ESRI.ArcGIS.ADF.Web.Geometry.Ring(TryCast(adfGeometry, ESRI.ArcGIS.ADF.Web.Geometry.Envelope))) adfGeometry = polygon End If ' Call method to draw the feature based on its geometry type If TypeOf adfGeometry Is ESRI.ArcGIS.ADF.Web.Geometry.Polygon Then drawPolygon(graphics, TryCast(adfGeometry, ESRI.ArcGIS.ADF.Web.Geometry.Polygon), height) ElseIf TypeOf adfGeometry Is ESRI.ArcGIS.ADF.Web.Geometry.Polyline Then drawPolyline(graphics, TryCast(adfGeometry, ESRI.ArcGIS.ADF.Web.Geometry.Polyline), height) End If End Sub ' Renders the specified polyline on the specified GDI+ surface with the specified extrusion height Private Sub drawPolyline(ByVal graphics As System.Drawing.Graphics, ByVal adfPolyline As ESRI.ArcGIS.ADF.Web.Geometry.Polyline, ByVal height As Double) ' Loop through the polyline's paths, rendering each For Each adfPath As ESRI.ArcGIS.ADF.Web.Geometry.Path In adfPolyline.Paths ' Loop through the path's points, rendering each line segment Dim i As Integer = 1 Do While i < adfPath.Points.Count ' Get the direction of the segment formed by the current point and the previous Dim direction As Double = getLineDirection(adfPath.Points(i - 1), adfPath.Points(i)) ' Render the line segment formed by the current point and the previous drawLineSegment(graphics, adfPath.Points(i - 1), adfPath.Points(i), height, direction, True) i += 1 Loop Next adfPath End Sub ' Converts a point collection to a set of line segments connecting the points Private Sub getLineSegments(ByVal points As ESRI.ArcGIS.ADF.Web.Geometry.PointCollection, ByRef segmentList As System.Collections.Generic.List(Of LineSegment)) Dim i As Integer = 1 Do While i < points.Count ' Create a segment from the current and previous points and add them to the passed-in list Dim segment As LineSegment = New LineSegment() segment.Start = points(i - 1) segment.End = points(i) segment.Direction = getLineDirection(segment.Start, segment.End) segmentList.Add(segment) i += 1 Loop End Sub ' Renders the specified polygon on the specified GDI+ surface with the specified extrusion height Private Sub drawPolygon(ByVal graphics As System.Drawing.Graphics, ByVal adfPolygon As ESRI.ArcGIS.ADF.Web.Geometry.Polygon, ByVal height As Double) ' Loop through the polygon's rings, drawing each For Each adfRing As ESRI.ArcGIS.ADF.Web.Geometry.Ring In adfPolygon.Rings ' Ensure orientation (important for calculating shading). Note that because we are in a ' left-handed image coordinate system, outer ring orientation is actually counter-clockwise. adfRing.CorrectSegmentOrientation() ' Get the line segments in the current ring Dim segmentList As System.Collections.Generic.List(Of LineSegment) = New System.Collections.Generic.List(Of LineSegment)(adfRing.Points.Count) getLineSegments(adfRing.Points, segmentList) ' Get the line segments in the ring's holes For Each adfHole As ESRI.ArcGIS.ADF.Web.Geometry.Hole In adfRing.Holes getLineSegments(adfHole.Points, segmentList) Next adfHole ' We implement a very simplified back-face culling mechanism. If the normal to the surface ' being rendered points upward, they must be behind other surfaces, so we draw them first. ' Lines with lowest Y (top of screen) are drawn first. segmentList.Sort() ' Sort so top line segments are rendered first ' First pass - Draw back surfaces If Me.Transparency > 0 Then ' Ignore if not see-through For Each segment As LineSegment In segmentList If System.Math.Abs(segment.Direction) > HALF_PI Then drawLineSegment(graphics, segment.Start, segment.End, height, segment.Direction, False) End If Next segment End If ' Second pass - Draw front surfaces For Each segment As LineSegment In segmentList If System.Math.Abs(segment.Direction) <= HALF_PI Then drawLineSegment(graphics, segment.Start, segment.End, height, segment.Direction, True) End If Next segment Next adfRing ' Draw the footprint Utility.FillPolygon(graphics, adfPolygon, Me.FillColor, Me.Transparency, 0, CInt(Fix(height))) End Sub ' Render the segment specified by the start and end points on the passed-in surface, extruded by the specified height Private Sub drawLineSegment(ByVal graphics As System.Drawing.Graphics, ByVal startPoint As ESRI.ArcGIS.ADF.Web.Geometry.Point, ByVal endPoint As ESRI.ArcGIS.ADF.Web.Geometry.Point, ByVal height As Double, ByVal direction As Double, ByVal fill As Boolean) Using graphicsPath As System.Drawing.Drawing2D.GraphicsPath = New System.Drawing.Drawing2D.GraphicsPath() ' Create a screen point array with the points comprising the polygon formed by extruding the segment Dim extrudedSegmentPoints As System.Drawing.Point() = New System.Drawing.Point() { New System.Drawing.Point(System.Convert.ToInt32(startPoint.X),System.Convert.ToInt32(startPoint.Y)), New System.Drawing.Point(System.Convert.ToInt32(endPoint.X),System.Convert.ToInt32(endPoint.Y)), New System.Drawing.Point(System.Convert.ToInt32(endPoint.X),System.Convert.ToInt32(endPoint.Y-height)), New System.Drawing.Point(System.Convert.ToInt32(startPoint.X),System.Convert.ToInt32(startPoint.Y-height)), New System.Drawing.Point(System.Convert.ToInt32(startPoint.X),System.Convert.ToInt32(startPoint.Y)) } ' Add the extrusion polygon to the graphics path graphicsPath.AddPolygon(extrudedSegmentPoints) ' Fill the extrusion polygon if fill is true If fill Then ' Calculate the brightness. We add PI/2 to the passed-in direction because normal to the surface is ' perpendicular to the line direction Dim brightness As Double = calculateBrightness(direction + HALF_PI) ' Calculate the color resulting from applying the brightness to the renderer's fill color Dim tempColor As System.Drawing.Color = adjustBrightness(Me.FillColor, brightness) ' Calculate the ultimate fill color by applying the renderer's transparency Dim fillColor_Renamed As System.Drawing.Color = System.Drawing.Color.FromArgb(Utility.TransparencyToAlpha(Me.Transparency), tempColor) ' Draw the fill Using solidBrush As System.Drawing.SolidBrush = New System.Drawing.SolidBrush(fillColor_Renamed) graphics.FillPath(solidBrush, graphicsPath) End Using End If ' Draw an outline on the extrusion polygon, if one is specified If Me.OutlineColor <> System.Drawing.Color.Transparent AndAlso Me.OutlineColor <> System.Drawing.Color.Empty Then Using pen As System.Drawing.Pen = New System.Drawing.Pen(Me.OutlineColor, 0.5f) graphics.DrawPath(pen, graphicsPath) End Using End If End Using End Sub ' Encapsulates a line segement composed of two Web ADF points Private Structure LineSegment Implements System.IComparable(Of LineSegment) Public Start As ESRI.ArcGIS.ADF.Web.Geometry.Point Public [End] As ESRI.ArcGIS.ADF.Web.Geometry.Point Public Direction As Double #Region "IComparable<LineSegment> Members" ''' <summary> ''' Compares the sourthern-most points of the current and passed-in line segment. ''' </summary> ''' <param name="other"></param> ''' <returns>An integer indicating the relationship between the segments' southern-most point, as follows: ''' Return value less than zero - the calling segment's southern-most point is more southerly ''' Return value equal to zero - the segments' southern-most points are equally southerly ''' Return value greater than zero - the passed-in segment's southern-most point is more southerly</returns> Public Function CompareTo(ByVal other As LineSegment) As Integer Implements System.IComparable(Of ESRI.ADF.Samples.Renderers.SimpleRenderer3D.LineSegment).CompareTo Return System.Math.Min(Me.End.Y, Me.Start.Y).CompareTo(System.Math.Min(other.End.Y, other.Start.Y)) End Function #End Region End Structure #End Region #Region "Helper Methods - adjustBrightness, calculateBrightness, getLineDirection" ' Applies the specified brightness factor to the specified color Private Function adjustBrightness(ByVal color As System.Drawing.Color, ByVal brightnessFactor As Double) As System.Drawing.Color Dim red As Integer = color.R Dim green As Integer = color.G Dim blue As Integer = color.B brightnessFactor *= (1 - Me.Ambience) red = System.Convert.ToInt32(red * (1 - brightnessFactor)) green = System.Convert.ToInt32(green * (1 - brightnessFactor)) blue = System.Convert.ToInt32(blue * (1 - brightnessFactor)) If red > 255 Then red = 255 ElseIf red < 0 Then red = 0 End If If green > 255 Then green = 255 ElseIf green < 0 Then green = 0 End If If blue > 255 Then blue = 255 ElseIf blue < 0 Then blue = 0 End If Return System.Drawing.Color.FromArgb(color.A, red, green, blue) End Function Private Const HALF_PI As Double = System.Math.PI * 0.5 ' Calculates brightness factor based on normals angle towards the light direction. Returns 0 when ' perpendicular, 1 when opposite, and -1 when same direction. Private Function calculateBrightness(ByVal angle As Double) As Double Dim diff As Double = (Me.LightDirection + HALF_PI - angle) Return System.Math.Abs(System.Math.Sin(-diff / 2)) End Function ' Calculates the direction of the line segment represented by the passed-in points Private Shared Function getLineDirection(ByVal startPoint As ESRI.ArcGIS.ADF.Web.Geometry.Point, ByVal endPoint As ESRI.ArcGIS.ADF.Web.Geometry.Point) As Double Dim dx As Double = endPoint.X - startPoint.X Dim dy As Double = endPoint.Y - startPoint.Y Return System.Math.Atan2(dy, dx) End Function #End Region End Class End Namespace