From 057b0531f5576e323e2223a044f8c32ad997b6c4 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 27 Oct 2022 15:41:08 +0200 Subject: [PATCH 001/294] Removed RC from ScenePicker, however therefore picking is now BROKEN --- .../Complete/AdvancedUI/Core/AdvancedUI.cs | 2 +- .../GeometryEditing/Core/GeometryEditing.cs | 2 +- Examples/Complete/Picking/Core/Picking.cs | 2 +- src/Engine/Core/ScenePicker.cs | 839 ++++++++---------- src/Engine/GUI/SceneInteractionHandler.cs | 2 +- 5 files changed, 369 insertions(+), 478 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs index 89d2a3332..3c3082069 100644 --- a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs +++ b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs @@ -247,7 +247,7 @@ public override void RenderAFrame() circle.GetComponent().Offsets = GuiElementPosition.CalcOffsets(AnchorPos.Middle, pos, _canvasHeight, _canvasWidth, uiInput.Size); //1.1 Check if circle is visible - PickResult newPick = _scenePicker.Pick(RC, new float2(clipPos.x, clipPos.y)).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); + PickResult newPick = _scenePicker.Pick(new float2(clipPos.x, clipPos.y)).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); if (newPick != null && uiInput.AffectedTriangles[0] == newPick.Triangle) //VISIBLE { diff --git a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs index a7368793c..aced65c8f 100644 --- a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs +++ b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs @@ -141,7 +141,7 @@ public override void RenderAFrame() { float2 pickPosClip = _pickPos * new float2(2.0f / Width, -2.0f / Height) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(RC, pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); + PickResult newPick = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); if (newPick?.Node != _currentPick?.Node) { diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 43029af33..ae8d6f985 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -123,7 +123,7 @@ public override void RenderAFrame() { float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(RC, pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z) + PickResult newPick = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); Diagnostics.Debug(newPick); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 7d86e5cc8..07f9fac82 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -25,16 +25,6 @@ public class PickResult /// public Mesh Mesh; - /// - /// The index of the triangle that was picked. - /// - public int Triangle; - - /// - /// The barycentric u, v coordinates within the picked triangle. - /// - public float U, V; - /// /// The model matrix. /// @@ -50,571 +40,472 @@ public class PickResult /// public float4x4 Projection; - // Convenience - /// - /// Gets the triangles of the picked mesh. - /// - /// - /// - /// - public void GetTriangle(out float3 a, out float3 b, out float3 c) - { - a = Mesh.Vertices[(int)Mesh.Triangles[Triangle + 0]]; - b = Mesh.Vertices[(int)Mesh.Triangles[Triangle + 1]]; - c = Mesh.MeshType == PrimitiveType.Triangles ? Mesh.Vertices[(int)Mesh.Triangles[Triangle + 2]] : float3.Zero; - } + public float3 ClipPos; + +} + +/// +/// Implements the scene picker. +/// +public class ScenePicker : Viserator +{ + private CanvasTransform _ctc; + + private bool isCtcInitialized = false; + private MinMaxRect _parentRect; + + #region State + /// + /// The picker state upon scene traversal. + /// + public class PickerState : VisitorState + { + private readonly CollapsingStateStack _canvasXForm = new(); + private readonly CollapsingStateStack _model = new(); + private readonly CollapsingStateStack _uiRect = new(); + private readonly CollapsingStateStack _cullMode = new(); /// - /// Returns the center of the picked triangle. - /// - public float3 TriangleCenter - { - get - { - GetTriangle(out var a, out var b, out var c); - return (a + b + c) / 3; - } - } - /// - /// Returns the barycentric triangle coordinates. - /// - public float3 TriangleBarycentric - { - get - { - GetTriangle(out var a, out var b, out var c); - return float3.Barycentric(a, b, c, U, V); - } - } - /// - /// Gets the normals at the picked triangle. - /// - /// - /// - /// - public void GetNormals(out float3 a, out float3 b, out float3 c) - { - a = Mesh.Normals[(int)Mesh.Triangles[Triangle + 0]]; - b = Mesh.Normals[(int)Mesh.Triangles[Triangle + 1]]; - c = Mesh.MeshType == PrimitiveType.Triangles ? Mesh.Normals[(int)Mesh.Triangles[Triangle + 2]] : float3.Zero; ; - } - /// - /// Returns the normal at the center of the picked triangle. + /// The registered model. /// - public float3 NormalCenter + public float4x4 Model { - get - { - GetNormals(out var a, out var b, out var c); - return (a + b + c) / 3; - } + set => _model.Tos = value; + get => _model.Tos; } + /// - /// Returns the barycentric normal coordinates. + /// The registered UI rectangle. /// - public float3 NormalBarycentric + public MinMaxRect UiRect { - get - { - GetNormals(out var a, out var b, out var c); - return float3.Barycentric(a, b, c, U, V); - } + set => _uiRect.Tos = value; + get => _uiRect.Tos; } + /// - /// Returns the model position. - /// - public float3 ModelPos => TriangleBarycentric; - /// - /// Returns the clipping position of the model. + /// The registered canvas transform. /// - public float3 ClipPos + public float4x4 CanvasXForm { - get - { - var mat = Projection * View * Model; - return float4x4.TransformPerspective(mat, ModelPos); - } + get => _canvasXForm.Tos; + set => _canvasXForm.Tos = value; } + /// - /// Returns the world position of the model. - /// - public float3 WorldPos => float4x4.TransformPerspective(Model, ModelPos); - /// - /// Returns the camera position. + /// The registered cull mode. /// - public float3 CameraPos + public Cull CullMode { - get - { - var mat = View * Model; - return float4x4.TransformPerspective(mat, ModelPos); - } + get => _cullMode.Tos; + set => _cullMode.Tos = value; } + /// - /// + /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. /// - public float2 UV + public PickerState() { - get - { - float2 uva = Mesh.UVs[(int)Mesh.Triangles[Triangle]]; - float2 uvb = Mesh.UVs[(int)Mesh.Triangles[Triangle + 1]]; - float2 uvc = Mesh.MeshType == PrimitiveType.Triangles ? Mesh.UVs[(int)Mesh.Triangles[Triangle + 2]] : float2.Zero; - - return float2.Barycentric(uva, uvb, uvc, U, V); - } + RegisterState(_model); + RegisterState(_uiRect); + RegisterState(_canvasXForm); + RegisterState(_cullMode); } - } + }; /// - /// Implements the scene picker. + /// The current view matrix. /// - public class ScenePicker : Viserator - { - private CanvasTransform _ctc; - private RenderContext _rc; + public float4x4 View { get; private set; } - private bool isCtcInitialized = false; - private MinMaxRect _parentRect; + /// + /// The current projection matrix. + /// + public float4x4 Projection { get; private set; } - #region State - /// - /// The picker state upon scene traversal. - /// - public class PickerState : VisitorState - { - private readonly CollapsingStateStack _canvasXForm = new(); - private readonly CollapsingStateStack _model = new(); - private readonly CollapsingStateStack _uiRect = new(); - private readonly CollapsingStateStack _cullMode = new(); - - /// - /// The registered model. - /// - public float4x4 Model - { - set => _model.Tos = value; - get => _model.Tos; - } + public float4x4 InvView => View.Invert(); - /// - /// The registered UI rectangle. - /// - public MinMaxRect UiRect - { - set => _uiRect.Tos = value; - get => _uiRect.Tos; - } + public float4x4 InvProjection => Projection.Invert(); - /// - /// The registered canvas transform. - /// - public float4x4 CanvasXForm - { - get => _canvasXForm.Tos; - set => _canvasXForm.Tos = value; - } + public Camera CurrentCamera { get; private set; } - /// - /// The registered cull mode. - /// - public Cull CullMode - { - get => _cullMode.Tos; - set => _cullMode.Tos = value; - } + #endregion - /// - /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. - /// - public PickerState() - { - RegisterState(_model); - RegisterState(_uiRect); - RegisterState(_canvasXForm); - RegisterState(_cullMode); - } - }; + /// + /// The constructor to initialize a new ScenePicker. + /// + /// The to pick from. + public ScenePicker(SceneContainer scene) + : base(scene.Children) + { + IgnoreInactiveComponents = true; + View = float4x4.Identity; + Projection = float4x4.Identity; + } - /// - /// The current view matrix. - /// - public float4x4 View { get; private set; } + /// + /// This method is called when traversal starts to initialize the traversal state. + /// + protected override void InitState() + { + base.InitState(); + State.Model = float4x4.Identity; + State.CanvasXForm = float4x4.Identity; + State.CullMode = Cull.None; // todo: set via camera visitor + } - /// - /// The current projection matrix. - /// - public float4x4 Projection { get; private set; } + /// + /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. + /// + /// + /// The pick position. + /// + public IEnumerable Pick(float2 pickPos) + { + PickPosClip = pickPos; + return Viserate(); + } - #endregion + #region Visitors - /// - /// The constructor to initialize a new ScenePicker. - /// - /// The to pick from. - public ScenePicker(SceneContainer scene) - : base(scene.Children) - { - IgnoreInactiveComponents = true; - View = float4x4.Identity; - Projection = float4x4.Identity; - } + /// + /// Set the current camera, update View & Projection matrices + /// + /// + public void UpdateCamera(Camera cam) + { + if (!cam.Active) return; - /// - /// This method is called when traversal starts to initialize the traversal state. - /// - protected override void InitState() - { - base.InitState(); - State.Model = float4x4.Identity; - State.CanvasXForm = float4x4.Identity; - State.CullMode = _rc != null ? (Cull)_rc.GetRenderState(RenderState.CullMode) : Cull.None; - } + CurrentCamera = cam; + View = CurrentNode.GetComponent().Matrix; + var viewport = cam.Viewport; + Projection = cam.GetProjectionMat((int)viewport.z, (int)viewport.w, out var _); - /// - /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. - /// - /// - /// The pick position. - /// - public IEnumerable Pick(RenderContext rc, float2 pickPos) - { - _rc = rc; - PickPosClip = pickPos; - View = _rc.View; - Projection = _rc.Projection; - return Viserate(); - } + } - #region Visitors + /// + /// Sets the state of the model matrices and UiRects. + /// + /// The CanvasTransformComponent. + [VisitMethod] + public void RenderCanvasTransform(CanvasTransform ctc) + { + _ctc = ctc; - /// - /// Sets the state of the model matrices and UiRects. - /// - /// The CanvasTransformComponent. - [VisitMethod] - public void RenderCanvasTransform(CanvasTransform ctc) + if (ctc.CanvasRenderMode == CanvasRenderMode.World) { - _ctc = ctc; - - if (ctc.CanvasRenderMode == CanvasRenderMode.World) + var newRect = new MinMaxRect { - var newRect = new MinMaxRect - { - Min = ctc.Size.Min, - Max = ctc.Size.Max - }; + Min = ctc.Size.Min, + Max = ctc.Size.Max + }; - State.CanvasXForm *= float4x4.CreateTranslation(newRect.Center.x, newRect.Center.y, 0); - State.Model *= State.CanvasXForm; + State.CanvasXForm *= float4x4.CreateTranslation(newRect.Center.x, newRect.Center.y, 0); + State.Model *= State.CanvasXForm; - _parentRect = newRect; - State.UiRect = newRect; - } - else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) - { - var invProj = float4x4.Invert(_rc.Projection); - - var frustumCorners = new float4[4]; + _parentRect = newRect; + State.UiRect = newRect; + } + else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) + { + var invProj = float4x4.Invert(Projection); - frustumCorners[0] = invProj * new float4(-1, -1, -1, 1); //nbl - frustumCorners[1] = invProj * new float4(1, -1, -1, 1); //nbr - frustumCorners[2] = invProj * new float4(-1, 1, -1, 1); //ntl - frustumCorners[3] = invProj * new float4(1, 1, -1, 1); //ntr + var frustumCorners = new float4[4]; - for (var i = 0; i < frustumCorners.Length; i++) - { - var corner = frustumCorners[i]; - corner /= corner.w; //world space frustum corners - frustumCorners[i] = corner; - } + frustumCorners[0] = invProj * new float4(-1, -1, -1, 1); //nbl + frustumCorners[1] = invProj * new float4(1, -1, -1, 1); //nbr + frustumCorners[2] = invProj * new float4(-1, 1, -1, 1); //ntl + frustumCorners[3] = invProj * new float4(1, 1, -1, 1); //ntr - var width = (frustumCorners[0] - frustumCorners[1]).Length; - var height = (frustumCorners[0] - frustumCorners[2]).Length; + for (var i = 0; i < frustumCorners.Length; i++) + { + var corner = frustumCorners[i]; + corner /= corner.w; //world space frustum corners + frustumCorners[i] = corner; + } - var zNear = frustumCorners[0].z; - var canvasPos = new float3(_rc.InvView.M14, _rc.InvView.M24, _rc.InvView.M34 + zNear); + var width = (frustumCorners[0] - frustumCorners[1]).Length; + var height = (frustumCorners[0] - frustumCorners[2]).Length; - ctc.ScreenSpaceSize = new MinMaxRect - { - Min = new float2(canvasPos.x - width / 2, canvasPos.y - height / 2), - Max = new float2(canvasPos.x + width / 2, canvasPos.y + height / 2) - }; + var zNear = frustumCorners[0].z; + var canvasPos = new float3(InvView.M14, InvView.M24, InvView.M34 + zNear); - var newRect = new MinMaxRect - { - Min = ctc.ScreenSpaceSize.Min, - Max = ctc.ScreenSpaceSize.Max - }; + ctc.ScreenSpaceSize = new MinMaxRect + { + Min = new float2(canvasPos.x - width / 2, canvasPos.y - height / 2), + Max = new float2(canvasPos.x + width / 2, canvasPos.y + height / 2) + }; - if (!isCtcInitialized) - { - ctc.Scale = new float2(ctc.Size.Size.x / ctc.ScreenSpaceSize.Size.x, - ctc.Size.Size.y / ctc.ScreenSpaceSize.Size.y); + var newRect = new MinMaxRect + { + Min = ctc.ScreenSpaceSize.Min, + Max = ctc.ScreenSpaceSize.Max + }; - _ctc = ctc; - isCtcInitialized = true; + if (!isCtcInitialized) + { + ctc.Scale = new float2(ctc.Size.Size.x / ctc.ScreenSpaceSize.Size.x, + ctc.Size.Size.y / ctc.ScreenSpaceSize.Size.y); - } - State.CanvasXForm *= _rc.InvModel * _rc.InvView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); - State.Model *= State.CanvasXForm; + _ctc = ctc; + isCtcInitialized = true; - _parentRect = newRect; - State.UiRect = newRect; } + State.CanvasXForm *= State.Model.Invert() * InvView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); + State.Model *= State.CanvasXForm; + + _parentRect = newRect; + State.UiRect = newRect; } + } - /// - /// If a RectTransformComponent is visited the model matrix and MinMaxRect get updated in the . - /// - /// The XFormComponent. - [VisitMethod] - public void RenderRectTransform(RectTransform rtc) + /// + /// If a RectTransformComponent is visited the model matrix and MinMaxRect get updated in the . + /// + /// The XFormComponent. + [VisitMethod] + public void RenderRectTransform(RectTransform rtc) + { + MinMaxRect newRect; + if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) { - MinMaxRect newRect; - if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) + newRect = new MinMaxRect { - newRect = new MinMaxRect - { - Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + (rtc.Offsets.Min / _ctc.Scale.x), - Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + (rtc.Offsets.Max / _ctc.Scale.y) - }; - } - else + Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + (rtc.Offsets.Min / _ctc.Scale.x), + Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + (rtc.Offsets.Max / _ctc.Scale.y) + }; + } + else + { + // The Heart of the UiRect calculation: Set anchor points relative to parent + // rectangle and add absolute offsets + newRect = new MinMaxRect { - // The Heart of the UiRect calculation: Set anchor points relative to parent - // rectangle and add absolute offsets - newRect = new MinMaxRect - { - Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + rtc.Offsets.Min, - Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + rtc.Offsets.Max - }; - } + Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + rtc.Offsets.Min, + Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + rtc.Offsets.Max + }; + } - var translationDelta = newRect.Center - State.UiRect.Center; - var translationX = translationDelta.x / State.UiRect.Size.x; - var translationY = translationDelta.y / State.UiRect.Size.y; + var translationDelta = newRect.Center - State.UiRect.Center; + var translationX = translationDelta.x / State.UiRect.Size.x; + var translationY = translationDelta.y / State.UiRect.Size.y; - _parentRect = State.UiRect; - State.UiRect = newRect; + _parentRect = State.UiRect; + State.UiRect = newRect; - State.Model *= float4x4.CreateTranslation(translationX, translationY, 0); - } + State.Model *= float4x4.CreateTranslation(translationX, translationY, 0); + } - /// - /// If a XFormComponent is visited the model matrix gets updated in the and set in the . - /// - /// The XFormComponent. - [VisitMethod] - public void RenderXForm(XForm xfc) + /// + /// If a XFormComponent is visited the model matrix gets updated in the and set in the . + /// + /// The XFormComponent. + [VisitMethod] + public void RenderXForm(XForm xfc) + { + float4x4 scale; + + if (State.UiRect.Size != _parentRect.Size) + { + var scaleX = State.UiRect.Size.x / _parentRect.Size.x; + var scaleY = State.UiRect.Size.y / _parentRect.Size.y; + scale = float4x4.CreateScale(scaleX, scaleY, 1); + } + else if (State.UiRect.Size == _parentRect.Size && xfc.Name.Contains("Canvas")) { - float4x4 scale; + scale = float4x4.CreateScale(State.UiRect.Size.x, State.UiRect.Size.y, 1); + } + else + { + scale = float4x4.CreateScale(1, 1, 1); + } - if (State.UiRect.Size != _parentRect.Size) - { - var scaleX = State.UiRect.Size.x / _parentRect.Size.x; - var scaleY = State.UiRect.Size.y / _parentRect.Size.y; - scale = float4x4.CreateScale(scaleX, scaleY, 1); - } - else if (State.UiRect.Size == _parentRect.Size && xfc.Name.Contains("Canvas")) - { - scale = float4x4.CreateScale(State.UiRect.Size.x, State.UiRect.Size.y, 1); - } - else - { - scale = float4x4.CreateScale(1, 1, 1); - } + State.Model *= scale; + } - State.Model *= scale; - _rc.Model = State.Model; - } + /// + /// If a XFormTextComponent is visited the model matrix gets updated in the and set in the . + /// + /// The XFormTextComponent. + [VisitMethod] + public void RenderXFormText(XFormText xfc) + { + var zNear = (InvProjection * new float4(-1, -1, -1, 1)).z; + var scaleFactor = zNear / 100; + var invScaleFactor = 1 / scaleFactor; - /// - /// If a XFormTextComponent is visited the model matrix gets updated in the and set in the . - /// - /// The XFormTextComponent. - [VisitMethod] - public void RenderXFormText(XFormText xfc) - { - var zNear = (_rc.InvProjection * new float4(-1, -1, -1, 1)).z; - var scaleFactor = zNear / 100; - var invScaleFactor = 1 / scaleFactor; + float translationY; + float translationX; - float translationY; - float translationX; + float scaleX; + float scaleY; - float scaleX; - float scaleY; + if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) + { + //Undo parent scale + scaleX = 1 / State.UiRect.Size.x; + scaleY = 1 / State.UiRect.Size.y; - if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) + //Calculate translation according to alignment + translationX = xfc.HorizontalAlignment switch { - //Undo parent scale - scaleX = 1 / State.UiRect.Size.x; - scaleY = 1 / State.UiRect.Size.y; - - //Calculate translation according to alignment - translationX = xfc.HorizontalAlignment switch - { - HorizontalTextAlignment.Left => -State.UiRect.Size.x / 2, - HorizontalTextAlignment.Center => -xfc.Width / 2, - HorizontalTextAlignment.Right => State.UiRect.Size.x / 2 - xfc.Width, - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - translationY = xfc.VerticalAlignment switch - { - VerticalTextAlignment.Top => State.UiRect.Size.y / 2, - VerticalTextAlignment.Center => xfc.Height / 2, - VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y / 2), - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - } - else + HorizontalTextAlignment.Left => -State.UiRect.Size.x / 2, + HorizontalTextAlignment.Center => -xfc.Width / 2, + HorizontalTextAlignment.Right => State.UiRect.Size.x / 2 - xfc.Width, + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + translationY = xfc.VerticalAlignment switch { - //Undo parent scale, scale by distance - scaleX = 1 / State.UiRect.Size.x * scaleFactor; - scaleY = 1 / State.UiRect.Size.y * scaleFactor; + VerticalTextAlignment.Top => State.UiRect.Size.y / 2, + VerticalTextAlignment.Center => xfc.Height / 2, + VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y / 2), + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + } + else + { + //Undo parent scale, scale by distance + scaleX = 1 / State.UiRect.Size.x * scaleFactor; + scaleY = 1 / State.UiRect.Size.y * scaleFactor; - //Calculate translation according to alignment by scaling the rectangle size - translationX = xfc.HorizontalAlignment switch - { - HorizontalTextAlignment.Left => -State.UiRect.Size.x * invScaleFactor / 2, - HorizontalTextAlignment.Center => -xfc.Width / 2, - HorizontalTextAlignment.Right => State.UiRect.Size.x * invScaleFactor / 2 - xfc.Width, - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - translationY = xfc.VerticalAlignment switch - { - VerticalTextAlignment.Top => State.UiRect.Size.y * invScaleFactor / 2, - VerticalTextAlignment.Center => xfc.Height / 2, - VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y * invScaleFactor / 2), - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - } + //Calculate translation according to alignment by scaling the rectangle size + translationX = xfc.HorizontalAlignment switch + { + HorizontalTextAlignment.Left => -State.UiRect.Size.x * invScaleFactor / 2, + HorizontalTextAlignment.Center => -xfc.Width / 2, + HorizontalTextAlignment.Right => State.UiRect.Size.x * invScaleFactor / 2 - xfc.Width, + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + translationY = xfc.VerticalAlignment switch + { + VerticalTextAlignment.Top => State.UiRect.Size.y * invScaleFactor / 2, + VerticalTextAlignment.Center => xfc.Height / 2, + VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y * invScaleFactor / 2), + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + } - var translation = float4x4.CreateTranslation(translationX, translationY, 0); - var scale = float4x4.CreateScale(scaleX, scaleY, 1); + var translation = float4x4.CreateTranslation(translationX, translationY, 0); + var scale = float4x4.CreateScale(scaleX, scaleY, 1); - State.Model *= scale; - State.Model *= translation; - _rc.Model = State.Model; - } + State.Model *= scale; + State.Model *= translation; + } - /// - /// If a TransformComponent is visited the model matrix of the and is updated. - /// It additionally updates the view matrix of the RenderContext. - /// - /// The TransformComponent. - [VisitMethod] - public void RenderTransform(Transform transform) - { - State.Model *= transform.Matrix; - _rc.Model = State.Model; - } + /// + /// If a TransformComponent is visited the model matrix of the and is updated. + /// It additionally updates the view matrix of the RenderContext. + /// + /// The TransformComponent. + [VisitMethod] + public void RenderTransform(Transform transform) + { + State.Model *= transform.Matrix; + } - /// - /// Creates pick results from a given mesh if it is within the pick position. - /// - /// The given Mesh. - [VisitMethod] - public void PickMesh(Mesh mesh) - { - if (!mesh.Active) return; + /// + /// Creates pick results from a given mesh if it is within the pick position. + /// + /// The given Mesh. + [VisitMethod] + public void PickMesh(Mesh mesh) + { + if (!mesh.Active) return; - var mvp = Projection * View * State.Model; + var mvp = Projection * View * State.Model; - if (mesh.MeshType == PrimitiveType.Triangles || - mesh.MeshType == PrimitiveType.TriangleFan || - mesh.MeshType == PrimitiveType.TriangleStrip) + if (mesh.MeshType == PrimitiveType.Triangles || + mesh.MeshType == PrimitiveType.TriangleFan || + mesh.MeshType == PrimitiveType.TriangleStrip) + { + for (var i = 0; i < mesh.Triangles.Length; i += 3) { - for (var i = 0; i < mesh.Triangles.Length; i += 3) - { - // a, b c: current triangle's vertices in clip coordinates - var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - a = float4x4.TransformPerspective(mvp, a); + // a, b c: current triangle's vertices in clip coordinates + var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); + a = float4x4.TransformPerspective(mvp, a); - var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - b = float4x4.TransformPerspective(mvp, b); + var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); + b = float4x4.TransformPerspective(mvp, b); - var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - c = float4x4.TransformPerspective(mvp, c); + var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); + c = float4x4.TransformPerspective(mvp, c); - // Point-in-Triangle-Test - if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) - { - var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + // Point-in-Triangle-Test + if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) + { + var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); - if (pickPos.z >= -1 && pickPos.z <= 1) + if (pickPos.z >= -1 && pickPos.z <= 1) + { + if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) { - if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) + YieldItem(new PickResult { - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Triangle = i, - Model = State.Model, - View = View, - Projection = Projection, - U = u, - V = v - }); - } + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + View = View, + ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + Projection = Projection + }); } } } } - else if (mesh.MeshType == PrimitiveType.Lines) + } + else if (mesh.MeshType == PrimitiveType.Lines) + { + var matOfNode = CurrentNode.GetComponent(); + if (matOfNode == null) { - var matOfNode = CurrentNode.GetComponent(); - if (matOfNode == null) - { - Diagnostics.Warn("No shader effect for line renderer found!"); - return; - } - var thicknessFromShader = matOfNode.GetFxParam("Thickness"); + Diagnostics.Warn("No shader effect for line renderer found!"); + return; + } + var thicknessFromShader = matOfNode.GetFxParam("Thickness"); - for (var i = 0; i < mesh.Triangles.Length; i += 2) - { - var thickness = (thicknessFromShader / _rc.ViewportHeight); + for (var i = 0; i < mesh.Triangles.Length; i += 2) + { + var viewportHeight = CurrentCamera.Viewport.w; + var thickness = (thicknessFromShader / viewportHeight); - var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; - var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = PickPosClip; + var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; + var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; + var pt0 = PickPosClip; - // Line Eq = ax + by + c = 0 - // A = (y1 - y2) - // B = (x2 - x1) - // C = (x1 * y2 - x2 * y1) + // Line Eq = ax + by + c = 0 + // A = (y1 - y2) + // B = (x2 - x1) + // C = (x1 * y2 - x2 * y1) - // dist(line, pt) = |Ax + By + C| / A² + B² - var a = pt1.y - pt2.y; - var b = pt2.x - pt1.x; - var c = pt1.x * pt2.y - pt2.x * pt1.y; + // dist(line, pt) = |Ax + By + C| / A² + B² + var a = pt1.y - pt2.y; + var b = pt2.x - pt1.x; + var c = pt1.x * pt2.y - pt2.x * pt1.y; - var d = (MathF.Abs(a * pt0.x + b * pt0.y + c) / (a * a + b * b)); + var d = (MathF.Abs(a * pt0.x + b * pt0.y + c) / (a * a + b * b)); - if (d <= thickness) + if (d <= thickness) + { + YieldItem(new PickResult { - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Triangle = i, - Model = State.Model, - View = View, - Projection = Projection - }); - } + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + View = View, + Projection = Projection + }); } } } + } - /// - /// The pick position on the screen. - /// - public float2 PickPosClip { get; set; } + /// + /// The pick position on the screen. + /// + public float2 PickPosClip { get; set; } - #endregion + #endregion - } +} } \ No newline at end of file diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index 718774aa1..78c2b1dbc 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -64,7 +64,7 @@ public void CheckForInteractiveObjects(RenderContext rc, float2 mousePos, int ca { var pickPosClip = mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight) + new float2(-1, 1); - var pickResults = _scenePicker.Pick(rc, pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); + var pickResults = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); var pickResNodes = pickResults.Select(x => x.Node).ToList(); var firstPickRes = pickResults.FirstOrDefault(); From 420ec2153a7b8e77784db5262f95b9145be95e29 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 3 Nov 2022 11:39:29 +0100 Subject: [PATCH 002/294] Fixed picking without RC TODO: How to update width/height of Projection after scaling the window? --- .../Complete/AdvancedUI/Core/AdvancedUI.cs | 2 +- .../GeometryEditing/Core/GeometryEditing.cs | 2 +- Examples/Complete/Picking/Core/Picking.cs | 30 +- src/Engine/Core/ScenePicker.cs | 772 +++++++++--------- src/Engine/GUI/SceneInteractionHandler.cs | 4 +- 5 files changed, 425 insertions(+), 385 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs index 3c3082069..63c41dcd2 100644 --- a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs +++ b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs @@ -160,7 +160,7 @@ public override void Init() _sih = new SceneInteractionHandler(_gui); //Create a scene picker for performing visibility tests - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); diff --git a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs index aced65c8f..c85342154 100644 --- a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs +++ b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs @@ -120,7 +120,7 @@ public override void Init() _scene = new SceneContainer { Children = new List { _parentNode } }; _renderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); _activeGeometrys = new Dictionary(); } diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index ae8d6f985..7659e0e6a 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -32,9 +32,9 @@ public class Picking : RenderCanvas private const float ZFar = 1000; private readonly float _fovy = M.PiOver4; - private SceneRendererForward _guiRenderer; - private SceneContainer _gui; - private SceneInteractionHandler _sih; + //private SceneRendererForward _guiRenderer; + //private SceneContainer _gui; + //private SceneInteractionHandler _sih; private PickResult _currentPick; private float4 _oldColor; @@ -48,12 +48,12 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); - _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); + //_gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); - _guiRenderer = new SceneRendererForward(_gui); + //_sih = new SceneInteractionHandler(_gui); + // _guiRenderer = new SceneRendererForward(_gui); } public override async Task InitAsync() @@ -148,16 +148,16 @@ public override void RenderAFrame() _pick = false; } - _guiRenderer.Render(RC); + //_guiRenderer.Render(RC); // Constantly check for interactive objects. - if (!Input.Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Input.Mouse.Position, Width, Height); - - if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) - { - _sih.CheckForInteractiveObjects(RC, Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); - } + // if (!Input.Mouse.Desc.Contains("Android")) + // _sih.CheckForInteractiveObjects(RC, Input.Mouse.Position, Width, Height); + // + // if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) + // { + // _sih.CheckForInteractiveObjects(RC, Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + // } // Swap buffers: Show the contents of the back buffer (containing the currently rendered frame) on the front buffer. Present(); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 07f9fac82..bb9eeae1e 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -18,12 +18,12 @@ public class PickResult /// /// The scene code container. /// - public SceneNode Node; + public SceneNode? Node; /// /// The mesh. /// - public Mesh Mesh; + public Mesh? Mesh; /// /// The model matrix. @@ -40,472 +40,512 @@ public class PickResult /// public float4x4 Projection; + /// + /// The clip position + /// public float3 ClipPos; -} - -/// -/// Implements the scene picker. -/// -public class ScenePicker : Viserator -{ - private CanvasTransform _ctc; - - private bool isCtcInitialized = false; - private MinMaxRect _parentRect; + } - #region State /// - /// The picker state upon scene traversal. + /// Implements the scene picker. /// - public class PickerState : VisitorState + public class ScenePicker : Viserator { - private readonly CollapsingStateStack _canvasXForm = new(); - private readonly CollapsingStateStack _model = new(); - private readonly CollapsingStateStack _uiRect = new(); - private readonly CollapsingStateStack _cullMode = new(); + private CanvasTransform? _ctc; + private bool isCtcInitialized = false; + private MinMaxRect _parentRect; + + #region State /// - /// The registered model. + /// The picker state upon scene traversal. /// - public float4x4 Model + public class PickerState : VisitorState { - set => _model.Tos = value; - get => _model.Tos; - } + private readonly CollapsingStateStack _canvasXForm = new(); + private readonly CollapsingStateStack _model = new(); + private readonly CollapsingStateStack _uiRect = new(); + private readonly CollapsingStateStack _cullMode = new(); + + /// + /// The registered model. + /// + public float4x4 Model + { + set => _model.Tos = value; + get => _model.Tos; + } + + /// + /// The registered UI rectangle. + /// + public MinMaxRect UiRect + { + set => _uiRect.Tos = value; + get => _uiRect.Tos; + } + + /// + /// The registered canvas transform. + /// + public float4x4 CanvasXForm + { + get => _canvasXForm.Tos; + set => _canvasXForm.Tos = value; + } + + /// + /// The registered cull mode. + /// + public Cull CullMode + { + get => _cullMode.Tos; + set => _cullMode.Tos = value; + } + + /// + /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. + /// + public PickerState() + { + RegisterState(_model); + RegisterState(_uiRect); + RegisterState(_canvasXForm); + RegisterState(_cullMode); + } + }; /// - /// The registered UI rectangle. + /// The current view matrix. /// - public MinMaxRect UiRect - { - set => _uiRect.Tos = value; - get => _uiRect.Tos; - } + public float4x4 View { get; private set; } + + /// + /// The current projection matrix. + /// + public float4x4 Projection { get; private set; } + + public float4x4 InvView => View.Invert(); + + public float4x4 InvProjection => Projection.Invert(); + + public Camera? CurrentCamera { get; private set; } + + #endregion + + private int _canvasWidth; + private int _canvasHeight; /// - /// The registered canvas transform. + /// The constructor to initialize a new ScenePicker. /// - public float4x4 CanvasXForm + /// The to pick from. + public ScenePicker(SceneContainer scene, int canvasWidth, int canvasHeight) + : base(scene.Children) { - get => _canvasXForm.Tos; - set => _canvasXForm.Tos = value; + IgnoreInactiveComponents = true; + View = float4x4.Identity; + Projection = float4x4.Identity; + _canvasWidth = canvasWidth; + _canvasHeight = canvasHeight; } /// - /// The registered cull mode. + /// This method is called when traversal starts to initialize the traversal state. /// - public Cull CullMode + protected override void InitState() { - get => _cullMode.Tos; - set => _cullMode.Tos = value; + base.InitState(); + State.Model = float4x4.Identity; + State.CanvasXForm = float4x4.Identity; + State.CullMode = Cull.None; // todo: set via camera visitor } /// - /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. + /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// - public PickerState() + /// The pick position. + /// + public IEnumerable Pick(float2 pickPos) { - RegisterState(_model); - RegisterState(_uiRect); - RegisterState(_canvasXForm); - RegisterState(_cullMode); + PickPosClip = pickPos; + return Viserate(); } - }; - /// - /// The current view matrix. - /// - public float4x4 View { get; private set; } + #region Visitors - /// - /// The current projection matrix. - /// - public float4x4 Projection { get; private set; } - - public float4x4 InvView => View.Invert(); - - public float4x4 InvProjection => Projection.Invert(); - - public Camera CurrentCamera { get; private set; } + /// + /// Set the current camera, update View and Projection matrices + /// + /// + [VisitMethod] + public void UpdateCamera(Camera cam) + { + if (!cam.Active) return; - #endregion + CurrentCamera = cam; - /// - /// The constructor to initialize a new ScenePicker. - /// - /// The to pick from. - public ScenePicker(SceneContainer scene) - : base(scene.Children) - { - IgnoreInactiveComponents = true; - View = float4x4.Identity; - Projection = float4x4.Identity; - } + var view = State.Model; + var scale = float4x4.GetScale(View); - /// - /// This method is called when traversal starts to initialize the traversal state. - /// - protected override void InitState() - { - base.InitState(); - State.Model = float4x4.Identity; - State.CanvasXForm = float4x4.Identity; - State.CullMode = Cull.None; // todo: set via camera visitor - } + if (scale.x != 1) + { + view.M11 /= scale.x; + view.M21 /= scale.x; + view.M31 /= scale.x; + } - /// - /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. - /// - /// - /// The pick position. - /// - public IEnumerable Pick(float2 pickPos) - { - PickPosClip = pickPos; - return Viserate(); - } + if (scale.y != 1) + { + view.M12 /= scale.y; + view.M22 /= scale.y; + view.M32 /= scale.y; + } - #region Visitors + if (scale.z != 1) + { + view.M13 /= scale.z; + view.M23 /= scale.z; + view.M33 /= scale.z; + } - /// - /// Set the current camera, update View & Projection matrices - /// - /// - public void UpdateCamera(Camera cam) - { - if (!cam.Active) return; + View = view.Invert(); + Projection = cam.GetProjectionMat((int)_canvasWidth, (int)_canvasHeight, out var _); + } - CurrentCamera = cam; - View = CurrentNode.GetComponent().Matrix; - var viewport = cam.Viewport; - Projection = cam.GetProjectionMat((int)viewport.z, (int)viewport.w, out var _); + /// + /// Sets the state of the model matrices and UiRects. + /// + /// The CanvasTransformComponent. + [VisitMethod] + public void RenderCanvasTransform(CanvasTransform ctc) + { + _ctc = ctc; - } + if (ctc.CanvasRenderMode == CanvasRenderMode.World) + { + var newRect = new MinMaxRect + { + Min = ctc.Size.Min, + Max = ctc.Size.Max + }; - /// - /// Sets the state of the model matrices and UiRects. - /// - /// The CanvasTransformComponent. - [VisitMethod] - public void RenderCanvasTransform(CanvasTransform ctc) - { - _ctc = ctc; + State.CanvasXForm *= float4x4.CreateTranslation(newRect.Center.x, newRect.Center.y, 0); + State.Model *= State.CanvasXForm; - if (ctc.CanvasRenderMode == CanvasRenderMode.World) - { - var newRect = new MinMaxRect + _parentRect = newRect; + State.UiRect = newRect; + } + else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) { - Min = ctc.Size.Min, - Max = ctc.Size.Max - }; + var invProj = float4x4.Invert(Projection); - State.CanvasXForm *= float4x4.CreateTranslation(newRect.Center.x, newRect.Center.y, 0); - State.Model *= State.CanvasXForm; + var frustumCorners = new float4[4]; - _parentRect = newRect; - State.UiRect = newRect; - } - else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) - { - var invProj = float4x4.Invert(Projection); + frustumCorners[0] = invProj * new float4(-1, -1, -1, 1); //nbl + frustumCorners[1] = invProj * new float4(1, -1, -1, 1); //nbr + frustumCorners[2] = invProj * new float4(-1, 1, -1, 1); //ntl + frustumCorners[3] = invProj * new float4(1, 1, -1, 1); //ntr - var frustumCorners = new float4[4]; + for (var i = 0; i < frustumCorners.Length; i++) + { + var corner = frustumCorners[i]; + corner /= corner.w; //world space frustum corners + frustumCorners[i] = corner; + } - frustumCorners[0] = invProj * new float4(-1, -1, -1, 1); //nbl - frustumCorners[1] = invProj * new float4(1, -1, -1, 1); //nbr - frustumCorners[2] = invProj * new float4(-1, 1, -1, 1); //ntl - frustumCorners[3] = invProj * new float4(1, 1, -1, 1); //ntr + var width = (frustumCorners[0] - frustumCorners[1]).Length; + var height = (frustumCorners[0] - frustumCorners[2]).Length; - for (var i = 0; i < frustumCorners.Length; i++) - { - var corner = frustumCorners[i]; - corner /= corner.w; //world space frustum corners - frustumCorners[i] = corner; - } - - var width = (frustumCorners[0] - frustumCorners[1]).Length; - var height = (frustumCorners[0] - frustumCorners[2]).Length; + var zNear = frustumCorners[0].z; + var canvasPos = new float3(InvView.M14, InvView.M24, InvView.M34 + zNear); - var zNear = frustumCorners[0].z; - var canvasPos = new float3(InvView.M14, InvView.M24, InvView.M34 + zNear); + ctc.ScreenSpaceSize = new MinMaxRect + { + Min = new float2(canvasPos.x - width / 2, canvasPos.y - height / 2), + Max = new float2(canvasPos.x + width / 2, canvasPos.y + height / 2) + }; - ctc.ScreenSpaceSize = new MinMaxRect - { - Min = new float2(canvasPos.x - width / 2, canvasPos.y - height / 2), - Max = new float2(canvasPos.x + width / 2, canvasPos.y + height / 2) - }; + var newRect = new MinMaxRect + { + Min = ctc.ScreenSpaceSize.Min, + Max = ctc.ScreenSpaceSize.Max + }; - var newRect = new MinMaxRect - { - Min = ctc.ScreenSpaceSize.Min, - Max = ctc.ScreenSpaceSize.Max - }; + if (!isCtcInitialized) + { + ctc.Scale = new float2(ctc.Size.Size.x / ctc.ScreenSpaceSize.Size.x, + ctc.Size.Size.y / ctc.ScreenSpaceSize.Size.y); - if (!isCtcInitialized) - { - ctc.Scale = new float2(ctc.Size.Size.x / ctc.ScreenSpaceSize.Size.x, - ctc.Size.Size.y / ctc.ScreenSpaceSize.Size.y); + _ctc = ctc; + isCtcInitialized = true; - _ctc = ctc; - isCtcInitialized = true; + } + State.CanvasXForm *= State.Model.Invert() * InvView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); + State.Model *= State.CanvasXForm; + _parentRect = newRect; + State.UiRect = newRect; } - State.CanvasXForm *= State.Model.Invert() * InvView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); - State.Model *= State.CanvasXForm; - - _parentRect = newRect; - State.UiRect = newRect; } - } - /// - /// If a RectTransformComponent is visited the model matrix and MinMaxRect get updated in the . - /// - /// The XFormComponent. - [VisitMethod] - public void RenderRectTransform(RectTransform rtc) - { - MinMaxRect newRect; - if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) + /// + /// If a RectTransformComponent is visited the model matrix and MinMaxRect get updated in the . + /// + /// The XFormComponent. + [VisitMethod] + public void RenderRectTransform(RectTransform rtc) { - newRect = new MinMaxRect + MinMaxRect newRect; + if (_ctc?.CanvasRenderMode == CanvasRenderMode.Screen) { - Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + (rtc.Offsets.Min / _ctc.Scale.x), - Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + (rtc.Offsets.Max / _ctc.Scale.y) - }; - } - else - { - // The Heart of the UiRect calculation: Set anchor points relative to parent - // rectangle and add absolute offsets - newRect = new MinMaxRect + newRect = new MinMaxRect + { + Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + (rtc.Offsets.Min / _ctc.Scale.x), + Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + (rtc.Offsets.Max / _ctc.Scale.y) + }; + } + else { - Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + rtc.Offsets.Min, - Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + rtc.Offsets.Max - }; - } - - var translationDelta = newRect.Center - State.UiRect.Center; - var translationX = translationDelta.x / State.UiRect.Size.x; - var translationY = translationDelta.y / State.UiRect.Size.y; - - _parentRect = State.UiRect; - State.UiRect = newRect; + // The Heart of the UiRect calculation: Set anchor points relative to parent + // rectangle and add absolute offsets + newRect = new MinMaxRect + { + Min = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Min + rtc.Offsets.Min, + Max = State.UiRect.Min + State.UiRect.Size * rtc.Anchors.Max + rtc.Offsets.Max + }; + } - State.Model *= float4x4.CreateTranslation(translationX, translationY, 0); - } + var translationDelta = newRect.Center - State.UiRect.Center; + var translationX = translationDelta.x / State.UiRect.Size.x; + var translationY = translationDelta.y / State.UiRect.Size.y; - /// - /// If a XFormComponent is visited the model matrix gets updated in the and set in the . - /// - /// The XFormComponent. - [VisitMethod] - public void RenderXForm(XForm xfc) - { - float4x4 scale; + _parentRect = State.UiRect; + State.UiRect = newRect; - if (State.UiRect.Size != _parentRect.Size) - { - var scaleX = State.UiRect.Size.x / _parentRect.Size.x; - var scaleY = State.UiRect.Size.y / _parentRect.Size.y; - scale = float4x4.CreateScale(scaleX, scaleY, 1); - } - else if (State.UiRect.Size == _parentRect.Size && xfc.Name.Contains("Canvas")) - { - scale = float4x4.CreateScale(State.UiRect.Size.x, State.UiRect.Size.y, 1); + State.Model *= float4x4.CreateTranslation(translationX, translationY, 0); } - else - { - scale = float4x4.CreateScale(1, 1, 1); - } - - State.Model *= scale; - } - - /// - /// If a XFormTextComponent is visited the model matrix gets updated in the and set in the . - /// - /// The XFormTextComponent. - [VisitMethod] - public void RenderXFormText(XFormText xfc) - { - var zNear = (InvProjection * new float4(-1, -1, -1, 1)).z; - var scaleFactor = zNear / 100; - var invScaleFactor = 1 / scaleFactor; - - float translationY; - float translationX; - float scaleX; - float scaleY; - - if (_ctc.CanvasRenderMode == CanvasRenderMode.Screen) + /// + /// If a XFormComponent is visited the model matrix gets updated in the and set in the . + /// + /// The XFormComponent. + [VisitMethod] + public void RenderXForm(XForm xfc) { - //Undo parent scale - scaleX = 1 / State.UiRect.Size.x; - scaleY = 1 / State.UiRect.Size.y; + float4x4 scale; - //Calculate translation according to alignment - translationX = xfc.HorizontalAlignment switch + if (State.UiRect.Size != _parentRect.Size) { - HorizontalTextAlignment.Left => -State.UiRect.Size.x / 2, - HorizontalTextAlignment.Center => -xfc.Width / 2, - HorizontalTextAlignment.Right => State.UiRect.Size.x / 2 - xfc.Width, - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - translationY = xfc.VerticalAlignment switch + var scaleX = State.UiRect.Size.x / _parentRect.Size.x; + var scaleY = State.UiRect.Size.y / _parentRect.Size.y; + scale = float4x4.CreateScale(scaleX, scaleY, 1); + } + else { - VerticalTextAlignment.Top => State.UiRect.Size.y / 2, - VerticalTextAlignment.Center => xfc.Height / 2, - VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y / 2), - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; + scale = State.UiRect.Size == _parentRect.Size && xfc.Name.Contains("Canvas") + ? float4x4.CreateScale(State.UiRect.Size.x, State.UiRect.Size.y, 1) + : float4x4.CreateScale(1, 1, 1); + } + + State.Model *= scale; } - else + + /// + /// If a XFormTextComponent is visited the model matrix gets updated in the and set in the . + /// + /// The XFormTextComponent. + [VisitMethod] + public void RenderXFormText(XFormText xfc) { - //Undo parent scale, scale by distance - scaleX = 1 / State.UiRect.Size.x * scaleFactor; - scaleY = 1 / State.UiRect.Size.y * scaleFactor; + var zNear = (InvProjection * new float4(-1, -1, -1, 1)).z; + var scaleFactor = zNear / 100; + var invScaleFactor = 1 / scaleFactor; - //Calculate translation according to alignment by scaling the rectangle size - translationX = xfc.HorizontalAlignment switch - { - HorizontalTextAlignment.Left => -State.UiRect.Size.x * invScaleFactor / 2, - HorizontalTextAlignment.Center => -xfc.Width / 2, - HorizontalTextAlignment.Right => State.UiRect.Size.x * invScaleFactor / 2 - xfc.Width, - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - translationY = xfc.VerticalAlignment switch + float translationY; + float translationX; + + float scaleX; + float scaleY; + + if (_ctc?.CanvasRenderMode == CanvasRenderMode.Screen) { - VerticalTextAlignment.Top => State.UiRect.Size.y * invScaleFactor / 2, - VerticalTextAlignment.Center => xfc.Height / 2, - VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y * invScaleFactor / 2), - _ => throw new ArgumentException("Invalid Horizontal Alignment"), - }; - } + //Undo parent scale + scaleX = 1 / State.UiRect.Size.x; + scaleY = 1 / State.UiRect.Size.y; - var translation = float4x4.CreateTranslation(translationX, translationY, 0); - var scale = float4x4.CreateScale(scaleX, scaleY, 1); + //Calculate translation according to alignment + translationX = xfc.HorizontalAlignment switch + { + HorizontalTextAlignment.Left => -State.UiRect.Size.x / 2, + HorizontalTextAlignment.Center => -xfc.Width / 2, + HorizontalTextAlignment.Right => State.UiRect.Size.x / 2 - xfc.Width, + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + translationY = xfc.VerticalAlignment switch + { + VerticalTextAlignment.Top => State.UiRect.Size.y / 2, + VerticalTextAlignment.Center => xfc.Height / 2, + VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y / 2), + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + } + else + { + //Undo parent scale, scale by distance + scaleX = 1 / State.UiRect.Size.x * scaleFactor; + scaleY = 1 / State.UiRect.Size.y * scaleFactor; - State.Model *= scale; - State.Model *= translation; - } + //Calculate translation according to alignment by scaling the rectangle size + translationX = xfc.HorizontalAlignment switch + { + HorizontalTextAlignment.Left => -State.UiRect.Size.x * invScaleFactor / 2, + HorizontalTextAlignment.Center => -xfc.Width / 2, + HorizontalTextAlignment.Right => State.UiRect.Size.x * invScaleFactor / 2 - xfc.Width, + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + translationY = xfc.VerticalAlignment switch + { + VerticalTextAlignment.Top => State.UiRect.Size.y * invScaleFactor / 2, + VerticalTextAlignment.Center => xfc.Height / 2, + VerticalTextAlignment.Bottom => xfc.Height - (State.UiRect.Size.y * invScaleFactor / 2), + _ => throw new ArgumentException("Invalid Horizontal Alignment"), + }; + } - /// - /// If a TransformComponent is visited the model matrix of the and is updated. - /// It additionally updates the view matrix of the RenderContext. - /// - /// The TransformComponent. - [VisitMethod] - public void RenderTransform(Transform transform) - { - State.Model *= transform.Matrix; - } + var translation = float4x4.CreateTranslation(translationX, translationY, 0); + var scale = float4x4.CreateScale(scaleX, scaleY, 1); - /// - /// Creates pick results from a given mesh if it is within the pick position. - /// - /// The given Mesh. - [VisitMethod] - public void PickMesh(Mesh mesh) - { - if (!mesh.Active) return; + State.Model *= scale; + State.Model *= translation; + } - var mvp = Projection * View * State.Model; + /// + /// If a TransformComponent is visited the model matrix of the and is updated. + /// It additionally updates the view matrix of the RenderContext. + /// + /// The TransformComponent. + [VisitMethod] + public void RenderTransform(Transform transform) + { + State.Model *= transform.Matrix; + } - if (mesh.MeshType == PrimitiveType.Triangles || - mesh.MeshType == PrimitiveType.TriangleFan || - mesh.MeshType == PrimitiveType.TriangleStrip) + /// + /// Creates pick results from a given mesh if it is within the pick position. + /// + /// The given Mesh. + [VisitMethod] + public void PickMesh(Mesh mesh) { - for (var i = 0; i < mesh.Triangles.Length; i += 3) - { - // a, b c: current triangle's vertices in clip coordinates - var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - a = float4x4.TransformPerspective(mvp, a); + if (!mesh.Active) return; - var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - b = float4x4.TransformPerspective(mvp, b); + var mvp = Projection * View * State.Model; - var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - c = float4x4.TransformPerspective(mvp, c); + if (mesh != null && (mesh.MeshType == PrimitiveType.Triangles || + mesh.MeshType == PrimitiveType.TriangleFan || + mesh.MeshType == PrimitiveType.TriangleStrip)) + { + if (mesh.Triangles == null) return; + if (mesh.Vertices == null) return; - // Point-in-Triangle-Test - if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) + for (var i = 0; i < mesh.Triangles.Length; i += 3) { - var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + // a, b c: current triangle's vertices in clip coordinates + var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); + a = float4x4.TransformPerspective(mvp, a); - if (pickPos.z >= -1 && pickPos.z <= 1) + var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); + b = float4x4.TransformPerspective(mvp, b); + + var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); + c = float4x4.TransformPerspective(mvp, c); + + // Point-in-Triangle-Test + if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) { - if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) + var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + + if (pickPos.z >= -1 && pickPos.z <= 1) { - YieldItem(new PickResult + if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - View = View, - ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), - Projection = Projection - }); + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + View = View, + ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + Projection = Projection + }); + } } } } } - } - else if (mesh.MeshType == PrimitiveType.Lines) - { - var matOfNode = CurrentNode.GetComponent(); - if (matOfNode == null) + else if (mesh?.MeshType == PrimitiveType.Lines) { - Diagnostics.Warn("No shader effect for line renderer found!"); - return; - } - var thicknessFromShader = matOfNode.GetFxParam("Thickness"); + var matOfNode = CurrentNode.GetComponent(); + if (matOfNode == null) + { + Diagnostics.Warn("No shader effect for line renderer found!"); + return; + } + var thicknessFromShader = matOfNode.GetFxParam("Thickness"); - for (var i = 0; i < mesh.Triangles.Length; i += 2) - { - var viewportHeight = CurrentCamera.Viewport.w; - var thickness = (thicknessFromShader / viewportHeight); + if (mesh.Triangles == null) return; + if (mesh.Vertices == null) return; + if (CurrentCamera == null) + { + Diagnostics.Warn("No camera found in SceneGraph, no picking possible"); + return; + } - var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; - var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = PickPosClip; + for (var i = 0; i < mesh.Triangles.Length; i += 2) + { + var viewportHeight = CurrentCamera.Viewport.w; + var thickness = (thicknessFromShader / viewportHeight); - // Line Eq = ax + by + c = 0 - // A = (y1 - y2) - // B = (x2 - x1) - // C = (x1 * y2 - x2 * y1) + var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; + var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; + var pt0 = PickPosClip; - // dist(line, pt) = |Ax + By + C| / A² + B² - var a = pt1.y - pt2.y; - var b = pt2.x - pt1.x; - var c = pt1.x * pt2.y - pt2.x * pt1.y; + // Line Eq = ax + by + c = 0 + // A = (y1 - y2) + // B = (x2 - x1) + // C = (x1 * y2 - x2 * y1) - var d = (MathF.Abs(a * pt0.x + b * pt0.y + c) / (a * a + b * b)); + // dist(line, pt) = |Ax + By + C| / A² + B² + var a = pt1.y - pt2.y; + var b = pt2.x - pt1.x; + var c = pt1.x * pt2.y - pt2.x * pt1.y; - if (d <= thickness) - { - YieldItem(new PickResult + var d = (MathF.Abs(a * pt0.x + b * pt0.y + c) / (a * a + b * b)); + + if (d <= thickness) { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), - View = View, - Projection = Projection - }); + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + View = View, + Projection = Projection + }); + } } } } - } - /// - /// The pick position on the screen. - /// - public float2 PickPosClip { get; set; } + /// + /// The pick position on the screen. + /// + public float2 PickPosClip { get; set; } - #endregion + #endregion -} + } } \ No newline at end of file diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index 78c2b1dbc..35efce4db 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -23,10 +23,10 @@ public class SceneInteractionHandler : Visitor /// Initializes a new instance of the class. /// /// The scene the interaction handler belongs to. - public SceneInteractionHandler(SceneContainer scene) + public SceneInteractionHandler(SceneContainer scene, int canvasWidth = 0, int canvasHeight = 0) { IgnoreInactiveComponents = true; - _scenePicker = new ScenePicker(scene); + _scenePicker = new ScenePicker(scene, canvasWidth, canvasHeight); } private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IList pickResults) From 86a8d3b5d6215c03afdd1b3e34dcbe4c1760b2cc Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 3 Nov 2022 14:06:12 +0100 Subject: [PATCH 003/294] Fixed picking without RC, fixed Interaction Handler --- .../Complete/AdvancedUI/Core/AdvancedUI.cs | 39 ++++++++++--------- Examples/Complete/Camera/Core/Camera.cs | 4 +- .../ComputeFractal/Core/ComputeFractal.cs | 4 +- .../GeometryEditing/Core/GeometryEditing.cs | 4 +- Examples/Complete/Integrations/Core/Main.cs | 4 +- Examples/Complete/Labyrinth/Core/Labyrinth.cs | 4 +- Examples/Complete/Materials/Core/Materials.cs | 4 +- Examples/Complete/Picking/Core/Picking.cs | 32 +++++++-------- Examples/Complete/Simple/Core/Simple.cs | 4 +- Examples/Complete/UI/Core/UI.cs | 4 +- src/Engine/Core/ScenePicker.cs | 24 +++++++----- src/Engine/GUI/SceneInteractionHandler.cs | 12 +++--- src/Engine/Player/Core/Player.cs | 4 +- 13 files changed, 76 insertions(+), 67 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs index 63c41dcd2..9dbb41baa 100644 --- a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs +++ b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs @@ -160,7 +160,7 @@ public override void Init() _sih = new SceneInteractionHandler(_gui); //Create a scene picker for performing visibility tests - _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); + _scenePicker = new ScenePicker(_scene); // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); @@ -247,22 +247,23 @@ public override void RenderAFrame() circle.GetComponent().Offsets = GuiElementPosition.CalcOffsets(AnchorPos.Middle, pos, _canvasHeight, _canvasWidth, uiInput.Size); //1.1 Check if circle is visible - PickResult newPick = _scenePicker.Pick(new float2(clipPos.x, clipPos.y)).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); - - if (newPick != null && uiInput.AffectedTriangles[0] == newPick.Triangle) //VISIBLE - { - uiInput.IsVisible = true; - - var effect = circle.GetComponent(); - effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaVis); - } - else - { - uiInput.IsVisible = false; - var effect = circle.GetComponent(); - effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaInv); - - } + PickResult newPick = _scenePicker.Pick(new float2(clipPos.x, clipPos.y), Width, Height).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); + + // TODO(mr): Fix and introduce new PickResult for TriangleMeshes, use it here + //if (newPick != null && uiInput.AffectedTriangles[0] == newPick.Triangle) //VISIBLE + //{ + // uiInput.IsVisible = true; + // + // var effect = circle.GetComponent(); + // effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaVis); + //} + //else + //{ + // uiInput.IsVisible = false; + // var effect = circle.GetComponent(); + // effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaInv); + // + //} //1.2 Calculate annotation positions without intersections. if (!uiInput.CircleCanvasPos.Equals(uiInput.CircleCanvasPosCache)) @@ -336,11 +337,11 @@ public override void RenderAFrame() // Constantly check for interactive objects. if (!Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); if (Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } Present(); diff --git a/Examples/Complete/Camera/Core/Camera.cs b/Examples/Complete/Camera/Core/Camera.cs index abce4009a..23570b9e9 100644 --- a/Examples/Complete/Camera/Core/Camera.cs +++ b/Examples/Complete/Camera/Core/Camera.cs @@ -194,11 +194,11 @@ public override void RenderAFrame() if (!Mouse.Desc.Contains("Android")) { - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); } if (Touch != null && Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } // Swap buffers: Show the contents of the backbuffer (containing the currently rendered frame) on the front buffer. diff --git a/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs b/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs index 56afe751b..3c56da381 100644 --- a/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs +++ b/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs @@ -115,11 +115,11 @@ public override void RenderAFrame() if (!Mouse.Desc.Contains("Android")) { - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); } if (Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } Present(); } diff --git a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs index c85342154..863407d83 100644 --- a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs +++ b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs @@ -120,7 +120,7 @@ public override void Init() _scene = new SceneContainer { Children = new List { _parentNode } }; _renderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); + _scenePicker = new ScenePicker(_scene); _activeGeometrys = new Dictionary(); } @@ -141,7 +141,7 @@ public override void RenderAFrame() { float2 pickPosClip = _pickPos * new float2(2.0f / Width, -2.0f / Height) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); + PickResult newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); if (newPick?.Node != _currentPick?.Node) { diff --git a/Examples/Complete/Integrations/Core/Main.cs b/Examples/Complete/Integrations/Core/Main.cs index 200794339..24b05449c 100644 --- a/Examples/Complete/Integrations/Core/Main.cs +++ b/Examples/Complete/Integrations/Core/Main.cs @@ -140,10 +140,10 @@ public override void RenderAFrame() //Constantly check for interactive objects. if (!Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); if (Touch != null && Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } Present(); diff --git a/Examples/Complete/Labyrinth/Core/Labyrinth.cs b/Examples/Complete/Labyrinth/Core/Labyrinth.cs index be92b863c..c8d3a5046 100644 --- a/Examples/Complete/Labyrinth/Core/Labyrinth.cs +++ b/Examples/Complete/Labyrinth/Core/Labyrinth.cs @@ -369,11 +369,11 @@ public override void RenderAFrame() _guiRenderer.Render(RC); if (!Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); if (Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } // Swap buffers: Show the contents of the backbuffer (containing the currently rendered frame) on the front buffer. diff --git a/Examples/Complete/Materials/Core/Materials.cs b/Examples/Complete/Materials/Core/Materials.cs index 4861feaf6..93b736cf5 100644 --- a/Examples/Complete/Materials/Core/Materials.cs +++ b/Examples/Complete/Materials/Core/Materials.cs @@ -209,12 +209,12 @@ public override void RenderAFrame() if (!Mouse.Desc.Contains("Android")) { - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); } if (Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } Present(); diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 7659e0e6a..9170f1722 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -32,9 +32,9 @@ public class Picking : RenderCanvas private const float ZFar = 1000; private readonly float _fovy = M.PiOver4; - //private SceneRendererForward _guiRenderer; - //private SceneContainer _gui; - //private SceneInteractionHandler _sih; + private SceneRendererForward _guiRenderer; + private SceneContainer _gui; + private SceneInteractionHandler _sih; private PickResult _currentPick; private float4 _oldColor; @@ -48,12 +48,12 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene, RC.ViewportWidth, RC.ViewportHeight); + _scenePicker = new ScenePicker(_scene); - //_gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); + _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler - //_sih = new SceneInteractionHandler(_gui); - // _guiRenderer = new SceneRendererForward(_gui); + _sih = new SceneInteractionHandler(_gui); + _guiRenderer = new SceneRendererForward(_gui); } public override async Task InitAsync() @@ -123,7 +123,7 @@ public override void RenderAFrame() { float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z) + PickResult newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); Diagnostics.Debug(newPick); @@ -148,16 +148,16 @@ public override void RenderAFrame() _pick = false; } - //_guiRenderer.Render(RC); + _guiRenderer.Render(RC); // Constantly check for interactive objects. - // if (!Input.Mouse.Desc.Contains("Android")) - // _sih.CheckForInteractiveObjects(RC, Input.Mouse.Position, Width, Height); - // - // if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) - // { - // _sih.CheckForInteractiveObjects(RC, Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); - // } + if (!Input.Mouse.Desc.Contains("Android")) + _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); + + if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) + { + _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + } // Swap buffers: Show the contents of the back buffer (containing the currently rendered frame) on the front buffer. Present(); diff --git a/Examples/Complete/Simple/Core/Simple.cs b/Examples/Complete/Simple/Core/Simple.cs index 129ce5edd..a735fda81 100644 --- a/Examples/Complete/Simple/Core/Simple.cs +++ b/Examples/Complete/Simple/Core/Simple.cs @@ -136,10 +136,10 @@ public override void RenderAFrame() //Constantly check for interactive objects. if (!Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); if (Touch != null && Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } // Swap buffers: Show the contents of the backbuffer (containing the currently rendered frame) on the front buffer. diff --git a/Examples/Complete/UI/Core/UI.cs b/Examples/Complete/UI/Core/UI.cs index b9cfc19f9..d3699348f 100644 --- a/Examples/Complete/UI/Core/UI.cs +++ b/Examples/Complete/UI/Core/UI.cs @@ -469,11 +469,11 @@ public override void RenderAFrame() // Constantly check for interactive objects. if (!Input.Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(RC, Input.Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); if (Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } // Swap buffers: Show the contents of the back buffer (containing the currently rendered frame) on the front buffer. diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index bb9eeae1e..4c2496b52 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -5,7 +5,9 @@ using Fusee.Math.Core; using Fusee.Xene; using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; namespace Fusee.Engine.Core { @@ -57,6 +59,9 @@ public class ScenePicker : Viserator /// The picker state upon scene traversal. @@ -134,21 +139,16 @@ public PickerState() #endregion - private int _canvasWidth; - private int _canvasHeight; - /// /// The constructor to initialize a new ScenePicker. /// /// The to pick from. - public ScenePicker(SceneContainer scene, int canvasWidth, int canvasHeight) + public ScenePicker(SceneContainer scene) : base(scene.Children) { IgnoreInactiveComponents = true; View = float4x4.Identity; Projection = float4x4.Identity; - _canvasWidth = canvasWidth; - _canvasHeight = canvasHeight; } /// @@ -166,10 +166,14 @@ protected override void InitState() /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// /// The pick position. + /// The width of the current canvas, gets overwritte if a is bound + /// The height of the current canvas, gets overwritte if a is bound /// - public IEnumerable Pick(float2 pickPos) + public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { PickPosClip = pickPos; + _canvasWidth = canvasWidth; + _canvasHeight = canvasHeight; return Viserate(); } @@ -211,7 +215,9 @@ public void UpdateCamera(Camera cam) } View = view.Invert(); - Projection = cam.GetProjectionMat((int)_canvasWidth, (int)_canvasHeight, out var _); + Projection = CurrentCamera.RenderTexture != null + ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) + : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); } /// @@ -477,7 +483,7 @@ public void PickMesh(Mesh mesh) Node = CurrentNode, Model = State.Model, View = View, - ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + ClipPos = float4x4.TransformPerspective(Projection * View, State.Model.Translation()), Projection = Projection }); } diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index 35efce4db..e7b7d9d61 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -1,4 +1,5 @@ -using Fusee.Engine.Core; +using Fusee.Engine.Common; +using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.Xene; @@ -19,14 +20,15 @@ public class SceneInteractionHandler : Visitor private SceneNode _pickRes; private SceneNode _pickResCache; + /// /// Initializes a new instance of the class. /// /// The scene the interaction handler belongs to. - public SceneInteractionHandler(SceneContainer scene, int canvasWidth = 0, int canvasHeight = 0) + public SceneInteractionHandler(SceneContainer scene) { IgnoreInactiveComponents = true; - _scenePicker = new ScenePicker(scene, canvasWidth, canvasHeight); + _scenePicker = new ScenePicker(scene); } private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IList pickResults) @@ -60,11 +62,11 @@ private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IListThe current mouse position. /// Canvas width - needed to determine the mouse position in clip space. /// Canvas height - needed to determine the mouse position in clip space. - public void CheckForInteractiveObjects(RenderContext rc, float2 mousePos, int canvasWidth, int canvasHeight) + public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int canvasHeight) { var pickPosClip = mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight) + new float2(-1, 1); - var pickResults = _scenePicker.Pick(pickPosClip).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); + var pickResults = _scenePicker.Pick(pickPosClip, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); var pickResNodes = pickResults.Select(x => x.Node).ToList(); var firstPickRes = pickResults.FirstOrDefault(); diff --git a/src/Engine/Player/Core/Player.cs b/src/Engine/Player/Core/Player.cs index 8cf827777..b68165b93 100644 --- a/src/Engine/Player/Core/Player.cs +++ b/src/Engine/Player/Core/Player.cs @@ -215,11 +215,11 @@ public override void RenderAFrame() _guiRenderer.Render(RC); // Constantly check for interactive objects. - _sih.CheckForInteractiveObjects(RC, Mouse.Position, Width, Height); + _sih.CheckForInteractiveObjects(Mouse.Position, Width, Height); if (Touch != null && Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Touch.TwoPoint) { - _sih.CheckForInteractiveObjects(RC, Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + _sih.CheckForInteractiveObjects(Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); } // Swap buffers: Show the contents of the backbuffer (containing the currently rendered frame) on the front buffer. From a5892fb3268f42e02f02e0264a3b1dc8f280a587 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 3 Nov 2022 15:59:00 +0100 Subject: [PATCH 004/294] Added Serialization V2, moved Mesh to V2, added PickComponent, updated FusSceneConv --- src/Engine/Core/FusSceneConverter.cs | 162 +++++++++++++++++++---- src/Engine/Core/Scene/PickComponent.cs | 14 ++ src/Serialization/FusFile.cs | 4 +- src/Serialization/V1/FusMesh.cs | 2 +- src/Serialization/V2/FusMesh.cs | 104 +++++++++++++++ src/Serialization/V2/FusPickComponent.cs | 20 +++ 6 files changed, 279 insertions(+), 27 deletions(-) create mode 100644 src/Engine/Core/Scene/PickComponent.cs create mode 100644 src/Serialization/V2/FusMesh.cs create mode 100644 src/Serialization/V2/FusPickComponent.cs diff --git a/src/Engine/Core/FusSceneConverter.cs b/src/Engine/Core/FusSceneConverter.cs index 490b2d53a..fad015f3c 100644 --- a/src/Engine/Core/FusSceneConverter.cs +++ b/src/Engine/Core/FusSceneConverter.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Diagnostics; using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core.Effects; @@ -47,7 +48,7 @@ public static async Task ConvertFromAsync(FusFile fus, string id } // try to cast, if this fails the content is empty or null - if (!(fus.Contents is FusScene)) + if (fus.Contents is not FusScene) { Diagnostics.Error($"Could not read content of scene from {fus.Header.CreationDate} created by {fus.Header.CreatedBy} with {fus.Header.Generator}"); return new SceneContainer(); @@ -60,7 +61,12 @@ public static async Task ConvertFromAsync(FusFile fus, string id fus.Header.LoadPath = Path.GetDirectoryName(id); } - var instance = new FusFileToSceneConvertV1(); + var instance = fus.Header.FileVersion switch + { + 2 => new FusFileToSceneConvertV2(), + _ => new FusFileToSceneConvertV1(), + }; + var payload = (FusScene)fus.Contents; @@ -116,7 +122,8 @@ public static async Task ConvertFromAsync(FusFile fus, string id /// Traverses the given SceneContainer and creates new high low level graph by converting and/or splitting its components into the low level equivalents. /// /// The Scene to convert. - public static FusFile ConvertTo(SceneContainer sc) + /// The scene version, default = 2, currently there are V1 and V2 implemented + public static FusFile ConvertTo(SceneContainer sc, int fileVersion = 2) { if (sc == null) { @@ -124,7 +131,15 @@ public static FusFile ConvertTo(SceneContainer sc) return new FusFile(); } - var instance = new SceneToFusFileConvertV1(); + Guard.IsInRange(fileVersion, 1, 3); + + + var instance = fileVersion switch + { + 2 => new SceneToFusFileConvertV2(), + _ => new SceneToFusFileConvertV1() + }; + var converted = instance.Convert(sc); converted.Header = new FusHeader @@ -132,26 +147,88 @@ public static FusFile ConvertTo(SceneContainer sc) CreatedBy = sc.Header.CreatedBy, CreationDate = sc.Header.CreationDate, Generator = sc.Header.Generator, - FileVersion = 1 + FileVersion = fileVersion }; return converted; } } + internal class FusFileToSceneConvertV2 : FusFileToSceneConvertV1 + { + private readonly Dictionary _meshMap; + + internal FusFileToSceneConvertV2() + { + _meshMap = new Dictionary(); + } + + /// + /// Converts the PickComponent. + /// + /// The mesh to convert. + [VisitMethod] + public void ConvPickComp(Serialization.V2.FusPickComponent pc) + { + _currentNode.Components.Add(new PickComponent + { + Active = pc.Active, + Name = pc.Name, + PickLayer = pc.PickLayer + }); + } + + /// + /// Converts the mesh. + /// + /// The mesh to convert. + [VisitMethod] + public void ConvMesh(Serialization.V2.FusMesh m) + { + if (_currentNode.Components == null) + { + _currentNode.Components = new List(); + } + + if (_meshMap.TryGetValue(m, out var mesh)) + { + _currentNode.Components.Add(mesh); + return; + } + + // convert mesh + mesh = new Mesh(m.Triangles, m.Vertices, m.Normals, m.UVs, m.BoneWeights, m.BoneIndices, m.Tangents, m.BiTangents, + m.Colors) + { + MeshType = (PrimitiveType)m.MeshType, + Active = true, + Name = m.Name + }; + + if (_currentNode.Components == null) + { + _currentNode.Components = new List(); + } + + _currentNode.Components.Add(mesh); + + _meshMap.Add(m, mesh); + } + } + internal class FusFileToSceneConvertV1 : Visitor { - private FusScene _fusScene; - private readonly SceneContainer _convertedScene; - private readonly Stack _predecessors; - private SceneNode _currentNode; + protected FusScene _fusScene; + protected readonly SceneContainer _convertedScene; + protected readonly Stack _predecessors; + protected SceneNode _currentNode; - private readonly Dictionary _matMap; + protected readonly Dictionary _matMap; private readonly Dictionary _meshMap; - private readonly ConcurrentDictionary _texMap; - private readonly Stack _boneContainers; + protected readonly ConcurrentDictionary _texMap; + protected readonly Stack _boneContainers; - private readonly Dictionary> _allEffects; + protected readonly Dictionary> _allEffects; /// /// Method is called when going up one hierarchy level while traversing. Override this method to perform pop on any self-defined state. @@ -551,12 +628,14 @@ public void ConvMesh(FusMesh m) } // convert mesh - mesh = new Mesh(m.Triangles, m.Vertices, m.Normals, m.UVs, m.BoneWeights, m.BoneIndices, m.Tangents, m.BiTangents, - m.Colors); - - mesh.MeshType = (PrimitiveType)m.MeshType; - mesh.Active = true; - mesh.Name = m.Name; + var triangles = m.Triangles.Select(x => (uint)x).ToArray(); + mesh = new Mesh(triangles, m.Vertices, m.Normals, m.UVs, m.BoneWeights, m.BoneIndices, m.Tangents, m.BiTangents, + m.Colors) + { + MeshType = (PrimitiveType)m.MeshType, + Active = true, + Name = m.Name + }; if (_currentNode.Components == null) { @@ -954,13 +1033,48 @@ private async Task GetEffectForMat(FusMaterialBase m, ShadingModel light #endregion } + internal class SceneToFusFileConvertV2 : SceneToFusFileConvertV1 + { + internal SceneToFusFileConvertV2() + { + + } + + /// + /// Converts the mesh. + /// + /// The mesh to convert. + [VisitMethod] + public void ConvMesh(Mesh m) + { + // convert mesh + var mesh = new Serialization.V2.FusMesh + { + MeshType = (int)m.MeshType, + BiTangents = m.BiTangents?.ToArray(), + BoneIndices = m.BoneIndices?.ToArray(), + BoundingBox = m.BoundingBox, + BoneWeights = m.BoneWeights?.ToArray(), + Colors = m.Colors0?.ToArray(), + Name = m.Name, + Normals = m.Normals?.ToArray(), + Tangents = m.Tangents?.ToArray(), + Triangles = m.Triangles?.ToArray(), + UVs = m.UVs?.ToArray(), + Vertices = m.Vertices?.ToArray() + }; + + _currentNode.AddComponent(mesh); + } + } + internal class SceneToFusFileConvertV1 : Visitor { - private readonly FusFile _convertedScene; - private readonly Stack _predecessors; - private FusNode _currentNode; + protected readonly FusFile _convertedScene; + protected readonly Stack _predecessors; + protected FusNode _currentNode; - private readonly Stack _boneContainers; + protected readonly Stack _boneContainers; /// /// Method is called when going up one hierarchy level while traversing. Override this method to perform pop on any self-defined state. @@ -1255,7 +1369,7 @@ public void ConvMesh(Mesh m) Name = m.Name, Normals = m.Normals?.ToArray(), Tangents = m.Tangents?.ToArray(), - Triangles = m.Triangles.ToArray(), + Triangles = m.Triangles?.ToArray().Select(x => (int)x).ToArray(), UVs = m.UVs?.ToArray(), Vertices = m.Vertices?.ToArray() }; diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs new file mode 100644 index 000000000..d467da955 --- /dev/null +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fusee.Engine.Core.Scene +{ + /// + /// PickComponent + /// + public class PickComponent : SceneComponent + { + public int PickLayer { get; set; } + } +} diff --git a/src/Serialization/FusFile.cs b/src/Serialization/FusFile.cs index 6b2ddbe52..d69022edf 100644 --- a/src/Serialization/FusFile.cs +++ b/src/Serialization/FusFile.cs @@ -49,7 +49,7 @@ public struct FusHeader /// /// Polymorphic contents of a fus file. The actual contents is contained in a sub-class. Possible sub-classes are listed /// in decorators preceding this class declaration. This mechanism allows fus files - /// to contain different types of contents as well as different versions of contents. + /// to contain different types of contents as well as different versions of contents. /// [ProtoInclude(201, typeof(V1.FusScene))] [ProtoContract] @@ -71,7 +71,7 @@ public class FusFile public FusHeader Header; /// - /// The file contents. Check and cast to the concrete type to access it, e. g. using a C# 7.0 + /// The file contents. Check and cast to the concrete type to access it, e. g. using a C# 7.0 /// pattern matching in switch expression. /// [ProtoMember(2)] diff --git a/src/Serialization/V1/FusMesh.cs b/src/Serialization/V1/FusMesh.cs index 501bf036b..8fc0eb702 100644 --- a/src/Serialization/V1/FusMesh.cs +++ b/src/Serialization/V1/FusMesh.cs @@ -71,7 +71,7 @@ public class FusMesh : FusComponent /// The triangles. /// [ProtoMember(7)] - public uint[] Triangles; + public int[] Triangles; /// /// The bounding box of this geometry chunk. diff --git a/src/Serialization/V2/FusMesh.cs b/src/Serialization/V2/FusMesh.cs new file mode 100644 index 000000000..171acbcae --- /dev/null +++ b/src/Serialization/V2/FusMesh.cs @@ -0,0 +1,104 @@ +using Fusee.Math.Core; +using Fusee.Serialization.V1; +using ProtoBuf; + +namespace Fusee.Serialization.V2 +{ + /// + /// Contains 3D geometry information (Vertices, Triangles, Normals, UVs, ...). + /// + [ProtoContract] + public class FusMesh : FusComponent + { + #region Payload + /// + /// Gets and sets the vertices. + /// + /// + /// The vertices. + /// + [ProtoMember(1)] + public float3[] Vertices; + + /// + /// Gets and sets the color of a single vertex. + /// + /// + /// The color. + /// + [ProtoMember(2)] + public uint[] Colors; + + /// + /// Gets and sets the normals. + /// + /// + /// The normals.. + /// + [ProtoMember(3)] + public float3[] Normals; + + /// + /// Gets and sets the UV-coordinates. + /// + /// + /// The UV-coordinates. + /// + [ProtoMember(4)] + public float2[] UVs; + + /// + /// Gets and sets the boneweights. + /// + /// + /// The boneweights. + /// + [ProtoMember(5)] + public float4[] BoneWeights; + + /// + /// Gets and sets the boneindices. + /// + /// + /// The boneindices. + /// + [ProtoMember(6)] + public float4[] BoneIndices; + + /// + /// Gets and sets the triangles. + /// + /// + /// The triangles. + /// + [ProtoMember(7)] + public uint[] Triangles; + + /// + /// The bounding box of this geometry chunk. + /// + [ProtoMember(8)] + public AABBf BoundingBox; + + /// + /// The tangent of each triangle for normal mapping. + /// w-component is handedness + /// + [ProtoMember(9)] + public float4[] Tangents; + + /// + /// The bitangent of each triangle for normal mapping. + /// + [ProtoMember(10)] + public float3[] BiTangents; + + /// + /// The type of the mesh ??? + /// + [ProtoMember(11)] + public int MeshType = 0; + #endregion + } + +} \ No newline at end of file diff --git a/src/Serialization/V2/FusPickComponent.cs b/src/Serialization/V2/FusPickComponent.cs new file mode 100644 index 000000000..07efcce8e --- /dev/null +++ b/src/Serialization/V2/FusPickComponent.cs @@ -0,0 +1,20 @@ +using ProtoBuf; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fusee.Serialization.V2 +{ + /// + /// If is , the picking is being skiped for this and all successors. + /// + [ProtoContract] + public class FusPickComponent : V1.FusComponent + { + /// + /// If there is more than one PickComponent in one scene, the rendered output of the picker with a higher layer will be picked first above the output of a pick result with a lower layer. + /// + [ProtoMember(1)] + public int PickLayer; + } +} From f2582f3fe8e232ba900353f16fdce7031c3d2f0b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 3 Nov 2022 16:04:25 +0100 Subject: [PATCH 005/294] Added custom pick method to PickComponent --- src/Engine/Core/Scene/PickComponent.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs index d467da955..4be7dc791 100644 --- a/src/Engine/Core/Scene/PickComponent.cs +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -9,6 +9,14 @@ namespace Fusee.Engine.Core.Scene /// public class PickComponent : SceneComponent { + /// + /// Pick layer, on picking the result with the higher layer will be prefered + /// public int PickLayer { get; set; } + + /// + /// Posibility to deposit a custom method on how to pick the following mesh(es) + /// + public Func? CustomPickMethod; } } From a8a43092e4b48fd9a883d1a48069fb170b2cddb7 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 7 Nov 2022 16:03:47 +0100 Subject: [PATCH 006/294] initial point cloud picking --- src/Engine/Core/IPickerModule.cs | 20 +++ src/Engine/Core/Scene/PickComponent.cs | 3 +- src/Engine/Core/ScenePicker.cs | 123 ++++++++++++------ .../Core/Fusee.Engine.Player.Core.csproj | 4 +- .../Core/Scene/PointCloudPickerModule.cs | 40 ++++++ .../V1/SimpleConvertSceneGraph.cs | 4 +- .../Serialization/V1/SimpleSerialization.cs | 2 +- src/Xene/Viserator.cs | 18 ++- src/Xene/Visitor.cs | 2 +- 9 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 src/Engine/Core/IPickerModule.cs create mode 100644 src/PointCloud/Core/Scene/PointCloudPickerModule.cs diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs new file mode 100644 index 000000000..de1cf30a8 --- /dev/null +++ b/src/Engine/Core/IPickerModule.cs @@ -0,0 +1,20 @@ +using Fusee.Engine.Common; +using System; +using System.Collections.Generic; +using System.Text; +using static Fusee.Engine.Core.ScenePicker; + +namespace Fusee.Engine.Core +{ + public interface IPickerModule : IVisitorModule + { + + /// + /// Sets the for this module. Pass the state from the base renderer. + /// + /// The state to set. + public void SetState(PickerState state); + + public Func GetPickerState { get; set; } + } +} diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs index 4be7dc791..1f3048dd8 100644 --- a/src/Engine/Core/Scene/PickComponent.cs +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Fusee.Engine.Core.Scene { @@ -16,6 +14,7 @@ public class PickComponent : SceneComponent /// /// Posibility to deposit a custom method on how to pick the following mesh(es) + /// Check visitor module for new mesh types /// public Func? CustomPickMethod; } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 4c2496b52..a7b6ab921 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -5,10 +5,12 @@ using Fusee.Math.Core; using Fusee.Xene; using System; + using System.Collections; using System.Collections.Generic; using System.Linq; + namespace Fusee.Engine.Core { /// @@ -62,6 +64,14 @@ public class ScenePicker : Viserator + /// Current mouse position (in clip space) + /// + public int PickPosClip { get; set; } + } + #region State /// /// The picker state upon scene traversal. @@ -73,6 +83,8 @@ public class PickerState : VisitorState private readonly CollapsingStateStack _uiRect = new(); private readonly CollapsingStateStack _cullMode = new(); + public float2 PickPosClip { get; set; } + /// /// The registered model. /// @@ -112,6 +124,7 @@ public Cull CullMode /// /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. /// + // TODO: Ask @CMl why this is being called after every Iteration/Viseration public PickerState() { RegisterState(_model); @@ -143,8 +156,8 @@ public PickerState() /// The constructor to initialize a new ScenePicker. /// /// The to pick from. - public ScenePicker(SceneContainer scene) - : base(scene.Children) + public ScenePicker(SceneContainer scene, IEnumerable customPickModule = null) + : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; View = float4x4.Identity; @@ -174,9 +187,23 @@ public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasH PickPosClip = pickPos; _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; + + State.PickPosClip = pickPos; + SetState(); return Viserate(); } + /// + /// Wire state (call by ref) to visitor module + /// + private void SetState() + { + foreach (var module in VisitorModules) + { + ((IPickerModule)module).GetPickerState = () => State; + } + } + #region Visitors /// @@ -215,6 +242,7 @@ public void UpdateCamera(Camera cam) } View = view.Invert(); + // TODO(mr): TEST Renderlayer Projection = CurrentCamera.RenderTexture != null ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); @@ -438,6 +466,17 @@ public void RenderTransform(Transform transform) State.Model *= transform.Matrix; } + + ///// + ///// Creates pick results from a given point cloud if it is within the pick position. + ///// + ///// The given point cloud. + //[VisitMethod] + //public void PickMesh(PointCloudComponent pc) + //{ + // // TODO: Impl + //} + /// /// Creates pick results from a given mesh if it is within the pick position. /// @@ -447,49 +486,14 @@ public void PickMesh(Mesh mesh) { if (!mesh.Active) return; + var mvp = Projection * View * State.Model; if (mesh != null && (mesh.MeshType == PrimitiveType.Triangles || mesh.MeshType == PrimitiveType.TriangleFan || mesh.MeshType == PrimitiveType.TriangleStrip)) { - if (mesh.Triangles == null) return; - if (mesh.Vertices == null) return; - - for (var i = 0; i < mesh.Triangles.Length; i += 3) - { - // a, b c: current triangle's vertices in clip coordinates - var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - a = float4x4.TransformPerspective(mvp, a); - - var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - b = float4x4.TransformPerspective(mvp, b); - - var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - c = float4x4.TransformPerspective(mvp, c); - - // Point-in-Triangle-Test - if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) - { - var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); - - if (pickPos.z >= -1 && pickPos.z <= 1) - { - if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) - { - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - View = View, - ClipPos = float4x4.TransformPerspective(Projection * View, State.Model.Translation()), - Projection = Projection - }); - } - } - } - } + PickTriangleGeometry(mesh, mvp); } else if (mesh?.MeshType == PrimitiveType.Lines) { @@ -546,6 +550,47 @@ public void PickMesh(Mesh mesh) } } + private void PickTriangleGeometry(Mesh mesh, float4x4 mvp) + { + if (mesh.Triangles == null) return; + if (mesh.Vertices == null) return; + + for (var i = 0; i < mesh.Triangles.Length; i += 3) + { + // a, b c: current triangle's vertices in clip coordinates + var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); + a = float4x4.TransformPerspective(mvp, a); + + var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); + b = float4x4.TransformPerspective(mvp, b); + + var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); + c = float4x4.TransformPerspective(mvp, c); + + // Point-in-Triangle-Test + if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) + { + var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + + if (pickPos.z >= -1 && pickPos.z <= 1) + { + if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) + { + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + View = View, + ClipPos = float4x4.TransformPerspective(Projection * View, State.Model.Translation()), + Projection = Projection + }); + } + } + } + } + } + /// /// The pick position on the screen. /// diff --git a/src/Engine/Player/Core/Fusee.Engine.Player.Core.csproj b/src/Engine/Player/Core/Fusee.Engine.Player.Core.csproj index 4f1d22cfa..091f3b05a 100644 --- a/src/Engine/Player/Core/Fusee.Engine.Player.Core.csproj +++ b/src/Engine/Player/Core/Fusee.Engine.Player.Core.csproj @@ -1,5 +1,5 @@ - + netstandard2.1;net6.0 $(BaseOutputPath)\Player\Core\ @@ -32,5 +32,5 @@ analyzers - + \ No newline at end of file diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs new file mode 100644 index 000000000..763b813ef --- /dev/null +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -0,0 +1,40 @@ +using Fusee.Engine.Common; +using Fusee.Engine.Core; +using Fusee.Engine.Core.Scene; +using Fusee.Math.Core; +using Fusee.Xene; +using System; +using System.Collections.Generic; +using System.Text; +using static Fusee.Engine.Core.ScenePicker; + +namespace Fusee.PointCloud.Core.Scene +{ + public class PointCloudPickerModule : IPickerModule + { + private PickerState State; + + public Func GetPickerState { get; set; } + + /// + /// Determines visible points of a point cloud (using the components ) and renders them. + /// + /// The point cloud component. + [VisitMethod] + public void RenderPointCloud(PointCloudComponent pointCloud) + { + State = GetPickerState?.Invoke(); + Console.WriteLine($"MousePos: {State.PickPosClip}"); + } + + public PointCloudPickerModule() + { + + } + + public void SetState(PickerState state) + { + State = state; + } + } +} diff --git a/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs b/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs index 00b69d516..3a4943fb8 100644 --- a/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs +++ b/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs @@ -133,7 +133,7 @@ public void V1_SimpleScene_Convert() // Assert.Equal(mesh.BoundingBox, ((FusMesh)fusFileComp).BoundingBox); <- not yet calculated, is done after first frame Assert.Equal(mesh.Colors0?.ToArray(), ((FusMesh)fusFileComp).Colors); Assert.Equal(mesh.Vertices?.ToArray(), ((FusMesh)fusFileComp).Vertices); - Assert.Equal(mesh.Triangles?.ToArray(), ((FusMesh)fusFileComp).Triangles); + Assert.Equal(mesh.Triangles?.ToArray().Select(x => (int)x).ToArray(), ((FusMesh)fusFileComp).Triangles); Assert.Equal(mesh.UVs?.ToArray(), ((FusMesh)fusFileComp).UVs); Assert.Equal((int)mesh.MeshType, ((FusMesh)fusFileComp).MeshType); Assert.Equal(mesh.Tangents?.ToArray(), ((FusMesh)fusFileComp).Tangents); @@ -216,7 +216,7 @@ public void V1_SimpleScene_Convert() //Assert.Equal(t.Name, ((Transform)sceneFileComp).Name); //Assert.Equal(t.Rotation, ((Transform)sceneFileComp).Rotation); //Assert.Equal(t.Scale, ((Transform)sceneFileComp).Scale); - //Assert.Equal(t.Translation, ((Transform)sceneFileComp).Translation); + //Assert.Equal(t.Translation, ((Transform)sceneFileComp).Translation); //} //if (gtComp is Bone bone) diff --git a/src/Tests/Serialization/V1/SimpleSerialization.cs b/src/Tests/Serialization/V1/SimpleSerialization.cs index 4f21bdebb..a5be91b77 100644 --- a/src/Tests/Serialization/V1/SimpleSerialization.cs +++ b/src/Tests/Serialization/V1/SimpleSerialization.cs @@ -105,7 +105,7 @@ public static FusMesh CreateCuboid(float3 size) new float3 {x = -0.5f * size.x, y = -0.5f * size.y, z = -0.5f * size.z} }, - Triangles = new uint[] + Triangles = new int[] { // front face 0, 2, 1, 0, 3, 2, diff --git a/src/Xene/Viserator.cs b/src/Xene/Viserator.cs index a719baf60..d32597483 100644 --- a/src/Xene/Viserator.cs +++ b/src/Xene/Viserator.cs @@ -1,4 +1,5 @@ -using System; +using Fusee.Engine.Common; +using System; using System.Collections; using System.Collections.Generic; @@ -9,8 +10,8 @@ namespace Fusee.Xene /// public static class ViseratorExtensions { - // Unfortunate construct, but there seems no other way. What we really needed here is a MixIn to make - // a INode or SceneContainer implement IEnumerable (afterwards). All C# offers us is to + // Unfortunate construct, but there seems no other way. What we really needed here is a MixIn to make + // a INode or SceneContainer implement IEnumerable (afterwards). All C# offers us is to // define ExtensionMethods returning an IEnumerable<>. // Thus we need some class to implement that: internal class ViseratorEnumerable : IEnumerable @@ -86,7 +87,7 @@ public ViseratorBase() { } - // Step2: initialize the instance + // Step2: initialize the instance /// /// Initializes this instance with the specified tree. /// @@ -202,8 +203,8 @@ protected virtual void Dispose(bool disposing) /// time during traversal and some additional information of the tree object currently visited. /// /// To implement your own Viserator you should consider which state information the Viserator must keep track of. - /// Either you assemble your own State type by deriving from or choose to use one of - /// the standard state types like . Then you need to derive your own class from + /// Either you assemble your own State type by deriving from or choose to use one of + /// the standard state types like . Then you need to derive your own class from /// /// with the TState replaced by your choice of State and TItem replaced by the type of the items you want your Viserator to yield /// during the traversal. @@ -243,8 +244,11 @@ protected internal override void Init(IEnumerable rootList) /// Initializes a new instance of the class. /// /// The root list. - public Viserator(IEnumerable rootList) + /// Optional custom . Needs to be passed to base.ctor, + /// as the initialization and discovery of potential methods to visit is being done in + protected Viserator(IEnumerable rootList, IEnumerable customVisitorModules = null) { + VisitorModules.AddRange(customVisitorModules); Init(rootList); } diff --git a/src/Xene/Visitor.cs b/src/Xene/Visitor.cs index 506be045d..99f7fbe36 100644 --- a/src/Xene/Visitor.cs +++ b/src/Xene/Visitor.cs @@ -1,6 +1,7 @@ using Fusee.Engine.Common; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Reflection; namespace Fusee.Xene @@ -106,7 +107,6 @@ internal class VisitorSet public Dictionary> ModuleComponents = new(); } - // The list of visitor methods defined in a concrete child class of Visitor private VisitorSet _visitors; From f6d43d3d870eca0e62de90c84873f6846c918e15 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 8 Nov 2022 11:25:03 +0100 Subject: [PATCH 007/294] PointCloudDataHandler & MemoryChache: try to force mesh disposal --- src/Base/Core/MemoryCache.cs | 58 +++++++++++++++- .../Common/PointCloudDataHandlerBase.cs | 69 +++++++++++++++++-- src/PointCloud/Core/PointCloudDataHandler.cs | 57 +++++++++++++-- 3 files changed, 174 insertions(+), 10 deletions(-) diff --git a/src/Base/Core/MemoryCache.cs b/src/Base/Core/MemoryCache.cs index 8c111b40e..262f9d77c 100644 --- a/src/Base/Core/MemoryCache.cs +++ b/src/Base/Core/MemoryCache.cs @@ -8,7 +8,7 @@ namespace Fusee.Base.Core /// The type of the key. /// The type of the cached item. /// - public class MemoryCache + public class MemoryCache : IDisposable { /// /// Sets how long a cache entry can be inactive (not accessed) before it will be removed. @@ -27,8 +27,10 @@ public class MemoryCache private readonly MemoryCache _cache; + private bool _disposed = false; + /// - /// Creates a new instance and initializes the internal with the gi + /// Creates a new instance and initializes the internal . /// public MemoryCache() { @@ -95,5 +97,57 @@ public void AddOrUpdate(TKey key, TItem cacheEntry) _cache.Set(key, cacheEntry, cacheEntryOptions); } } + + /// + /// Implement IDisposable. + /// Do not make this method virtual. + /// A derived class should not be able to override this method. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!_disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // Dispose managed resources. + } + + _cache.Compact(100); + _cache.Dispose(); + + // Note disposing has been done. + _disposed = true; + } + } + + /// + /// Use C# finalizer syntax for finalization code. + /// This finalizer will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide finalizer in types derived from this class. + /// + ~MemoryCache() + { + Dispose(disposing: false); + } } } \ No newline at end of file diff --git a/src/PointCloud/Common/PointCloudDataHandlerBase.cs b/src/PointCloud/Common/PointCloudDataHandlerBase.cs index 908c69f80..e4d163b58 100644 --- a/src/PointCloud/Common/PointCloudDataHandlerBase.cs +++ b/src/PointCloud/Common/PointCloudDataHandlerBase.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Fusee.PointCloud.Common { /// /// Manages the caching and loading of point and mesh data. /// - public abstract class PointCloudDataHandlerBase + public abstract class PointCloudDataHandlerBase : IDisposable where TGpuData : IDisposable { /// /// Used to manage gpu pressure when disposing of a large quantity of meshes. @@ -31,11 +32,11 @@ public abstract class PointCloudDataHandlerBase /// /// Locking object for the loading queue. /// - protected static object LockLoadingQueue = new(); + protected object LockLoadingQueue = new(); /// /// Locking object for the dispose queue. /// - protected static object LockDisposeQueue = new(); + protected object LockDisposeQueue = new(); /// /// First looks in the mesh cache, if there are meshes return, @@ -55,5 +56,65 @@ public abstract class PointCloudDataHandlerBase /// Disposes of unused meshes, if needed. Depends on the dispose rate and the expiration frequency of the MeshCache. /// public abstract void ProcessDisposeQueue(); + + private bool _disposed = false; + + /// + /// Implement IDisposable. + /// Do not make this method virtual. + /// A derived class should not be able to override this method. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + protected virtual void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!_disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // Dispose managed resources. + + } + + // Call the appropriate methods to clean up + // unmanaged resources here. + foreach (var kvp in DisposeQueue) + { + foreach (var val in kvp.Value) + { + val.Dispose(); + } + } + _disposed = true; + } + } + + /// + /// Use C# finalizer syntax for finalization code. + /// This finalizer will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide finalizer in types derived from this class. + /// + ~PointCloudDataHandlerBase() + { + Dispose(disposing: false); + } } } \ No newline at end of file diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 1dcfcf8bd..2cd8f27dc 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -1,4 +1,4 @@ -using Fusee.Base.Core; +using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.PointCloud.Common; @@ -59,17 +59,17 @@ namespace Fusee.PointCloud.Core /// /// /// - public class PointCloudDataHandler : PointCloudDataHandlerBase where TPoint : new() where TGpuData : IDisposable + public class PointCloudDataHandler : PointCloudDataHandlerBase, IDisposable where TPoint : new() where TGpuData : IDisposable { /// /// Caches loaded points. /// - private readonly MemoryCache _pointCache; + private MemoryCache _pointCache; /// /// Caches loaded points. /// - private readonly MemoryCache> _gpuDataCache; + private MemoryCache> _gpuDataCache; private readonly PointAccessor _pointAccessor; private readonly CreateGpuData _createGpuDataHandler; @@ -78,6 +78,8 @@ namespace Fusee.PointCloud.Core private float _deltaTimeSinceLastDisposal; private readonly bool _doRenderInstanced; + private bool _disposed; + /// /// Creates a new instance. /// @@ -204,5 +206,52 @@ private void OnItemEvictedFromCache(object guid, object meshes, EvictionReason r DisposeQueue.Add((OctantId)guid, (IEnumerable)meshes); } } + + /// + /// Dispose(bool disposing) executes in two distinct scenarios. + /// If disposing equals true, the method has been called directly + /// or indirectly by a user's code. Managed and unmanaged resources + /// can be disposed. + /// If disposing equals false, the method has been called by the + /// runtime from inside the finalizer and you should not reference + /// other objects. Only unmanaged resources can be disposed. + /// + /// + protected override void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!_disposed) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + // Dispose managed resources. + } + + // Call the appropriate methods to clean up + // unmanaged resources here. + _gpuDataCache.Dispose(); + _gpuDataCache = null; + + lock (LockDisposeQueue) + { + foreach (var item in DisposeQueue) + { + foreach (var d in item.Value) + { + d.Dispose(); + } + } + } + + _pointCache.Dispose(); + _pointCache = null; + LoadingQueue.Clear(); + + // Note disposing has been done. + _disposed = true; + } + } } } \ No newline at end of file From 8dcbcaa02f8cc6e6db76b42a4bafbfcd5ac08892 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 8 Nov 2022 13:23:39 +0100 Subject: [PATCH 008/294] Potree2Reader: fixed PointCloudDataHandler ctor call for dynamic mshes --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2.cs | 7 ------- .../PointCloudPotree2/Core/PointCloudPotree2Core.cs | 2 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs index d9774fcf3..84e559de3 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs @@ -12,8 +12,6 @@ public class PointCloudPotree2 : RenderCanvas public bool IsInitialized { get; private set; } public bool IsAlive { get; private set; } - public RenderMode PointRenderMode = RenderMode.DynamicMesh; - public bool ClosingRequested { get => _pointRenderingCore.ClosingRequested; @@ -22,11 +20,6 @@ public bool ClosingRequested private PointCloudPotree2Core _pointRenderingCore; - public PointCloudPotree2() - { - - } - public override void Init() { VSync = false; diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 98bca57ab..fbbf04ee3 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -24,7 +24,7 @@ public bool ClosingRequested } private bool _closingRequested; - public RenderMode PointRenderMode = RenderMode.StaticMesh; + public RenderMode PointRenderMode = RenderMode.DynamicMesh; public string AssetsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); private static float _angleHorz, _angleVert, _angleVelHorz, _angleVelVert; diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 3f4e6483c..8c56e3d77 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -52,7 +52,7 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta { var dataHandlerDynamic = new PointCloudDataHandler( (PointAccessor)PointAccessor, MeshMaker.CreateDynamicMeshPosD3ColF3LblB, - LoadNodeData, true); + LoadNodeData); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); } From f316509fa1d38965f65ac77373ec119eeba43967 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 29 Nov 2022 16:24:10 +0100 Subject: [PATCH 009/294] Fixed Picking.cs and visitorModule = null --- Examples/Complete/Picking/Core/Picking.cs | 3 ++- src/Xene/Viserator.cs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 9170f1722..8d021a112 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -125,7 +125,8 @@ public override void RenderAFrame() PickResult newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); - Diagnostics.Debug(newPick); + if (newPick != null) + Diagnostics.Debug(newPick.Node.Name); if (newPick?.Node != _currentPick?.Node) { diff --git a/src/Xene/Viserator.cs b/src/Xene/Viserator.cs index d32597483..b66d78007 100644 --- a/src/Xene/Viserator.cs +++ b/src/Xene/Viserator.cs @@ -241,14 +241,15 @@ protected internal override void Init(IEnumerable rootList) } /// - /// Initializes a new instance of the class. + /// Initializes a new instanc e of the class. /// /// The root list. /// Optional custom . Needs to be passed to base.ctor, /// as the initialization and discovery of potential methods to visit is being done in protected Viserator(IEnumerable rootList, IEnumerable customVisitorModules = null) { - VisitorModules.AddRange(customVisitorModules); + if(customVisitorModules != null) + VisitorModules.AddRange(customVisitorModules); Init(rootList); } From 8f16549b09a30e02cc632116e7a9375fed24b165 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 6 Dec 2022 14:13:52 +0100 Subject: [PATCH 010/294] Ongoing picking impl --- Examples/Complete/Picking/Core/Picking.cs | 2 +- src/Engine/Core/ScenePicker.cs | 250 +++++++++++++--------- src/Engine/Core/SceneRayCaster.cs | 12 +- 3 files changed, 162 insertions(+), 102 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 8d021a112..619f93d85 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -123,7 +123,7 @@ public override void RenderAFrame() { float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) + var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); if (newPick != null) Diagnostics.Debug(newPick.Node.Name); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 1c3e17233..b65a9fd86 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -5,10 +5,7 @@ using Fusee.Math.Core; using Fusee.Xene; using System; - -using System.Collections; using System.Collections.Generic; -using System.Linq; namespace Fusee.Engine.Core @@ -48,7 +45,14 @@ public class PickResult /// The clip position /// public float3 ClipPos; + } + public class MeshPickResult : PickResult + { + public int Triangle; + public float U; + public float V; + public float DistanceFromOrigin; } /// @@ -121,6 +125,8 @@ public Cull CullMode set => _cullMode.Tos = value; } + public PickComponent? CurrentPickComp; + /// /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. /// @@ -132,13 +138,18 @@ public PickerState() RegisterState(_canvasXForm); RegisterState(_cullMode); } - }; + } /// /// The current view matrix. /// public float4x4 View { get; private set; } + /// + /// The pick position on the screen. + /// + public float2 PickPosClip { get; set; } + /// /// The current projection matrix. /// @@ -155,13 +166,15 @@ public PickerState() /// /// The constructor to initialize a new ScenePicker. /// + /// /// The to pick from. - public ScenePicker(SceneContainer scene, IEnumerable customPickModule = null) + public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable customPickModule = null) : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; View = float4x4.Identity; Projection = float4x4.Identity; + State.CullMode = cullMode; } /// @@ -172,15 +185,14 @@ protected override void InitState() base.InitState(); State.Model = float4x4.Identity; State.CanvasXForm = float4x4.Identity; - State.CullMode = Cull.None; // todo: set via camera visitor } /// /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// /// The pick position. - /// The width of the current canvas, gets overwritte if a is bound - /// The height of the current canvas, gets overwritte if a is bound + /// The width of the current canvas, gets overwrite if a is bound + /// The height of the current canvas, gets overwrite if a is bound /// public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { @@ -246,6 +258,8 @@ public void UpdateCamera(Camera cam) Projection = CurrentCamera.RenderTexture != null ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); + + } /// @@ -466,16 +480,16 @@ public void RenderTransform(Transform transform) State.Model *= transform.Matrix; } - - ///// - ///// Creates pick results from a given point cloud if it is within the pick position. - ///// - ///// The given point cloud. - //[VisitMethod] - //public void PickMesh(PointCloudComponent pc) - //{ - // // TODO: Impl - //} + /// + /// Handles custom pick component with pick layer and custom picking methods. + /// If is not active, the picking is being skipped + /// + /// + [VisitMethod] + public void HandlePickComponent(PickComponent comp) + { + State.CurrentPickComp = comp; + } /// /// Creates pick results from a given mesh if it is within the pick position. @@ -484,118 +498,162 @@ public void RenderTransform(Transform transform) [VisitMethod] public void PickMesh(Mesh mesh) { + if (State.CurrentPickComp?.Active == false) return; + if (!mesh.Active) return; + if (mesh == null) return; + // TODO (MR): + // Custom picking method + // Geometry shader + // - var mvp = Projection * View * State.Model; - - if (mesh != null && (mesh.MeshType == PrimitiveType.Triangles || - mesh.MeshType == PrimitiveType.TriangleFan || - mesh.MeshType == PrimitiveType.TriangleStrip)) + switch (mesh.MeshType) { - PickTriangleGeometry(mesh, mvp); + case PrimitiveType.Triangles: + case PrimitiveType.TriangleFan: + case PrimitiveType.TriangleStrip: + PickTriangleGeometry(mesh, Projection, View); + break; + case PrimitiveType.Lines: + PickLineGeometry(mesh); + break; + default: + Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); + break; } - else if (mesh?.MeshType == PrimitiveType.Lines) + } + + private void PickLineGeometry(Mesh mesh) + { + var mvp = Projection * View * State.Model; + var matOfNode = CurrentNode.GetComponent(); + if (matOfNode == null) { - var matOfNode = CurrentNode.GetComponent(); - if (matOfNode == null) - { - Diagnostics.Debug("No shader effect for line renderer found!"); - return; - } - var thicknessFromShader = matOfNode.GetFxParam("Thickness"); + Diagnostics.Debug("No shader effect for line renderer found!"); + return; + } + var thicknessFromShader = matOfNode.GetFxParam("Thickness"); - if (mesh.Triangles == null) return; - if (mesh.Vertices == null) return; - if (CurrentCamera == null) - { - Diagnostics.Warn("No camera found in SceneGraph, no picking possible"); - return; - } + if (mesh.Triangles == null) return; + if (mesh.Vertices == null) return; + if (CurrentCamera == null) + { + Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); + return; + } - for (var i = 0; i < mesh.Triangles.Length; i += 2) - { - var viewportHeight = CurrentCamera.Viewport.w; - var thickness = (thicknessFromShader / viewportHeight); + for (var i = 0; i < mesh.Triangles.Length; i += 2) + { + var viewportHeight = CurrentCamera.Viewport.w; + var thickness = (thicknessFromShader / viewportHeight); - var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; - var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = PickPosClip; + var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; + var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; + var pt0 = PickPosClip; - // Line Eq = ax + by + c = 0 - // A = (y1 - y2) - // B = (x2 - x1) - // C = (x1 * y2 - x2 * y1) + // Line Eq = ax + by + c = 0 + // A = (y1 - y2) + // B = (x2 - x1) + // C = (x1 * y2 - x2 * y1) - // dist(line, pt) = |Ax + By + C| / A² + B² - var a = pt1.y - pt2.y; - var b = pt2.x - pt1.x; - var c = pt1.x * pt2.y - pt2.x * pt1.y; + // dist(line, pt) = |Ax + By + C| / A² + B² + var a = pt1.y - pt2.y; + var b = pt2.x - pt1.x; + var c = (pt1.x * pt2.y) - (pt2.x * pt1.y); - var d = (MathF.Abs(a * pt0.x + b * pt0.y + c) / (a * a + b * b)); + var d = MathF.Abs((a * pt0.x) + (b * pt0.y) + c) / ((a * a) + (b * b)); - if (d <= thickness) + if (d <= thickness) + { + YieldItem(new PickResult { - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), - View = View, - Projection = Projection - }); - } + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), + View = View, + Projection = Projection + }); } } } - private void PickTriangleGeometry(Mesh mesh, float4x4 mvp) + + private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 viewMatrix) { + if (mesh == null) return; if (mesh.Triangles == null) return; if (mesh.Vertices == null) return; - for (var i = 0; i < mesh.Triangles.Length; i += 3) + if (mesh.BoundingBox == default) { - // a, b c: current triangle's vertices in clip coordinates - var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - a = float4x4.TransformPerspective(mvp, a); + Diagnostics.Warn($"Current bounding box of {mesh} is default while mesh is being picked. Generating box ..."); + mesh.BoundingBox = new(mesh.Vertices.AsReadOnlySpan); + } - var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - b = float4x4.TransformPerspective(mvp, b); + if (mesh.BoundingBox.Size.x <= 0 || mesh.BoundingBox.Size.y <= 0 || mesh.BoundingBox.Size.z <= 0) + { + Diagnostics.Warn($"Current bounding box of {mesh} is smaller or equal to zero. Forcing a thickness in zero direction of >= float.Epsilon"); + var maxX = mesh.BoundingBox.Size.x <= 0 ? float.Epsilon : mesh.BoundingBox.max.x; + var maxY = mesh.BoundingBox.Size.y <= 0 ? float.Epsilon : mesh.BoundingBox.max.y; + var maxZ = mesh.BoundingBox.Size.z <= 0 ? float.Epsilon : mesh.BoundingBox.max.z; - var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - c = float4x4.TransformPerspective(mvp, c); + var minX = mesh.BoundingBox.Size.x <= 0 ? 0 : mesh.BoundingBox.min.x; + var minY = mesh.BoundingBox.Size.y <= 0 ? 0 : mesh.BoundingBox.min.y; + var minZ = mesh.BoundingBox.Size.z <= 0 ? 0 : mesh.BoundingBox.min.z; - // Point-in-Triangle-Test - if (float2.PointInTriangle(a.xy, b.xy, c.xy, PickPosClip, out var u, out var v)) - { - var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + mesh.BoundingBox = new AABBf(new float3(minX, minY, minZ), new float3(maxX, maxY, maxZ)); + } + + + var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); + var box = State.Model * mesh.BoundingBox; + if (!box.IntersectRay(ray)) return; + + for (int i = 0; i < mesh.Triangles.Length; i += 3) + { + // Vertices of the picked triangle in world space + var a = new float3(mesh.Vertices[(int)mesh.Triangles[i + 0]]); + a = float4x4.Transform(State.Model, a); + + var b = new float3(mesh.Vertices[(int)mesh.Triangles[i + 1]]); + b = float4x4.Transform(State.Model, b); + + var c = new float3(mesh.Vertices[(int)mesh.Triangles[i + 2]]); + c = float4x4.Transform(State.Model, c); + + // Normal of the plane defined by a, b, and c. + var n = float3.Normalize(float3.Cross(a - c, b - c)); + + // Distance between "Origin" and the plane abc when following the Direction. + var distance = -float3.Dot(ray.Origin - a, n) / float3.Dot(ray.Direction, n); - if (pickPos.z >= -1 && pickPos.z <= 1) + if (distance < 0) + continue; + + // Position of the intersection point between ray and plane. + var point = ray.Origin + (ray.Direction * distance); + + if (float3.PointInTriangle(a, b, c, point, out float u, out float v)) + { + if (State.CullMode == Cull.None || (State.CullMode == Cull.Clockwise) == (float3.Dot(n, ray.Direction) < 0)) { - if (State.CullMode == Cull.None || float2.IsTriangleCW(a.xy, b.xy, c.xy) == (State.CullMode == Cull.Clockwise)) + YieldItem(new MeshPickResult { - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - View = View, - ClipPos = float4x4.TransformPerspective(Projection * View, State.Model.Translation()), - Projection = Projection - }); - } + Mesh = mesh, + Node = CurrentNode, + Triangle = i, + Model = State.Model, + U = u, + V = v, + DistanceFromOrigin = distance + }); } } } } - /// - /// The pick position on the screen. - /// - public float2 PickPosClip { get; set; } - #endregion } diff --git a/src/Engine/Core/SceneRayCaster.cs b/src/Engine/Core/SceneRayCaster.cs index 3d4c21d32..2e10a5650 100644 --- a/src/Engine/Core/SceneRayCaster.cs +++ b/src/Engine/Core/SceneRayCaster.cs @@ -200,11 +200,13 @@ public IEnumerable RayPick(RenderContext rc, float2 pickPos) foreach (var camRes in cams) { - Rectangle camRect = new(); - camRect.Left = (int)(camRes.Camera.Viewport.x * rc.ViewportWidth / 100); - camRect.Top = (int)(camRes.Camera.Viewport.y * rc.ViewportHeight / 100); - camRect.Right = (int)(camRes.Camera.Viewport.z * rc.ViewportWidth) / 100 + camRect.Left; - camRect.Bottom = (int)(camRes.Camera.Viewport.w * rc.ViewportHeight) / 100 + camRect.Top; + Rectangle camRect = new() + { + Left = (int)(camRes.Camera.Viewport.x * rc.ViewportWidth / 100), + Top = (int)(camRes.Camera.Viewport.y * rc.ViewportHeight / 100) + }; + camRect.Right = ((int)(camRes.Camera.Viewport.z * rc.ViewportWidth) / 100) + camRect.Left; + camRect.Bottom = ((int)(camRes.Camera.Viewport.w * rc.ViewportHeight) / 100) + camRect.Top; if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) continue; From 04dbd01d00cf9ef348b0e03d10cc377620279ca0 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 6 Dec 2022 17:00:58 +0100 Subject: [PATCH 011/294] Preparation for fixing XForm et al. RayCast vs. MVP ViewTransform Picking --- Examples/Complete/Picking/Core/Picking.cs | 60 +++++++++++------------ src/Engine/Core/ScenePicker.cs | 4 +- src/Engine/GUI/SceneInteractionHandler.cs | 5 ++ 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 619f93d85..9b024e40b 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -48,7 +48,7 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, RC.CurrentRenderState.CullMode); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler @@ -119,35 +119,35 @@ public override void RenderAFrame() _sceneRenderer.Render(RC); //Picking - if (_pick) - { - float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - - var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) - .FirstOrDefault(); - if (newPick != null) - Diagnostics.Debug(newPick.Node.Name); - - if (newPick?.Node != _currentPick?.Node) - { - if (_currentPick != null) - { - var ef = _currentPick.Node.GetComponent(); - ef.SurfaceInput.Albedo = _oldColor; - } - - if (newPick != null) - { - var ef = newPick.Node.GetComponent(); - _oldColor = ef.SurfaceInput.Albedo; - ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; - } - - _currentPick = newPick; - } - - _pick = false; - } + //if (_pick) + //{ + // float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); + + // var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) + // .FirstOrDefault(); + // if (newPick != null) + // Diagnostics.Debug(newPick.Node.Name); + + // if (newPick?.Node != _currentPick?.Node) + // { + // if (_currentPick != null) + // { + // var ef = _currentPick.Node.GetComponent(); + // ef.SurfaceInput.Albedo = _oldColor; + // } + + // if (newPick != null) + // { + // var ef = newPick.Node.GetComponent(); + // _oldColor = ef.SurfaceInput.Albedo; + // ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; + // } + + // _currentPick = newPick; + // } + + // _pick = false; + //} _guiRenderer.Render(RC); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index b65a9fd86..936099899 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -506,7 +506,7 @@ public void PickMesh(Mesh mesh) // TODO (MR): // Custom picking method // Geometry shader - // + // Render XForm (usually MVP based not ray traced ...) switch (mesh.MeshType) { @@ -609,7 +609,7 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); var box = State.Model * mesh.BoundingBox; - if (!box.IntersectRay(ray)) return; + //if (!box.IntersectRay(ray)) return; for (int i = 0; i < mesh.Triangles.Length; i += 3) { diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index e7b7d9d61..c00b4704a 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -70,6 +70,11 @@ public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int can var pickResNodes = pickResults.Select(x => x.Node).ToList(); var firstPickRes = pickResults.FirstOrDefault(); + if (pickResults.Count > 0) + { + var t = 0; + } + _pickRes = null; if (firstPickRes != null) From 93a157903239f70fba943b0a481d0526113f79c8 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 7 Dec 2022 12:04:46 +0100 Subject: [PATCH 012/294] Added custom picking method for GUI elements --- src/Engine/Core/Scene/PickComponent.cs | 10 ++++-- src/Engine/Core/ScenePicker.cs | 16 +++++++-- src/Engine/GUI/FuseeGuiHelper.cs | 44 +++++++++++++++++++++++ src/Engine/GUI/SceneInteractionHandler.cs | 5 --- 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs index 1f3048dd8..3678e04a9 100644 --- a/src/Engine/Core/Scene/PickComponent.cs +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -1,4 +1,5 @@ -using System; +using Fusee.Math.Core; +using System; namespace Fusee.Engine.Core.Scene { @@ -13,9 +14,12 @@ public class PickComponent : SceneComponent public int PickLayer { get; set; } /// - /// Posibility to deposit a custom method on how to pick the following mesh(es) + /// Possibility to deposit a custom method on how to pick the following mesh(es) /// Check visitor module for new mesh types + /// Passes current , + /// , + /// Pick position in clip space /// - public Func? CustomPickMethod; + public Func? CustomPickMethod; } } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 936099899..119053252 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -504,9 +504,21 @@ public void PickMesh(Mesh mesh) if (mesh == null) return; // TODO (MR): - // Custom picking method // Geometry shader - // Render XForm (usually MVP based not ray traced ...) + // PointCloud -> module + + if (State?.CurrentPickComp != null) + { + if (State?.CurrentPickComp?.CustomPickMethod != null) + { + var res = State?.CurrentPickComp?.CustomPickMethod(mesh, CurrentNode, State.Model, View, Projection, PickPosClip); + if (res != null) + { + YieldItem(res); + return; + } + } + } switch (mesh.MeshType) { diff --git a/src/Engine/GUI/FuseeGuiHelper.cs b/src/Engine/GUI/FuseeGuiHelper.cs index b3a4d410a..fb28f47f2 100644 --- a/src/Engine/GUI/FuseeGuiHelper.cs +++ b/src/Engine/GUI/FuseeGuiHelper.cs @@ -114,6 +114,50 @@ public static async Task CreateDefaultGuiAsync(RenderCanvas rc, ClearColor = false } } + }, new SceneNode() + { + Components = new List + { + new PickComponent() + { + CustomPickMethod = (mesh, currentNode, model, view, projection, pickPosClip) => + { + var mvp = projection * view * model; + + for (var i = 0; i < mesh.Triangles.Length; i += 3) + { + // a, b c: current triangle's vertices in clip coordinates + var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); + a = float4x4.TransformPerspective(mvp, a); + + var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); + b = float4x4.TransformPerspective(mvp, b); + + var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); + c = float4x4.TransformPerspective(mvp, c); + + // Point-in-Triangle-Test + if (float2.PointInTriangle(a.xy, b.xy, c.xy, pickPosClip, out var u, out var v)) + { + var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + + if (pickPos.z >= -1 && pickPos.z <= 1) + { + return new PickResult + { + Mesh = mesh, + Node = currentNode, + Model = model, + View = view, + Projection = projection + }; + } + } + } + return null; + } + } + } }, canvas } diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index c00b4704a..e7b7d9d61 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -70,11 +70,6 @@ public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int can var pickResNodes = pickResults.Select(x => x.Node).ToList(); var firstPickRes = pickResults.FirstOrDefault(); - if (pickResults.Count > 0) - { - var t = 0; - } - _pickRes = null; if (firstPickRes != null) From fd809ec69372ddb1c7b9443a4b6e14dff23f0177 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 7 Dec 2022 14:31:54 +0100 Subject: [PATCH 013/294] Wired all point cloud picking stuff, picking returns all octants --- .../Core/PointCloudPotree2Core.cs | 24 ++++++++++++++ src/Engine/Core/IPickerModule.cs | 3 -- src/Engine/Core/ScenePicker.cs | 23 +++++++++---- .../Core/Scene/PointCloudPickerModule.cs | 33 ++++++++++++++++--- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 98bca57ab..d8638914e 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace Fusee.Examples.PointCloudPotree2.Core { @@ -50,6 +51,8 @@ public bool ClosingRequested private Potree2Reader _potreeReader; private PotreeData _potreeData; + private ScenePicker _picker; + private readonly RenderContext _rc; public void OnLoadNewFile(object sender, EventArgs e) @@ -69,6 +72,12 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloud.Camera = _cam; _pointCloudNode.Components[3] = _pointCloud; + + // re-generate picker and octree + _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree) + }); } public PointCloudPotree2Core(RenderContext rc) @@ -157,6 +166,12 @@ public void Init() _sceneRenderer.VisitorModules.Add(new PointCloudRenderModule(_sceneRenderer.GetType() == typeof(SceneRendererForward))); _pointCloud.Camera = _cam; + + _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree) + }); + } // RenderAFrame is called once a frame @@ -227,6 +242,15 @@ public void Update(bool allowInput) _angleVelVert = 0; _camTransform.FpsView(_angleHorz, _angleVert, Input.Keyboard.WSAxis, Input.Keyboard.ADAxis, Time.DeltaTimeUpdate * 20); + + if (!_keys && Input.Mouse.LeftButton) + { + var width = _rc.ViewportWidth; + var height = _rc.ViewportHeight; + var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); + var result = _picker.Pick(pickPosClip, width, height).ToList(); + Console.WriteLine(result.Count > 0); + } } private void OnThresholdChanged(int newValue) diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs index de1cf30a8..c80e45c71 100644 --- a/src/Engine/Core/IPickerModule.cs +++ b/src/Engine/Core/IPickerModule.cs @@ -8,13 +8,10 @@ namespace Fusee.Engine.Core { public interface IPickerModule : IVisitorModule { - /// /// Sets the for this module. Pass the state from the base renderer. /// /// The state to set. public void SetState(PickerState state); - - public Func GetPickerState { get; set; } } } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 119053252..a273847ea 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -138,12 +138,22 @@ public PickerState() RegisterState(_canvasXForm); RegisterState(_cullMode); } + + /// + /// The current view matrix. + /// + public float4x4 View { get; set; } + + /// + /// The current projection matrix. + /// + public float4x4 Projection { get; set; } } /// /// The current view matrix. /// - public float4x4 View { get; private set; } + public float4x4 View { get; set; } /// /// The pick position on the screen. @@ -212,7 +222,7 @@ private void SetState() { foreach (var module in VisitorModules) { - ((IPickerModule)module).GetPickerState = () => State; + ((IPickerModule)module).SetState(State); } } @@ -259,7 +269,8 @@ public void UpdateCamera(Camera cam) ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); - + State.View = View; + State.Projection = Projection; } /// @@ -496,7 +507,7 @@ public void HandlePickComponent(PickComponent comp) /// /// The given Mesh. [VisitMethod] - public void PickMesh(Mesh mesh) + public void HandleMesh(Mesh mesh) { if (State.CurrentPickComp?.Active == false) return; @@ -504,7 +515,7 @@ public void PickMesh(Mesh mesh) if (mesh == null) return; // TODO (MR): - // Geometry shader + // Geometry shader -> PickComp? // PointCloud -> module if (State?.CurrentPickComp != null) @@ -621,7 +632,7 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); var box = State.Model * mesh.BoundingBox; - //if (!box.IntersectRay(ray)) return; + if (!box.IntersectRay(ray)) return; for (int i = 0; i < mesh.Triangles.Length; i += 3) { diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 763b813ef..11bb31915 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -6,15 +6,17 @@ using System; using System.Collections.Generic; using System.Text; +using Fusee.PointCloud.Core; using static Fusee.Engine.Core.ScenePicker; +using System.Linq; +using Microsoft.Extensions.Options; namespace Fusee.PointCloud.Core.Scene { public class PointCloudPickerModule : IPickerModule { private PickerState State; - - public Func GetPickerState { get; set; } + private PointCloudOctree _octree; /// /// Determines visible points of a point cloud (using the components ) and renders them. @@ -23,13 +25,36 @@ public class PointCloudPickerModule : IPickerModule [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { - State = GetPickerState?.Invoke(); Console.WriteLine($"MousePos: {State.PickPosClip}"); + + var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); + var tmpList = new List(); + + var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList); + + Console.WriteLine(allHitBoxes.Count > 0); + } - public PointCloudPickerModule() + private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) { + list.Add(node); + if (node.Children[0] != null) + { + foreach (var child in node.Children.Cast()) + { + if (child?.IsVisible == true && child.IntersectRay(ray)) + { + PickOctantRecursively(child, ray, list); + } + } + } + return list; + } + public PointCloudPickerModule(Common.IPointCloudOctree octree) + { + _octree = (PointCloudOctree)octree; } public void SetState(PickerState state) From a0b446c714b2d6aa97c965a94f96cda754daa6cc Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 12 Dec 2022 16:22:36 +0100 Subject: [PATCH 014/294] Basic lines_adjacency support --- src/Engine/Common/IRenderContextImp.cs | 7 ++- src/Engine/Core/Assets/lineAdjacency.geom | 63 +++++++++++++++++++ src/Engine/Core/Fusee.Engine.Core.csproj | 3 + src/Engine/Core/MakeEffect.cs | 31 +++++++++ src/Engine/Core/ScenePicker.cs | 2 +- .../Imp/Graphics/Desktop/RenderContextImp.cs | 1 + 6 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 src/Engine/Core/Assets/lineAdjacency.geom diff --git a/src/Engine/Common/IRenderContextImp.cs b/src/Engine/Common/IRenderContextImp.cs index bfe354d72..844b773d0 100644 --- a/src/Engine/Common/IRenderContextImp.cs +++ b/src/Engine/Common/IRenderContextImp.cs @@ -890,6 +890,11 @@ public enum PrimitiveType /// /// Relates to OpenGl GL_PATCHES. /// - Patches + Patches, + + /// + /// Relates to OpenGl GL_LINES_ADJACENCY. + /// + LineAdjacency } } \ No newline at end of file diff --git a/src/Engine/Core/Assets/lineAdjacency.geom b/src/Engine/Core/Assets/lineAdjacency.geom new file mode 100644 index 000000000..1efc4414a --- /dev/null +++ b/src/Engine/Core/Assets/lineAdjacency.geom @@ -0,0 +1,63 @@ +#version 460 core + +layout (lines_adjacency) in;// enables access to four vertices (line segment vertices, predecessor, successor) +layout (triangle_strip, max_vertices = 256) out; + +in vec4 vColor0[]; +out vec4 gColor; + +uniform float Thickness = 4;// just a test default +uniform ivec2 FUSEE_ViewportPx; +uniform mat4 FUSEE_MVP; +uniform bool EnableVertexColors = false; + +void drawSegment(float u_width, float u_height, float u_aspect_ratio, vec4 pos0, vec4 pos1) +{ + + vec2 ndc_a = pos0.xy / pos0.w; + vec2 ndc_b = pos1.xy / pos1.w; + + vec2 line_vector = ndc_b - ndc_a; + vec2 viewport_line_vector = line_vector * vec2(u_width, u_height); + vec2 dir = normalize(vec2(line_vector.x, line_vector.y * u_aspect_ratio)); + + float line_width = max(1.0, Thickness); + float line_length = length(viewport_line_vector); + + vec2 normal = vec2(-dir.y, dir.x); + vec2 normal_a = vec2(line_width/u_width, line_width/u_height) * normal; + vec2 normal_b = vec2(line_width/u_width, line_width/u_height) * normal; + + if(EnableVertexColors) + gColor = vColor0[0]; + gl_Position = vec4((ndc_a + normal_a) * pos0.w, pos0.zw); + EmitVertex(); + + if(EnableVertexColors) + gColor = vColor0[0]; + gl_Position = vec4((ndc_a - normal_a) * pos0.w, pos0.zw); + EmitVertex(); + + if(EnableVertexColors) + gColor = vColor0[1]; + gl_Position = vec4((ndc_b + normal_b) * pos1.w, pos1.zw); + EmitVertex(); + + if(EnableVertexColors) + gColor = vColor0[1]; + gl_Position = vec4((ndc_b - normal_b) * pos1.w, pos1.zw); + EmitVertex(); +} + +void main() +{ + float u_width = float(FUSEE_ViewportPx.x); + float u_height = float(FUSEE_ViewportPx.y); + float u_aspect_ratio = u_height / u_width; + + drawSegment(u_width, u_height, u_aspect_ratio, gl_in[0].gl_Position, gl_in[1].gl_Position); + drawSegment(u_width, u_height, u_aspect_ratio, gl_in[1].gl_Position, gl_in[2].gl_Position); + drawSegment(u_width, u_height, u_aspect_ratio, gl_in[2].gl_Position, gl_in[3].gl_Position); + + EndPrimitive(); +} \ No newline at end of file diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 33b1c9a3a..5921e0e13 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -40,6 +40,9 @@ + + PreserveNewest + PreserveNewest diff --git a/src/Engine/Core/MakeEffect.cs b/src/Engine/Core/MakeEffect.cs index f6b0bc902..9e2b210ff 100644 --- a/src/Engine/Core/MakeEffect.cs +++ b/src/Engine/Core/MakeEffect.cs @@ -55,6 +55,37 @@ public static Effect LineEffect(float lineThickness, float4 albedoColor, bool en return new ShaderEffect(uniformParameters, RenderStateSet.Default, vs, ps, gs); } + + /// + /// Generates a line shader which can be used with a with set to . /// Loads shader files via + /// For an asynchronous version use + /// + /// + /// + /// + /// + public static Effect LineEffectAdjacency(float lineThickness, float4 albedoColor, bool enableVertexColors = false) + { + var vs = AssetStorage.Get("line.vert"); + var gs = AssetStorage.Get("lineAdjacency.geom"); + var ps = AssetStorage.Get("line.frag"); + var uniformParameters = new List + { + new FxParamDeclaration + { Name = UniformNameDeclarations.ModelViewProjection, Value = float4x4.Identity }, + new FxParamDeclaration + { Name = UniformNameDeclarations.ModelView, Value = float4x4.Identity }, + new FxParamDeclaration + { Name = UniformNameDeclarations.Projection, Value = float4x4.Identity }, + new FxParamDeclaration { Name = "Thickness", Value = lineThickness }, + new FxParamDeclaration { Name = UniformNameDeclarations.ViewportPx, Value = int2.Zero }, + new FxParamDeclaration { Name = "Albedo", Value = albedoColor }, + new FxParamDeclaration { Name = "EnableVertexColors", Value = enableVertexColors } + }; + + return new ShaderEffect(uniformParameters, RenderStateSet.Default, vs, ps, gs); + } + /// /// Generates a line shader which can be used with a with set to . /// Loads shader files via diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 4ae31e3ad..a970cb64a 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -563,7 +563,7 @@ public void PickMesh(Mesh mesh) } } } - else if (mesh.MeshType == PrimitiveType.Lines) + else if (mesh.MeshType == PrimitiveType.Lines || mesh.MeshType == PrimitiveType.LineAdjacency) { var matOfNode = CurrentNode.GetComponent(); if (matOfNode == null) diff --git a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs index 74792d954..995d40916 100644 --- a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs +++ b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs @@ -2274,6 +2274,7 @@ public void Render(IMeshImp mr, IInstanceDataImp instanceData = null) Common.PrimitiveType.TriangleFan => OpenTK.Graphics.OpenGL.PrimitiveType.TriangleFan, Common.PrimitiveType.TriangleStrip => OpenTK.Graphics.OpenGL.PrimitiveType.TriangleStrip, Common.PrimitiveType.Quads => OpenTK.Graphics.OpenGL.PrimitiveType.Quads, + Common.PrimitiveType.LineAdjacency => OpenTK.Graphics.OpenGL.PrimitiveType.LinesAdjacency, _ => OpenTK.Graphics.OpenGL.PrimitiveType.Triangles, }; From ed699cabe8bfc6b0bf859dc44cd2c8081d183e14 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 13 Dec 2022 13:10:46 +0100 Subject: [PATCH 015/294] lineAdjacency geometry shader: added basic miter --- src/Engine/Core/Assets/lineAdjacency.geom | 65 ++++++++++++++--------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Engine/Core/Assets/lineAdjacency.geom b/src/Engine/Core/Assets/lineAdjacency.geom index 1efc4414a..a72601547 100644 --- a/src/Engine/Core/Assets/lineAdjacency.geom +++ b/src/Engine/Core/Assets/lineAdjacency.geom @@ -11,53 +11,66 @@ uniform ivec2 FUSEE_ViewportPx; uniform mat4 FUSEE_MVP; uniform bool EnableVertexColors = false; -void drawSegment(float u_width, float u_height, float u_aspect_ratio, vec4 pos0, vec4 pos1) +void main() { + float u_width = float(FUSEE_ViewportPx.x); + float u_height = float(FUSEE_ViewportPx.y); + float u_aspect_ratio = u_height / u_width; + vec2 Viewport = vec2(u_width, u_height); + float line_width = max(1.0, Thickness); + vec4 pos0 = gl_in[0].gl_Position; + vec4 pos1 = gl_in[1].gl_Position; + vec4 pos2 = gl_in[2].gl_Position; + vec4 pos3 = gl_in[3].gl_Position; - vec2 ndc_a = pos0.xy / pos0.w; - vec2 ndc_b = pos1.xy / pos1.w; + //ndc + vec2 ndc0 = gl_in[0].gl_Position.xy / gl_in[0].gl_Position.w; + vec2 ndc1 = gl_in[1].gl_Position.xy / gl_in[1].gl_Position.w; + vec2 ndc2 = gl_in[2].gl_Position.xy / gl_in[2].gl_Position.w; + vec2 ndc3 = gl_in[3].gl_Position.xy / gl_in[3].gl_Position.w; - vec2 line_vector = ndc_b - ndc_a; - vec2 viewport_line_vector = line_vector * vec2(u_width, u_height); - vec2 dir = normalize(vec2(line_vector.x, line_vector.y * u_aspect_ratio)); + //direction of the three segments (previous, current, next) */ + vec2 line_vector0 = ndc1 - ndc0; + vec2 line_vector1 = ndc2 - ndc1; + vec2 line_vector2 = ndc3 - ndc2; + vec2 dir0 = normalize(vec2(line_vector0.x, line_vector0.y * u_aspect_ratio)); + vec2 dir1 = normalize(vec2(line_vector1.x, line_vector1.y * u_aspect_ratio)); + vec2 dir2 = normalize(vec2(line_vector2.x, line_vector2.y * u_aspect_ratio)); - float line_width = max(1.0, Thickness); - float line_length = length(viewport_line_vector); + //normals of the three segments (previous, current, next) */ + vec2 n0 = vec2( -dir0.y, dir0.x ); + vec2 n1 = vec2( -dir1.y, dir1.x ); + vec2 n2 = vec2( -dir2.y, dir2.x ); + + // determine miter lines by averaging the normals of the 2 segments */ + vec2 miter_a = normalize( n0 + n1 );// miter at start of current segment + vec2 miter_b = normalize( n1 + n2 );// miter at end of current segment - vec2 normal = vec2(-dir.y, dir.x); - vec2 normal_a = vec2(line_width/u_width, line_width/u_height) * normal; - vec2 normal_b = vec2(line_width/u_width, line_width/u_height) * normal; + n0 = vec2(line_width/u_width, line_width/u_height) * n0; + n1 = vec2(line_width/u_width, line_width/u_height) * n1; + n2 = vec2(line_width/u_width, line_width/u_height) * n2; + miter_a = vec2(line_width/u_width, line_width/u_height) * miter_a; + miter_b = vec2(line_width/u_width, line_width/u_height) * miter_b; if(EnableVertexColors) gColor = vColor0[0]; - gl_Position = vec4((ndc_a + normal_a) * pos0.w, pos0.zw); + gl_Position = vec4((ndc1 + miter_a) * pos1.w, pos1.zw); EmitVertex(); if(EnableVertexColors) gColor = vColor0[0]; - gl_Position = vec4((ndc_a - normal_a) * pos0.w, pos0.zw); + gl_Position = vec4((ndc1 - miter_a) * pos1.w, pos1.zw); EmitVertex(); if(EnableVertexColors) gColor = vColor0[1]; - gl_Position = vec4((ndc_b + normal_b) * pos1.w, pos1.zw); + gl_Position = vec4((ndc2 + miter_b) * pos2.w, pos2.zw); EmitVertex(); if(EnableVertexColors) gColor = vColor0[1]; - gl_Position = vec4((ndc_b - normal_b) * pos1.w, pos1.zw); + gl_Position = vec4((ndc2 - miter_b) * pos2.w, pos2.zw); EmitVertex(); -} - -void main() -{ - float u_width = float(FUSEE_ViewportPx.x); - float u_height = float(FUSEE_ViewportPx.y); - float u_aspect_ratio = u_height / u_width; - - drawSegment(u_width, u_height, u_aspect_ratio, gl_in[0].gl_Position, gl_in[1].gl_Position); - drawSegment(u_width, u_height, u_aspect_ratio, gl_in[1].gl_Position, gl_in[2].gl_Position); - drawSegment(u_width, u_height, u_aspect_ratio, gl_in[2].gl_Position, gl_in[3].gl_Position); EndPrimitive(); } \ No newline at end of file From 72deb62d602a2fd8932e589a7580def1ee8ea104 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 13 Dec 2022 15:55:29 +0100 Subject: [PATCH 016/294] lineAdjacency geometry shader: calc miter length --- src/Engine/Core/Assets/lineAdjacency.geom | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Engine/Core/Assets/lineAdjacency.geom b/src/Engine/Core/Assets/lineAdjacency.geom index a72601547..bb63ca7e2 100644 --- a/src/Engine/Core/Assets/lineAdjacency.geom +++ b/src/Engine/Core/Assets/lineAdjacency.geom @@ -37,20 +37,28 @@ void main() vec2 dir1 = normalize(vec2(line_vector1.x, line_vector1.y * u_aspect_ratio)); vec2 dir2 = normalize(vec2(line_vector2.x, line_vector2.y * u_aspect_ratio)); - //normals of the three segments (previous, current, next) */ + //normals of the three segments (previous, current, next) vec2 n0 = vec2( -dir0.y, dir0.x ); vec2 n1 = vec2( -dir1.y, dir1.x ); vec2 n2 = vec2( -dir2.y, dir2.x ); - // determine miter lines by averaging the normals of the 2 segments */ + // determine miter lines by averaging the normals of the 2 segments vec2 miter_a = normalize( n0 + n1 );// miter at start of current segment vec2 miter_b = normalize( n1 + n2 );// miter at end of current segment + // determine the length of the miter by projecting it onto normal and then inverse it + float an1 = dot(miter_a, n1); + float bn1 = dot(miter_b, n2); + if (an1==0) an1 = 1; + if (bn1==0) bn1 = 1; + float length_a = line_width / an1; + float length_b = line_width / bn1; + n0 = vec2(line_width/u_width, line_width/u_height) * n0; n1 = vec2(line_width/u_width, line_width/u_height) * n1; n2 = vec2(line_width/u_width, line_width/u_height) * n2; - miter_a = vec2(line_width/u_width, line_width/u_height) * miter_a; - miter_b = vec2(line_width/u_width, line_width/u_height) * miter_b; + miter_a = vec2(length_a/u_width, length_a/u_height) * miter_a; + miter_b = vec2(length_b/u_width, length_b/u_height) * miter_b; if(EnableVertexColors) gColor = vColor0[0]; From b5f5881126d7c7ab2ff0cfec9b5da247f23d9670 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 20 Dec 2022 14:31:32 +0100 Subject: [PATCH 017/294] lineAdjacency geometry shader: limited meter length --- src/Engine/Core/Assets/lineAdjacency.geom | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Engine/Core/Assets/lineAdjacency.geom b/src/Engine/Core/Assets/lineAdjacency.geom index bb63ca7e2..f134918ab 100644 --- a/src/Engine/Core/Assets/lineAdjacency.geom +++ b/src/Engine/Core/Assets/lineAdjacency.geom @@ -51,8 +51,19 @@ void main() float bn1 = dot(miter_b, n2); if (an1==0) an1 = 1; if (bn1==0) bn1 = 1; + float length_a = line_width / an1; + if( dot(dir0, dir1 ) < -0.1/*MiterLimit*/) + { + miter_a = n1; + length_a = Thickness; + } + float length_b = line_width / bn1; + if( dot(dir1, dir2) < -0.1/*MiterLimit*/) { + miter_b = n1; + length_b = Thickness; + } n0 = vec2(line_width/u_width, line_width/u_height) * n0; n1 = vec2(line_width/u_width, line_width/u_height) * n1; From 6dff1fd2ccf08a0316da41b9f4623b734d8c6f23 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 23 Dec 2022 14:50:20 +0100 Subject: [PATCH 018/294] Transform.RotateGlobal: inverted multiplication order --- src/Engine/Core/Scene/SceneExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Core/Scene/SceneExtensions.cs b/src/Engine/Core/Scene/SceneExtensions.cs index 39cdffd74..6dd626f90 100644 --- a/src/Engine/Core/Scene/SceneExtensions.cs +++ b/src/Engine/Core/Scene/SceneExtensions.cs @@ -509,7 +509,7 @@ public static void Rotate(this Transform tc, float4x4 rotationMtx) /// Global (accumulated) rotation of the parent node. public static void RotateGlobal(this Transform tc, QuaternionF rotation, QuaternionF parentGlobalRot) { - tc.RotationQuaternion *= parentGlobalRot * rotation * QuaternionF.Invert(parentGlobalRot); + tc.RotationQuaternion *= QuaternionF.Invert(parentGlobalRot) * rotation * parentGlobalRot; } /// From 11b248cbb3e3f8068e6e2873de17e13d704e3951 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 9 Jan 2023 13:43:34 +0100 Subject: [PATCH 019/294] First basic octant picking --- .../Core/PointCloudPotree2Core.cs | 16 +++++--- src/Engine/Core/IPickerModule.cs | 2 + src/Engine/Core/ScenePicker.cs | 19 +++++++-- .../Core/Scene/PointCloudPickerModule.cs | 39 +++++++++++++++---- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index d8638914e..4f70e561b 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -1,4 +1,5 @@ using Fusee.Base.Common; +using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; @@ -25,7 +26,7 @@ public bool ClosingRequested } private bool _closingRequested; - public RenderMode PointRenderMode = RenderMode.StaticMesh; + public RenderMode PointRenderMode = RenderMode.DynamicMesh; public string AssetsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); private static float _angleHorz, _angleVert, _angleVelHorz, _angleVelVert; @@ -76,7 +77,7 @@ public void OnLoadNewFile(object sender, EventArgs e) // re-generate picker and octree _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() { - new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree) + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); } @@ -167,9 +168,14 @@ public void Init() _pointCloud.Camera = _cam; + //_picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + //{ + // new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree, null) + //}); + _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() { - new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree) + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); } @@ -248,8 +254,8 @@ public void Update(bool allowInput) var width = _rc.ViewportWidth; var height = _rc.ViewportHeight; var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); - var result = _picker.Pick(pickPosClip, width, height).ToList(); - Console.WriteLine(result.Count > 0); + var result = _picker?.Pick(pickPosClip, width, height).ToList(); + if (result != null) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); } } diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs index c80e45c71..57d20d694 100644 --- a/src/Engine/Core/IPickerModule.cs +++ b/src/Engine/Core/IPickerModule.cs @@ -13,5 +13,7 @@ public interface IPickerModule : IVisitorModule /// /// The state to set. public void SetState(PickerState state); + + public PickResult PickResult { get; set; } } } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index a273847ea..9dd34a8d2 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -6,7 +6,7 @@ using Fusee.Xene; using System; using System.Collections.Generic; - +using System.Linq; namespace Fusee.Engine.Core { @@ -212,7 +212,19 @@ public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasH State.PickPosClip = pickPos; SetState(); - return Viserate(); + var res = Viserate().ToList(); + res.AddRange(CheckVisitorModuleResults()); + return res; + } + + private IEnumerable CheckVisitorModuleResults() + { + foreach (var module in VisitorModules) + { + var m = (IPickerModule)module; + if (m.PickResult != null) + yield return m.PickResult; + } } /// @@ -222,7 +234,8 @@ private void SetState() { foreach (var module in VisitorModules) { - ((IPickerModule)module).SetState(State); + var m = (IPickerModule)module; + m.SetState(State); } } diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 11bb31915..b22c7eab8 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -10,13 +10,25 @@ using static Fusee.Engine.Core.ScenePicker; using System.Linq; using Microsoft.Extensions.Options; +using Fusee.PointCloud.Common; +using Fusee.Base.Core; namespace Fusee.PointCloud.Core.Scene { + public class PointCloudPickResult : PickResult + { + public OctantId OctandID; + + } + public class PointCloudPickerModule : IPickerModule { private PickerState State; private PointCloudOctree _octree; + private IPointCloudImp _pcImp; + + + public PickResult PickResult { get; set; } /// /// Determines visible points of a point cloud (using the components ) and renders them. @@ -25,15 +37,24 @@ public class PointCloudPickerModule : IPickerModule [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { - Console.WriteLine($"MousePos: {State.PickPosClip}"); + if (!pointCloud.Active) return; var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); var tmpList = new List(); - - var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList); - - Console.WriteLine(allHitBoxes.Count > 0); - + var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); + if (allHitBoxes != null && allHitBoxes.Any()) + { + var mvp = State.Projection * State.View * State.Model; + PickResult = new PointCloudPickResult + { + Node = null, + Projection = State.Projection, + View = State.View, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), + OctandID = allHitBoxes.ElementAt(0).OctId, + }; + } } private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) @@ -52,9 +73,13 @@ private List PickOctantRecursively(PointCloudOctant node, RayD return list; } - public PointCloudPickerModule(Common.IPointCloudOctree octree) + public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp) { + if (pcImp == null) + Diagnostics.Warn("No per point picking possible, no PointCloud type loaded"); + _octree = (PointCloudOctree)octree; + _pcImp = pcImp; } public void SetState(PickerState state) From 8fd29bd802d7550afdadade9f626d83187bcb842 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 9 Jan 2023 15:57:35 +0100 Subject: [PATCH 020/294] WIP ongoing refactoring for GUI picking --- Examples/Complete/Picking/Core/Picking.cs | 2 +- src/Engine/Core/ScenePicker.cs | 54 ++++++-------- src/Engine/GUI/FuseeGuiHelper.cs | 89 ++++++++++++----------- src/Engine/GUI/SceneInteractionHandler.cs | 4 +- src/Math/Core/Rayf.cs | 3 +- 5 files changed, 73 insertions(+), 79 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 9b024e40b..eac3e10a8 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -116,7 +116,7 @@ public override void Update() // RenderAFrame is called once a frame public override void RenderAFrame() { - _sceneRenderer.Render(RC); + //_sceneRenderer.Render(RC); //Picking //if (_pick) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 9dd34a8d2..bb67e0357 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -150,24 +150,14 @@ public PickerState() public float4x4 Projection { get; set; } } - /// - /// The current view matrix. - /// - public float4x4 View { get; set; } - /// /// The pick position on the screen. /// public float2 PickPosClip { get; set; } - /// - /// The current projection matrix. - /// - public float4x4 Projection { get; private set; } - - public float4x4 InvView => View.Invert(); + public float4x4 InvView => State.View.Invert(); - public float4x4 InvProjection => Projection.Invert(); + public float4x4 InvProjection => State.Projection.Invert(); public Camera? CurrentCamera { get; private set; } @@ -182,8 +172,6 @@ public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable< : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; - View = float4x4.Identity; - Projection = float4x4.Identity; State.CullMode = cullMode; } @@ -194,6 +182,8 @@ protected override void InitState() { base.InitState(); State.Model = float4x4.Identity; + State.View = float4x4.Identity; + State.Projection = float4x4.Identity; State.CanvasXForm = float4x4.Identity; } @@ -206,14 +196,17 @@ protected override void InitState() /// public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { - PickPosClip = pickPos; + _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; + PickPosClip = pickPos; State.PickPosClip = pickPos; + SetState(); var res = Viserate().ToList(); res.AddRange(CheckVisitorModuleResults()); + return res; } @@ -253,7 +246,7 @@ public void UpdateCamera(Camera cam) CurrentCamera = cam; var view = State.Model; - var scale = float4x4.GetScale(View); + var scale = float4x4.GetScale(State.View); if (scale.x != 1) { @@ -276,14 +269,12 @@ public void UpdateCamera(Camera cam) view.M33 /= scale.z; } - View = view.Invert(); + State.View = view.Invert(); // TODO(mr): TEST Renderlayer - Projection = CurrentCamera.RenderTexture != null + State.Projection = CurrentCamera.RenderTexture != null ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); - State.View = View; - State.Projection = Projection; } /// @@ -311,7 +302,7 @@ public void RenderCanvasTransform(CanvasTransform ctc) } else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) { - var invProj = float4x4.Invert(Projection); + var invProj = float4x4.Invert(State.Projection); var frustumCorners = new float4[4]; @@ -535,7 +526,7 @@ public void HandleMesh(Mesh mesh) { if (State?.CurrentPickComp?.CustomPickMethod != null) { - var res = State?.CurrentPickComp?.CustomPickMethod(mesh, CurrentNode, State.Model, View, Projection, PickPosClip); + var res = State?.CurrentPickComp?.CustomPickMethod(mesh, CurrentNode, State.Model, State.View, State.Projection, PickPosClip); if (res != null) { YieldItem(res); @@ -549,7 +540,7 @@ public void HandleMesh(Mesh mesh) case PrimitiveType.Triangles: case PrimitiveType.TriangleFan: case PrimitiveType.TriangleStrip: - PickTriangleGeometry(mesh, Projection, View); + PickTriangleGeometry(mesh, State.Projection, State.View); break; case PrimitiveType.Lines: PickLineGeometry(mesh); @@ -562,7 +553,7 @@ public void HandleMesh(Mesh mesh) private void PickLineGeometry(Mesh mesh) { - var mvp = Projection * View * State.Model; + var mvp = State.Projection * State.View * State.Model; var matOfNode = CurrentNode.GetComponent(); if (matOfNode == null) { @@ -586,7 +577,7 @@ private void PickLineGeometry(Mesh mesh) var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = PickPosClip; + var pt0 = State.PickPosClip; // Line Eq = ax + by + c = 0 // A = (y1 - y2) @@ -607,9 +598,9 @@ private void PickLineGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(Projection * View, CurrentNode.GetTransform().Translation), - View = View, - Projection = Projection + ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), + View = State.View, + Projection = State.Projection }); } } @@ -642,10 +633,13 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 mesh.BoundingBox = new AABBf(new float3(minX, minY, minZ), new float3(maxX, maxY, maxZ)); } - var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); + Diagnostics.Debug($"Origin: {ray.Origin}, Dir: {ray.Direction}, Inv: {ray.Inverse}"); + var box = State.Model * mesh.BoundingBox; - if (!box.IntersectRay(ray)) return; + Diagnostics.Debug($"Box: {box.Center}"); + if (!box.IntersectRay(ray)) + return; for (int i = 0; i < mesh.Triangles.Length; i += 3) { diff --git a/src/Engine/GUI/FuseeGuiHelper.cs b/src/Engine/GUI/FuseeGuiHelper.cs index fb28f47f2..06988bf99 100644 --- a/src/Engine/GUI/FuseeGuiHelper.cs +++ b/src/Engine/GUI/FuseeGuiHelper.cs @@ -114,51 +114,52 @@ public static async Task CreateDefaultGuiAsync(RenderCanvas rc, ClearColor = false } } - }, new SceneNode() - { - Components = new List - { - new PickComponent() - { - CustomPickMethod = (mesh, currentNode, model, view, projection, pickPosClip) => - { - var mvp = projection * view * model; - - for (var i = 0; i < mesh.Triangles.Length; i += 3) - { - // a, b c: current triangle's vertices in clip coordinates - var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - a = float4x4.TransformPerspective(mvp, a); - - var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - b = float4x4.TransformPerspective(mvp, b); - - var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - c = float4x4.TransformPerspective(mvp, c); - - // Point-in-Triangle-Test - if (float2.PointInTriangle(a.xy, b.xy, c.xy, pickPosClip, out var u, out var v)) - { - var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); - - if (pickPos.z >= -1 && pickPos.z <= 1) - { - return new PickResult - { - Mesh = mesh, - Node = currentNode, - Model = model, - View = view, - Projection = projection - }; - } - } - } - return null; - } - } - } }, + //new SceneNode() + //{ + // Components = new List + // { + // new PickComponent() + // { + // CustomPickMethod = (mesh, currentNode, model, view, projection, pickPosClip) => + // { + // var mvp = projection * view * model; + + // for (var i = 0; i < mesh.Triangles.Length; i += 3) + // { + // // a, b c: current triangle's vertices in clip coordinates + // var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); + // a = float4x4.TransformPerspective(mvp, a); + + // var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); + // b = float4x4.TransformPerspective(mvp, b); + + // var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); + // c = float4x4.TransformPerspective(mvp, c); + + // // Point-in-Triangle-Test + // if (float2.PointInTriangle(a.xy, b.xy, c.xy, pickPosClip, out var u, out var v)) + // { + // var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); + + // if (pickPos.z >= -1 && pickPos.z <= 1) + // { + // return new PickResult + // { + // Mesh = mesh, + // Node = currentNode, + // Model = model, + // View = view, + // Projection = projection + // }; + // } + // } + // } + // return null; + // } + // } + // } + //}, canvas } }; diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index e7b7d9d61..e7a150d96 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -64,10 +64,10 @@ private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IListCanvas height - needed to determine the mouse position in clip space. public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int canvasHeight) { - var pickPosClip = mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight) + new float2(-1, 1); + var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); var pickResults = _scenePicker.Pick(pickPosClip, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); - var pickResNodes = pickResults.Select(x => x.Node).ToList(); + var pickResNodes = pickResults.ConvertAll(x => x.Node); var firstPickRes = pickResults.FirstOrDefault(); _pickRes = null; diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index c1de24b42..a9dff3429 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -44,8 +44,7 @@ public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) float4x4 invViewProjection = float4x4.Invert(projection * view); - var pickPosWorld4 = float4x4.Transform(invViewProjection, new float4(pickPosClip.x, pickPosClip.y, 1, 1)); - var pickPosWorld = (pickPosWorld4 / pickPosWorld4.w).xyz; + var pickPosWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); Direction = (pickPosWorld - Origin).Normalize(); From 9f16f7ac40cc66334251bb0ed38a57cb69c1e46e Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 10 Jan 2023 15:29:07 +0100 Subject: [PATCH 021/294] First Potree2LAS converter --- src/PointCloud/Potree/Potree2LAS.cs | 255 ++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 src/PointCloud/Potree/Potree2LAS.cs diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs new file mode 100644 index 000000000..dc92bb1db --- /dev/null +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -0,0 +1,255 @@ +using CommunityToolkit.Diagnostics; +using Fusee.Base.Core; +using Fusee.PointCloud.Potree.V2; +using Fusee.PointCloud.Potree.V2.Data; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Fusee.PointCloud.Potree +{ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LASHeader + { + public LASHeader() + { + + } + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] FileSignature = new char[] { 'L', 'A', 'S', 'F' }; + + public ushort FileSourceID = 0; + public ushort GlobalEncoding = 0; + + public uint GUIDData1 = 0; + public ushort GUIDData2 = 0; + public ushort GUIDData3 = 0; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] GUIData4 = new byte[8]; + + public byte VersionMajor = 1; + public byte VersionMinor = 4; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] SystemIdentifier = new byte[32]; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] GeneratingSoftware = new byte[32]; + + public ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; + public ushort FileCreationYear = (ushort)DateTime.Now.Year; + public ushort HeaderSize = 375; + public uint OffsetToPointData = 375; // sizeof(LASHeader) + public uint NumberOfVariableLengthRecords = 0; + public byte PointDataRecordFormat = 2; + public ushort PointDataRecordLength = 26; // sizeof(LASPoint) + public uint LegacyNbrOfPoints = 0; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + public uint[] LeacyNbrOfPointsByRtn = new uint[5]; + + public double ScaleFactorX = 0; + public double ScaleFactorY = 0; + public double ScaleFactorZ = 0; + + public double OffsetX = 0; + public double OffsetY = 0; + public double OffsetZ = 0; + + public double MaxX = 0; + public double MinX = 0; + + public double MaxY = 0; + public double MinY = 0; + + public double MaxZ = 0; + public double MinZ = 0; + + public ulong StartOfWaveformPacket = 0; + public ulong StartOfFirstExtendend = 0; + public uint NbrOfExtendedVariableLength = 0; + + public ulong NumberOfPtRecords = 0; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] + public ulong[] NbrOfPointsByReturn = new ulong[15]; + + public byte[] ToByteArray() + { + var arr = new byte[] { }; + var ptr = IntPtr.Zero; + try + { + int size = Marshal.SizeOf(); + arr = new byte[size]; + ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(this, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + } + catch (Exception e) + { + Diagnostics.Error(e); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return arr; + } + } + + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LASPoint + { + public LASPoint() + { } + + public uint X = 0; + public uint Y = 0; + public uint Z = 0; + + public ushort Intensity = 0; + public byte ReturnNbrOfScanDirAndEdgeByte = 0; + + public byte Classification = 0; + public byte ScanAngleRank = 0; + public byte UserData = 0; + + public ushort PtSrcID = 0; + + public ushort R = 0; + public ushort G = 0; + public ushort B = 0; + + public byte[] ToByteArray() + { + var arr = new byte[] { }; + var ptr = IntPtr.Zero; + try + { + int size = Marshal.SizeOf(); + arr = new byte[size]; + ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(this, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + } + catch (Exception e) + { + Diagnostics.Error(e); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return arr; + } + } + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + + /// + /// This class provides methods to convert and save clouds to LAS 1.4 + /// + public static class Potree2LAS + { + /// + /// Converts a list of s to a LAS 1.4 file and writes it to the disk + /// + /// s as input + /// where to save the *.las file to, existing file is being overwritten! + public static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) + { + Guard.IsNotNull(savePath); + Guard.IsNotNull(points); + Guard.IsTrue(savePath.Extension == ".las"); + + Guard.IsEqualTo(Marshal.SizeOf(), 26); + Guard.IsEqualTo(Marshal.SizeOf(), 375); + + if (savePath.Exists) + Diagnostics.Warn($"File {savePath.FullName} already existing, overwriting ..."); + + var scaleFactor = metadata.Scale; + var offset = metadata.Offset; + + const float maxColorValuePotree = byte.MaxValue; + const short maxIntensityValuePotree = short.MaxValue; + const ushort maxColorAndIntensityValueLAS = ushort.MaxValue; + + var invFlipMatrix = Potree2Consts.YZflip.Invert(); + + var convertedData = new ConcurrentBag(); + //Parallel.ForEach(points, (p) => + foreach (var p in points) + { + var ptFlipped = invFlipMatrix * p.Position; + + var X = (uint)((ptFlipped.x ) / scaleFactor.x); + var Y = (uint)((ptFlipped.y ) / scaleFactor.y); + var Z = (uint)((ptFlipped.z ) / scaleFactor.z); + var R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS); + var G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS); + var B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS); + + convertedData.Add(new LASPoint + { + X = (uint)((ptFlipped.x /* + offset.x*/) / scaleFactor.x), + Y = (uint)((ptFlipped.y /* + offset.y*/) / scaleFactor.y), // flipped y/z + Z = (uint)((ptFlipped.z /* + offset.z*/) / scaleFactor.z), + Classification = p.Classification, + Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), + R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), + G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), + B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), + }); + } + //); + + var min = metadata.Attributes["position"].Min; + var max = metadata.Attributes["position"].Max; + + var header = new LASHeader + { + // flipped y/z + OffsetX = metadata.Offset.x, + OffsetY = metadata.Offset.y, + OffsetZ = metadata.Offset.z, + ScaleFactorX = metadata.Scale.x, + ScaleFactorY = metadata.Scale.y, + ScaleFactorZ = metadata.Scale.z, + NumberOfPtRecords = (ulong)convertedData.Count, + MinX = min.x, + MaxX = max.x, + MinY = min.y, + MaxY = max.y, + MinZ = min.z, + MaxZ = max.z + }; + + using var fs = savePath.Create(); + using var bw = new BinaryWriter(fs); + + bw.Write(header.ToByteArray()); + + foreach (var p in convertedData) + { + bw.Write(p.ToByteArray()); + } + + bw.Close(); + fs.Close(); + } + + + + } +} From 33401a637622dc7b30919d8443f8983544e4cbc3 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 10 Jan 2023 15:52:17 +0100 Subject: [PATCH 022/294] Fix Array.Fill "bug"/unexpected behavior, due to reference type --- src/PointCloud/Potree/V2/Potree2Reader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 8c56e3d77..6b6c8bf0a 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -131,7 +131,7 @@ public IPointCloudOctree GetOctree() var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); - // Commented code is to read the entire Potree2 file format. Since we don't use everything atm unused + // Commented code is to read the entire Potree2 file format. Since we don't use everything atm unused // things are commented for performance. for (int i = 0; i < node.NumPoints; i++) { @@ -220,7 +220,8 @@ public IPointCloudOctree GetOctree() var points = new TPotreePoint[node.NumPoints]; - Array.Fill(points, new TPotreePoint()); + for (var i = 0; i < points.Length; i++) + points[i] = new TPotreePoint(); var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); From 5640db61956c0b88250dd899626f6ca53393cc07 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 10 Jan 2023 15:59:39 +0100 Subject: [PATCH 023/294] Camera: made CustomCameraUpdate nullable --- src/Engine/Core/Scene/Camera.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Core/Scene/Camera.cs b/src/Engine/Core/Scene/Camera.cs index c38febd5b..f6562a953 100644 --- a/src/Engine/Core/Scene/Camera.cs +++ b/src/Engine/Core/Scene/Camera.cs @@ -102,7 +102,7 @@ public class Camera : SceneComponent /// but if this delegate is not null its out values (Projection matrix and Viewport) /// will overwrite the ones calculated from the other camera parameters. /// - public CustomCameraUpdate CustomCameraUpdate; + public CustomCameraUpdate? CustomCameraUpdate; /// /// Sets the RenderLayer for this camera. From 24e0e862c527e7d5f38e2b539c85d950ef80f327 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 10 Jan 2023 16:08:28 +0100 Subject: [PATCH 024/294] Cleanup Potree2LAS --- src/PointCloud/Potree/Potree2LAS.cs | 157 ++++++++++++++-------------- 1 file changed, 76 insertions(+), 81 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index dc92bb1db..acfe1202f 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -3,10 +3,8 @@ using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -15,74 +13,71 @@ namespace Fusee.PointCloud.Potree #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct LASHeader + internal struct LASHeader { - public LASHeader() - { - - } + public LASHeader() { } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public char[] FileSignature = new char[] { 'L', 'A', 'S', 'F' }; + internal char[] FileSignature = new char[] { 'L', 'A', 'S', 'F' }; - public ushort FileSourceID = 0; - public ushort GlobalEncoding = 0; + internal ushort FileSourceID = 0; + internal ushort GlobalEncoding = 0; - public uint GUIDData1 = 0; - public ushort GUIDData2 = 0; - public ushort GUIDData3 = 0; + internal uint GUIDData1 = 0; + internal ushort GUIDData2 = 0; + internal ushort GUIDData3 = 0; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] GUIData4 = new byte[8]; + internal byte[] GUIData4 = new byte[8]; - public byte VersionMajor = 1; - public byte VersionMinor = 4; + internal byte VersionMajor = 1; + internal byte VersionMinor = 4; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] SystemIdentifier = new byte[32]; + internal byte[] SystemIdentifier = new byte[32]; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] GeneratingSoftware = new byte[32]; + internal byte[] GeneratingSoftware = new byte[32]; - public ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; - public ushort FileCreationYear = (ushort)DateTime.Now.Year; - public ushort HeaderSize = 375; - public uint OffsetToPointData = 375; // sizeof(LASHeader) - public uint NumberOfVariableLengthRecords = 0; - public byte PointDataRecordFormat = 2; - public ushort PointDataRecordLength = 26; // sizeof(LASPoint) - public uint LegacyNbrOfPoints = 0; + internal ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; + internal ushort FileCreationYear = (ushort)DateTime.Now.Year; + internal ushort HeaderSize = 375; + internal uint OffsetToPointData = 375; // sizeof(LASHeader) + internal uint NumberOfVariableLengthRecords = 0; + internal byte PointDataRecordFormat = 2; + internal ushort PointDataRecordLength = 26; // sizeof(LASPoint) + internal uint LegacyNbrOfPoints = 0; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - public uint[] LeacyNbrOfPointsByRtn = new uint[5]; + internal uint[] LeacyNbrOfPointsByRtn = new uint[5]; - public double ScaleFactorX = 0; - public double ScaleFactorY = 0; - public double ScaleFactorZ = 0; + internal double ScaleFactorX = 0; + internal double ScaleFactorY = 0; + internal double ScaleFactorZ = 0; - public double OffsetX = 0; - public double OffsetY = 0; - public double OffsetZ = 0; + internal double OffsetX = 0; + internal double OffsetY = 0; + internal double OffsetZ = 0; - public double MaxX = 0; - public double MinX = 0; + internal double MaxX = 0; + internal double MinX = 0; - public double MaxY = 0; - public double MinY = 0; + internal double MaxY = 0; + internal double MinY = 0; - public double MaxZ = 0; - public double MinZ = 0; + internal double MaxZ = 0; + internal double MinZ = 0; - public ulong StartOfWaveformPacket = 0; - public ulong StartOfFirstExtendend = 0; - public uint NbrOfExtendedVariableLength = 0; + internal ulong StartOfWaveformPacket = 0; + internal ulong StartOfFirstExtendend = 0; + internal uint NbrOfExtendedVariableLength = 0; - public ulong NumberOfPtRecords = 0; + internal ulong NumberOfPtRecords = 0; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] - public ulong[] NbrOfPointsByReturn = new ulong[15]; + internal ulong[] NbrOfPointsByReturn = new ulong[15]; - public byte[] ToByteArray() + internal byte[] ToByteArray() { - var arr = new byte[] { }; + var arr = Array.Empty(); var ptr = IntPtr.Zero; try { @@ -107,31 +102,30 @@ public byte[] ToByteArray() [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct LASPoint + internal struct LASPoint { - public LASPoint() - { } + public LASPoint() { } - public uint X = 0; - public uint Y = 0; - public uint Z = 0; + internal uint X = 0; + internal uint Y = 0; + internal uint Z = 0; - public ushort Intensity = 0; - public byte ReturnNbrOfScanDirAndEdgeByte = 0; + internal ushort Intensity = 0; + internal byte ReturnNbrOfScanDirAndEdgeByte = 0; - public byte Classification = 0; - public byte ScanAngleRank = 0; - public byte UserData = 0; + internal byte Classification = 0; + internal byte ScanAngleRank = 0; + internal byte UserData = 0; - public ushort PtSrcID = 0; + internal ushort PtSrcID = 0; - public ushort R = 0; - public ushort G = 0; - public ushort B = 0; + internal ushort R = 0; + internal ushort G = 0; + internal ushort B = 0; - public byte[] ToByteArray() + internal byte[] ToByteArray() { - var arr = new byte[] { }; + var arr = Array.Empty(); var ptr = IntPtr.Zero; try { @@ -161,10 +155,22 @@ public byte[] ToByteArray() /// public static class Potree2LAS { + /// + /// Converts a list of s to a LAS 1.4 file and writes it to the disk in an async manner + /// + /// s as input + /// for offset and LAS header writing + /// where to save the *.las file to, existing file is being overwritten! + public static async Task WritePotree2LASAsync(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) + { + await Task.Run(() => WritePotree2LAS(points, metadata, savePath)); + } + /// /// Converts a list of s to a LAS 1.4 file and writes it to the disk /// /// s as input + /// for offset and LAS header writing /// where to save the *.las file to, existing file is being overwritten! public static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) { @@ -176,10 +182,9 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada Guard.IsEqualTo(Marshal.SizeOf(), 375); if (savePath.Exists) - Diagnostics.Warn($"File {savePath.FullName} already existing, overwriting ..."); + Diagnostics.Warn($"File {savePath.FullName} does exist, overwriting ..."); var scaleFactor = metadata.Scale; - var offset = metadata.Offset; const float maxColorValuePotree = byte.MaxValue; const short maxIntensityValuePotree = short.MaxValue; @@ -187,24 +192,18 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada var invFlipMatrix = Potree2Consts.YZflip.Invert(); - var convertedData = new ConcurrentBag(); - //Parallel.ForEach(points, (p) => + var convertedData = new List(); + foreach (var p in points) { + // flipped y/z var ptFlipped = invFlipMatrix * p.Position; - var X = (uint)((ptFlipped.x ) / scaleFactor.x); - var Y = (uint)((ptFlipped.y ) / scaleFactor.y); - var Z = (uint)((ptFlipped.z ) / scaleFactor.z); - var R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS); - var G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS); - var B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS); - convertedData.Add(new LASPoint { - X = (uint)((ptFlipped.x /* + offset.x*/) / scaleFactor.x), - Y = (uint)((ptFlipped.y /* + offset.y*/) / scaleFactor.y), // flipped y/z - Z = (uint)((ptFlipped.z /* + offset.z*/) / scaleFactor.z), + X = (uint)((ptFlipped.x) / scaleFactor.x), + Y = (uint)((ptFlipped.y) / scaleFactor.y), + Z = (uint)((ptFlipped.z) / scaleFactor.z), Classification = p.Classification, Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), @@ -212,7 +211,6 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), }); } - //); var min = metadata.Attributes["position"].Min; var max = metadata.Attributes["position"].Max; @@ -248,8 +246,5 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada bw.Close(); fs.Close(); } - - - } } From 23fb425e3e8998029dd77cc05348ec8f94f371af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jan 2023 02:07:13 +0000 Subject: [PATCH 025/294] Bump Microsoft.AspNetCore.Components.WebAssembly.DevServer Bumps [Microsoft.AspNetCore.Components.WebAssembly.DevServer](https://github.com/dotnet/aspnetcore) from 7.0.0 to 7.0.2. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.0...v7.0.2) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly.DevServer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 2 +- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 2 +- .../Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 2 +- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index 339c3470f..53865bb58 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index 6c52a4787..aa004e421 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index 154fb67ac..f0ec3aa52 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 7bbeb9a50..d9f892026 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index 274cc0ae0..a65b4abc8 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 360c08eb6..28c32af76 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 1de162868..3ca822bbc 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -24,7 +24,7 @@ - + From de713e7b3b6cc58f4ba3399b853110c045499136 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 11 Jan 2023 11:10:03 +0100 Subject: [PATCH 026/294] Cleanup Potree2LAS --- src/PointCloud/Potree/Potree2LAS.cs | 91 ++++++++++++----------------- 1 file changed, 38 insertions(+), 53 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index acfe1202f..bd10b72e5 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -10,7 +10,6 @@ namespace Fusee.PointCloud.Potree { -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct LASHeader @@ -74,30 +73,6 @@ public LASHeader() { } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] internal ulong[] NbrOfPointsByReturn = new ulong[15]; - - internal byte[] ToByteArray() - { - var arr = Array.Empty(); - var ptr = IntPtr.Zero; - try - { - int size = Marshal.SizeOf(); - arr = new byte[size]; - ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(this, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - } - catch (Exception e) - { - Diagnostics.Error(e); - } - finally - { - Marshal.FreeHGlobal(ptr); - } - - return arr; - } } @@ -122,34 +97,8 @@ public LASPoint() { } internal ushort R = 0; internal ushort G = 0; internal ushort B = 0; - - internal byte[] ToByteArray() - { - var arr = Array.Empty(); - var ptr = IntPtr.Zero; - try - { - int size = Marshal.SizeOf(); - arr = new byte[size]; - ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(this, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - } - catch (Exception e) - { - Diagnostics.Error(e); - } - finally - { - Marshal.FreeHGlobal(ptr); - } - - return arr; - } } -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member - /// /// This class provides methods to convert and save clouds to LAS 1.4 /// @@ -236,15 +185,51 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada using var fs = savePath.Create(); using var bw = new BinaryWriter(fs); - bw.Write(header.ToByteArray()); + bw.Write(ToByteArray(header)); foreach (var p in convertedData) { - bw.Write(p.ToByteArray()); + bw.Write(ToByteArray(p)); } bw.Close(); fs.Close(); } + + /// + /// Convert a struct to a byte array + /// + /// struct + /// converted byte array + /// + private static byte[] ToByteArray(T input) where T : struct + { + var arr = Array.Empty(); + var ptr = IntPtr.Zero; + try + { + var size = Marshal.SizeOf(); + + // check for overflow + Guard.IsGreaterThan(size, 0); + Guard.IsLessThanOrEqualTo(size, int.MaxValue); + + arr = new byte[size]; + ptr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(input, ptr, true); + Marshal.Copy(ptr, arr, 0, size); + } + catch (Exception e) + { + Diagnostics.Error(e); + Diagnostics.Error(Marshal.GetLastWin32Error()); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + + return arr; + } } } From cff1dde8985090a15c07a0d509b4e373c607269f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 19 Jan 2023 11:41:34 +0100 Subject: [PATCH 027/294] Fixed ortographic camera picking --- src/Engine/Core/ScenePicker.cs | 22 ++++++++++++---------- src/Math/Core/Rayf.cs | 9 +++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index bb67e0357..854e56301 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -540,10 +540,12 @@ public void HandleMesh(Mesh mesh) case PrimitiveType.Triangles: case PrimitiveType.TriangleFan: case PrimitiveType.TriangleStrip: - PickTriangleGeometry(mesh, State.Projection, State.View); - break; case PrimitiveType.Lines: - PickLineGeometry(mesh); + if (State != null) + PickTriangleGeometry(mesh, State.Projection, State.View); + break; + //case PrimitiveType.Lines: + // PickLineGeometry(mesh); break; default: Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); @@ -634,12 +636,10 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 } var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); - Diagnostics.Debug($"Origin: {ray.Origin}, Dir: {ray.Direction}, Inv: {ray.Inverse}"); - var box = State.Model * mesh.BoundingBox; - Diagnostics.Debug($"Box: {box.Center}"); - if (!box.IntersectRay(ray)) - return; + // does not work for Planes or Ortographic Cameras! + //if (!box.IntersectRay(ray)) + // return; for (int i = 0; i < mesh.Triangles.Length; i += 3) { @@ -656,11 +656,13 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 // Normal of the plane defined by a, b, and c. var n = float3.Normalize(float3.Cross(a - c, b - c)); + // Distance between "Origin" and the plane abc when following the Direction. var distance = -float3.Dot(ray.Origin - a, n) / float3.Dot(ray.Direction, n); - if (distance < 0) - continue; + // does not work for Planes or Ortographic Cameras! + //if (distance < 0) + // continue; // Position of the intersection point between ray and plane. var point = ray.Origin + (ray.Direction * distance); diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index a9dff3429..68f7f5ec9 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -40,15 +40,16 @@ public RayF(float3 origin_, float3 direction_) /// The Projection Matrix of the rendered scene. public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) { - Origin = float4x4.Invert(view).Translation(); - float4x4 invViewProjection = float4x4.Invert(projection * view); - var pickPosWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); + var pickPosFarWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); + var pickPosNearWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 0)); - Direction = (pickPosWorld - Origin).Normalize(); + Direction = (pickPosFarWorld - pickPosNearWorld).Normalize(); + Origin = pickPosNearWorld; Inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + Inverse = new float3(float.IsInfinity(Inverse.x) ? 0 : Inverse.x, float.IsInfinity(Inverse.y) ? 0 : Inverse.y, float.IsInfinity(Inverse.z) ? 0 : Inverse.z); } } } \ No newline at end of file From a921aee48679870a599d65a26125a54ffae70ccc Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 26 Jan 2023 16:21:22 +0100 Subject: [PATCH 028/294] First draft of point cloud writer interface --- src/PointCloud/Common/IPointWriter.cs | 134 ++++++++++++++++ src/PointCloud/Potree/Potree2LAS.cs | 212 ++++++++++++-------------- 2 files changed, 228 insertions(+), 118 deletions(-) create mode 100644 src/PointCloud/Common/IPointWriter.cs diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs new file mode 100644 index 000000000..29b8ef7d0 --- /dev/null +++ b/src/PointCloud/Common/IPointWriter.cs @@ -0,0 +1,134 @@ +using Fusee.Math.Core; +using Fusee.PointCloud.Common.Accessors; +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace Fusee.PointCloud.Common +{ + /// + /// Information about the hierarchy data of the given point cloud + /// + public interface IPointWriterHierarchy + { + /// + /// Size of the first chunk + /// + public int FirstChunkSize { get; set; } + + /// + /// Hierarchy step size + /// + public int StepSize { get; set; } + + /// + /// Depth of the Hierarchy + /// + public int Depth { get; set; } + } + + /// + /// Metadata about the point cloud + /// the "attributes" which are needed for e. g. the Potree export can be reconstructed via the + /// + public interface IPointWriterMetadata + { + /// + /// Version flag + /// + public string Version { get; set; } + + /// + /// Point cloud name + /// + public string Name { get; set; } + + /// + /// Description of point cloud + /// + public string Description { get; set; } + + /// + /// How many points does this point cloud contain + /// + public int PointCount { get; set; } + + /// + /// A possible projection method + /// + public string Projection { get; set; } + + /// + /// The point cloud hierarchy information + /// + public IPointWriterHierarchy Hierarchy { get; set; } + + /// + /// Global offset of each point + /// + public double3 Offset { get; set; } + + /// + /// Global scale value. Points are being converted to int + /// During load this scale factor is being applied to convert int to double + /// + public double3 Scale { get; set; } + + /// + /// The spacing between points (set during the sampling process) + /// + public double Spacing { get; set; } + + /// + /// Global of the point cloud + /// + public AABBd BoundingBox { get; set; } + + /// + /// The encoding of every point, as we save the point cloud as elements + /// Default is + /// + public string Encoding { get; set; } + + /// + /// The size of one point in bytes (for sanity checks later on, compare with data) + /// + public int PointSize { get; set; } + } + + /// + /// Every point writer (e. g. Potree, LAS, etc.) implements this interface + /// + public interface IPointWriter + { + /// + /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . + /// + public IPointAccessor PointAccessor { get; } + + /// + /// Returns the point type. + /// + public PointType PointType { get; } + + /// + /// This methods takes a list s and converts it to the desired output format and writes the file + /// to disk at given + /// + /// Path to save to + /// The point data as + /// Necessary metadata, e. g. global scale, etc. + public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata); + + /// + /// This methods takes a list of s and converts it to the desired output format and writes the file + /// to disk at given in an async manner + /// + /// Path to save to + /// The point data as , no as ref types are not allowed + /// during async operations + /// Necessary metadata, e. g. global scale, etc. + public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); + } +} diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index bd10b72e5..a2ffd92e5 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,5 +1,8 @@ using CommunityToolkit.Diagnostics; using Fusee.Base.Core; +using Fusee.PointCloud.Common; +using Fusee.PointCloud.Common.Accessors; +using Fusee.PointCloud.Core.Accessors; using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; @@ -99,137 +102,110 @@ public LASPoint() { } internal ushort B = 0; } + internal class PotreeAccessor : PointAccessor + { + + } + /// /// This class provides methods to convert and save clouds to LAS 1.4 /// - public static class Potree2LAS + public class Potree2LAS : IPointWriter { /// - /// Converts a list of s to a LAS 1.4 file and writes it to the disk in an async manner + /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . /// - /// s as input - /// for offset and LAS header writing - /// where to save the *.las file to, existing file is being overwritten! - public static async Task WritePotree2LASAsync(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) - { - await Task.Run(() => WritePotree2LAS(points, metadata, savePath)); - } + public IPointAccessor PointAccessor => new PotreeAccessor(); /// - /// Converts a list of s to a LAS 1.4 file and writes it to the disk + /// Returns the point type. /// - /// s as input - /// for offset and LAS header writing - /// where to save the *.las file to, existing file is being overwritten! - public static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) + public PointType PointType => PointType.PosD3ColF3InUsLblB; + + public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata) { - Guard.IsNotNull(savePath); - Guard.IsNotNull(points); - Guard.IsTrue(savePath.Extension == ".las"); - - Guard.IsEqualTo(Marshal.SizeOf(), 26); - Guard.IsEqualTo(Marshal.SizeOf(), 375); - - if (savePath.Exists) - Diagnostics.Warn($"File {savePath.FullName} does exist, overwriting ..."); - - var scaleFactor = metadata.Scale; - - const float maxColorValuePotree = byte.MaxValue; - const short maxIntensityValuePotree = short.MaxValue; - const ushort maxColorAndIntensityValueLAS = ushort.MaxValue; - - var invFlipMatrix = Potree2Consts.YZflip.Invert(); - - var convertedData = new List(); - - foreach (var p in points) - { - // flipped y/z - var ptFlipped = invFlipMatrix * p.Position; - - convertedData.Add(new LASPoint - { - X = (uint)((ptFlipped.x) / scaleFactor.x), - Y = (uint)((ptFlipped.y) / scaleFactor.y), - Z = (uint)((ptFlipped.z) / scaleFactor.z), - Classification = p.Classification, - Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), - R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), - G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), - B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), - }); - } - - var min = metadata.Attributes["position"].Min; - var max = metadata.Attributes["position"].Max; - - var header = new LASHeader - { - // flipped y/z - OffsetX = metadata.Offset.x, - OffsetY = metadata.Offset.y, - OffsetZ = metadata.Offset.z, - ScaleFactorX = metadata.Scale.x, - ScaleFactorY = metadata.Scale.y, - ScaleFactorZ = metadata.Scale.z, - NumberOfPtRecords = (ulong)convertedData.Count, - MinX = min.x, - MaxX = max.x, - MinY = min.y, - MaxY = max.y, - MinZ = min.z, - MaxZ = max.z - }; - - using var fs = savePath.Create(); - using var bw = new BinaryWriter(fs); - - bw.Write(ToByteArray(header)); - - foreach (var p in convertedData) - { - bw.Write(ToByteArray(p)); - } - - bw.Close(); - fs.Close(); + throw new NotImplementedException(); } - /// - /// Convert a struct to a byte array - /// - /// struct - /// converted byte array - /// - private static byte[] ToByteArray(T input) where T : struct + public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata) { - var arr = Array.Empty(); - var ptr = IntPtr.Zero; - try - { - var size = Marshal.SizeOf(); - - // check for overflow - Guard.IsGreaterThan(size, 0); - Guard.IsLessThanOrEqualTo(size, int.MaxValue); - - arr = new byte[size]; - ptr = Marshal.AllocHGlobal(size); - Marshal.StructureToPtr(input, ptr, true); - Marshal.Copy(ptr, arr, 0, size); - } - catch (Exception e) - { - Diagnostics.Error(e); - Diagnostics.Error(Marshal.GetLastWin32Error()); - } - finally - { - Marshal.FreeHGlobal(ptr); - } - - return arr; + throw new NotImplementedException(); } + + // old code, replace + //private static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) + //{ + // Guard.IsNotNull(savePath); + // Guard.IsNotNull(points); + // Guard.IsTrue(savePath.Extension == ".las"); + + // Guard.IsEqualTo(Marshal.SizeOf(), 26); + // Guard.IsEqualTo(Marshal.SizeOf(), 375); + + // if (savePath.Exists) + // Diagnostics.Warn($"File {savePath.FullName} does exist, overwriting ..."); + + // var scaleFactor = metadata.Scale; + + // const float maxColorValuePotree = byte.MaxValue; + // const short maxIntensityValuePotree = short.MaxValue; + // const ushort maxColorAndIntensityValueLAS = ushort.MaxValue; + + // var invFlipMatrix = Potree2Consts.YZflip.Invert(); + + // var convertedData = new List(); + + // foreach (var p in points) + // { + // // flipped y/z + // var ptFlipped = invFlipMatrix * p.Position; + + // convertedData.Add(new LASPoint + // { + // X = (uint)((ptFlipped.x) / scaleFactor.x), + // Y = (uint)((ptFlipped.y) / scaleFactor.y), + // Z = (uint)((ptFlipped.z) / scaleFactor.z), + // Classification = p.Classification, + // Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), + // R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), + // G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), + // B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), + // }); + // } + + // var min = metadata.Attributes["position"].Min; + // var max = metadata.Attributes["position"].Max; + + // var header = new LASHeader + // { + // // flipped y/z + // OffsetX = metadata.Offset.x, + // OffsetY = metadata.Offset.y, + // OffsetZ = metadata.Offset.z, + // ScaleFactorX = metadata.Scale.x, + // ScaleFactorY = metadata.Scale.y, + // ScaleFactorZ = metadata.Scale.z, + // NumberOfPtRecords = (ulong)convertedData.Count, + // MinX = min.x, + // MaxX = max.x, + // MinY = min.y, + // MaxY = max.y, + // MinZ = min.z, + // MaxZ = max.z + // }; + + // using var fs = savePath.Create(); + // using var bw = new BinaryWriter(fs); + + // bw.Write(ToByteArray(header)); + + // foreach (var p in convertedData) + // { + // bw.Write(ToByteArray(p)); + // } + + // bw.Close(); + // fs.Close(); + //} } } From 206c9cc66f931c3818fe723915a947ead7542712 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 31 Jan 2023 15:33:25 +0100 Subject: [PATCH 029/294] RGB picking --- Examples/Complete/Picking/Core/Picking.cs | 87 +++++++++++++---------- src/Engine/Core/ScenePicker.cs | 68 +++++++++++++++--- 2 files changed, 107 insertions(+), 48 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index eac3e10a8..1a0999a9e 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -24,6 +24,7 @@ public class Picking : RenderCanvas private SceneContainer _scene; private Transform _camPivotTransform; private SceneRendererForward _sceneRenderer; + private SceneRendererDeferred _pickRenderer; private ScenePicker _scenePicker; private bool _keys; @@ -46,9 +47,21 @@ private async Task Load() // Create the robot model _scene = CreateScene(); + _scene.Children.Add(new SceneNode + { + Components = new List { + new Transform(), + MakeEffect.LineEffect(5, new float4(1,0,0,0)), + new Mesh(new uint[] {0, 1}, new float3[] {new float3(0,150,0), new float3(5,170,5) }) + { + MeshType = PrimitiveType.Lines + } + } + }); + // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene, RC.CurrentRenderState.CullMode); + _scenePicker = new ScenePicker(_scene, RC, RC.CurrentRenderState.CullMode); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler @@ -116,49 +129,49 @@ public override void Update() // RenderAFrame is called once a frame public override void RenderAFrame() { - //_sceneRenderer.Render(RC); + _sceneRenderer.Render(RC); - //Picking + // Picking //if (_pick) //{ - // float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - - // var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) - // .FirstOrDefault(); - // if (newPick != null) - // Diagnostics.Debug(newPick.Node.Name); - - // if (newPick?.Node != _currentPick?.Node) - // { - // if (_currentPick != null) - // { - // var ef = _currentPick.Node.GetComponent(); - // ef.SurfaceInput.Albedo = _oldColor; - // } - - // if (newPick != null) - // { - // var ef = newPick.Node.GetComponent(); - // _oldColor = ef.SurfaceInput.Albedo; - // ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; - // } - - // _currentPick = newPick; - // } - - // _pick = false; + float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); + + var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) + .FirstOrDefault(); + if (newPick != null) + Diagnostics.Debug(newPick.Node.Name); + + if (newPick?.Node != _currentPick?.Node) + { + if (_currentPick != null) + { + var ef = _currentPick.Node.GetComponent(); + ef.SurfaceInput.Albedo = _oldColor; + } + + if (newPick != null) + { + var ef = newPick.Node.GetComponent(); + _oldColor = ef.SurfaceInput.Albedo; + ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; + } + + _currentPick = newPick; + } + + _pick = false; //} - _guiRenderer.Render(RC); + //_guiRenderer.Render(RC); // Constantly check for interactive objects. - if (!Input.Mouse.Desc.Contains("Android")) - _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); - - if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) - { - _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); - } + //if (!Input.Mouse.Desc.Contains("Android")) + // _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); + // + //if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) + //{ + // _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + //} // Swap buffers: Show the contents of the back buffer (containing the currently rendered frame) on the front buffer. Present(); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 854e56301..19bb7d008 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -1,4 +1,6 @@ -using Fusee.Base.Core; +using CommunityToolkit.Diagnostics; +using Fusee.Base.Common; +using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core.Effects; using Fusee.Engine.Core.Scene; @@ -163,16 +165,23 @@ public PickerState() #endregion + // deferred renderer for RGB FBO picking + private SceneRendererForward _sceneRenderer; + private WritableTexture _writableTexture; + private Transform _cameraPosition = new(); + private RenderContext _rc; + /// /// The constructor to initialize a new ScenePicker. /// /// /// The to pick from. - public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable customPickModule = null) + public ScenePicker(SceneContainer scene, RenderContext rc = null, Cull cullMode = Cull.None, IEnumerable customPickModule = null) : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; State.CullMode = cullMode; + _rc = rc; } /// @@ -537,18 +546,55 @@ public void HandleMesh(Mesh mesh) switch (mesh.MeshType) { + // we should be able to pick all Triangle* with Raycast case PrimitiveType.Triangles: case PrimitiveType.TriangleFan: case PrimitiveType.TriangleStrip: - case PrimitiveType.Lines: if (State != null) PickTriangleGeometry(mesh, State.Projection, State.View); break; - //case PrimitiveType.Lines: - // PickLineGeometry(mesh); + // everything else should be pickable with an color coded FBO at mouse position + // point cloud will be picked via PointCloudPicker Module + case PrimitiveType.Points: + Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); break; default: - Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); + // non-triangle picking is only possible with camera + Guard.IsNotNull(CurrentCamera, nameof(CurrentCamera)); + + var cam = new Camera(CurrentCamera.ProjectionMethod, 1, 1000, CurrentCamera.Fov); + + // if scene renderer is empty, generate new with camera and custom shader stuff + // attach current scene node to shader + var container = new SceneContainer + { + Children = new List + { + new SceneNode + { + Components = new List + { + _cameraPosition, + cam + } + } + // add current node without material here + } + }; + // generate texture size 1,1, move the current camera at mouse position + _writableTexture ??= new WritableTexture(RenderTargetTextureTypes.Albedo, new ImagePixelFormat(ColorFormat.RGB), 10, 10, false); + var iVP = (State.Projection * State.View).Invert(); + _cameraPosition.Translation = iVP * new float3(PickPosClip.x, PickPosClip.y, 0); + //cam.RenderTexture = _writableTexture; + cam.FrustumCullingOn = false; + var compCopy = CurrentNode.Components.ToArray().ToList(); + + container.Children.Insert(1, new SceneNode { Components = compCopy }); + _sceneRenderer = new SceneRendererForward(container); + _sceneRenderer.Render(_rc); + + Console.WriteLine("Render"); + break; } } @@ -636,10 +682,10 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 } var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); - + var box = State.Model * mesh.BoundingBox; // does not work for Planes or Ortographic Cameras! - //if (!box.IntersectRay(ray)) - // return; + if (!box.IntersectRay(ray)) + return; for (int i = 0; i < mesh.Triangles.Length; i += 3) { @@ -661,8 +707,8 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 var distance = -float3.Dot(ray.Origin - a, n) / float3.Dot(ray.Direction, n); // does not work for Planes or Ortographic Cameras! - //if (distance < 0) - // continue; + if (distance < 0) + continue; // Position of the intersection point between ray and plane. var point = ray.Origin + (ray.Direction * distance); From e26556a5067b9b814ad4b801ae99102ea6f55b4a Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 7 Feb 2023 09:05:34 +0100 Subject: [PATCH 030/294] Camera: RenderTexture is nullable --- src/Engine/Core/RenderContext.cs | 2 +- src/Engine/Core/Scene/Camera.cs | 2 +- src/Engine/Core/SceneRendererDeferred.cs | 8 ++++---- src/Engine/Core/SceneRendererForward.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Engine/Core/RenderContext.cs b/src/Engine/Core/RenderContext.cs index 3e1d3c176..0476c0de8 100644 --- a/src/Engine/Core/RenderContext.cs +++ b/src/Engine/Core/RenderContext.cs @@ -1908,7 +1908,7 @@ public void SetRenderTarget(IWritableArrayTexture tex, int layer) /// Renders into the given texture. /// /// The render texture. - public void SetRenderTarget(IWritableTexture tex) + public void SetRenderTarget(IWritableTexture? tex) { if (tex == null) SetRenderTarget(); diff --git a/src/Engine/Core/Scene/Camera.cs b/src/Engine/Core/Scene/Camera.cs index f6562a953..3f313dbf6 100644 --- a/src/Engine/Core/Scene/Camera.cs +++ b/src/Engine/Core/Scene/Camera.cs @@ -91,7 +91,7 @@ public class Camera : SceneComponent /// The texture given here will be used as render target. /// If this is not null the output gets rendered into the texture, otherwise to the screen. /// - public IWritableTexture RenderTexture; + public IWritableTexture? RenderTexture; /// /// Allows to overwrite the calculation of the projection matrix. diff --git a/src/Engine/Core/SceneRendererDeferred.cs b/src/Engine/Core/SceneRendererDeferred.cs index 965a5994b..a0baad28c 100644 --- a/src/Engine/Core/SceneRendererDeferred.cs +++ b/src/Engine/Core/SceneRendererDeferred.cs @@ -479,7 +479,7 @@ public override void Render(RenderContext rc) } } - private void PerCamRender(CameraResult cam, IWritableTexture renderTex = null) + private void PerCamRender(CameraResult cam, IWritableTexture? renderTex = null) { RenderLayer = cam.Camera.RenderLayer; _rc.View = cam.View; @@ -507,7 +507,7 @@ private void PerCamRender(CameraResult cam, IWritableTexture renderTex = null) } } - private void RenderAllPasses(float4 lightingPassViewport, IWritableTexture renderTex = null) + private void RenderAllPasses(float4 lightingPassViewport, IWritableTexture? renderTex = null) { var preRenderStateSet = _rc.CurrentRenderState.Copy(); //"Snapshot" of the current render states as they came from the user code. var preRenderLockedStates = new Dictionary>(_rc.LockedStates); @@ -570,7 +570,7 @@ private void RenderAllPasses(float4 lightingPassViewport, IWritableTexture rende /// Alternatively it would be possible to iterate the lights in the shader, but this would create a more complex shader. Additionally it would be more difficult to implement a dynamic number of lights. /// The iteration here should not prove critical, due to the scene only consisting of a single quad. /// - private void RenderLightPasses(IWritableTexture renderTex = null) + private void RenderLightPasses(IWritableTexture? renderTex = null) { _rc.SetRenderTarget(renderTex); _rc.Clear(ClearFlags.Color | ClearFlags.Depth); @@ -836,7 +836,7 @@ private void RenderSSAO() _gBufferRenderTarget.SetTexture(_blurRenderTex, RenderTargetTextureTypes.Ssao); } - private void RenderFXAA(IWritableTexture renderTex = null) + private void RenderFXAA(IWritableTexture? renderTex = null) { _currentPass = RenderPasses.Fxaa; if (_fxaaEffect == null) diff --git a/src/Engine/Core/SceneRendererForward.cs b/src/Engine/Core/SceneRendererForward.cs index 191ff2176..02643e706 100644 --- a/src/Engine/Core/SceneRendererForward.cs +++ b/src/Engine/Core/SceneRendererForward.cs @@ -377,7 +377,7 @@ private void PerCamRender(CameraResult cam) _rc.SetRenderTarget(tex); _rc.Projection = tex != null - ? cam.Camera.GetProjectionMat(cam.Camera.RenderTexture.Width, cam.Camera.RenderTexture.Height, out float4 viewport) + ? cam.Camera.GetProjectionMat(tex.Width, tex.Height, out float4 viewport) : cam.Camera.GetProjectionMat(_rc.GetWindowWidth(), _rc.GetWindowHeight(), out viewport); _rc.Viewport((int)viewport.x, (int)viewport.y, (int)viewport.z, (int)viewport.w); From 2f84d88c84df108f1412435d31519a189957ca06 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 8 Feb 2023 12:14:23 +0100 Subject: [PATCH 031/294] ImGuiFilePicker: suppressed Vec4 hidden copy in Draw call --- .../Templates/ImGuiFilePicker.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 9286f82e7..58681ca0d 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -121,7 +121,18 @@ public bool IsOpen /// /// Background color of pop up window /// - public Vector4 WindowBackground = new(200, 200, 200, 255); + public Vector4 WindowBackground + { + get => _windowBackground; + set + { + _windowBackground = value; + _windowBackgroundUint = _windowBackground.ToUintColor(); + } + } + private Vector4 _windowBackground = new(200, 200, 200, 255); + + public uint _windowBackgroundUint = new Vector4(200, 200, 200, 255).ToUintColor(); /// /// Background of file selection menu @@ -182,7 +193,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, WindowPadding); ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, 0); - ImGui.PushStyleColor(ImGuiCol.WindowBg, WindowBackground.ToUintColor()); + ImGui.PushStyleColor(ImGuiCol.WindowBg, _windowBackgroundUint); if (DoFocusPicker) ImGui.SetNextWindowFocus(); From 4f1930cf9b5fbf4d5275543b89a9780216e30601 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Thu, 9 Feb 2023 08:37:27 +0100 Subject: [PATCH 032/294] ImGuiFilePicker: allows editing the filename & back button is disabled when stack is empty --- .../Templates/ImGuiFilePicker.cs | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 58681ca0d..ce13ddb80 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -121,15 +121,15 @@ public bool IsOpen /// /// Background color of pop up window /// - public Vector4 WindowBackground - { - get => _windowBackground; + public Vector4 WindowBackground + { + get => _windowBackground; set { _windowBackground = value; _windowBackgroundUint = _windowBackground.ToUintColor(); } - } + } private Vector4 _windowBackground = new(200, 200, 200, 255); public uint _windowBackgroundUint = new Vector4(200, 200, 200, 255).ToUintColor(); @@ -219,10 +219,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } ImGui.SameLine(); - if (ImGui.Button($"{BackTxt}##{_filePickerCount}", TopButtonSize)) + if (LastOpenendFolders.Count != 0) { - if (LastOpenendFolders.Count != 0) + if (ImGui.Button($"{BackTxt}##{_filePickerCount}", TopButtonSize)) { + var lastFolder = LastOpenendFolders.Pop(); var lastDi = new DirectoryInfo(lastFolder); if (lastDi.Exists) @@ -232,6 +233,12 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } } } + else + { + ImGui.BeginDisabled(); + ImGui.Button($"{BackTxt}##{_filePickerCount}", TopButtonSize); + ImGui.EndDisabled(); + } if ((IntPtr)SymbolsFontPtr.NativePtr != IntPtr.Zero) ImGui.PopFont(); @@ -364,7 +371,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) var selectedFile = !string.IsNullOrWhiteSpace(SelectedFile) ? SelectedFile : ""; ImGui.SetNextItemWidth(FileTextInputWidth - ImGui.CalcTextSize(FileLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); - ImGui.InputTextWithHint(FileLabelTxt, FileInputHintTxt, ref selectedFile, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => + if (ImGui.InputTextWithHint(FileLabelTxt, FileInputHintTxt, ref selectedFile, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => { var arr = selectedFile.ToCharArray(); @@ -375,7 +382,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGuiInputImp.CurrentlySelectedText = new string(selectedText); } return 0; - }); + })) + { + SelectedFile = selectedFile; + } + if (_sizeOfInputText == Vector2.Zero) _sizeOfInputText = ImGui.GetItemRectSize(); @@ -383,7 +394,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) if (!string.IsNullOrWhiteSpace(SelectedFile)) { var fi = new FileInfo(Path.Combine(CurrentOpenFolder, SelectedFile)); - if (fi.Exists && AllowedExtensions != null && AllowedExtensions.Contains(fi.Extension)) + if (AllowedExtensions != null && AllowedExtensions.Contains(fi.Extension)) { ImGui.SameLine(sameLineOffset); if (ImGui.Button($"{PickedFileTxt}##{_filePickerCount}", BottomButtonSize)) @@ -395,6 +406,13 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } } } + else + { + ImGui.SameLine(sameLineOffset); + ImGui.BeginDisabled(); + ImGui.Button(PickedFileTxt, BottomButtonSize); + ImGui.EndDisabled(); + } } else { From 03d645b5112f335abe8050e1ff7755076981051b Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Thu, 9 Feb 2023 13:06:56 +0100 Subject: [PATCH 033/294] PointCloudOctant proj. screen size calc: suppresses negative tan values --- src/PointCloud/Core/PointCloudOctant.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Core/PointCloudOctant.cs b/src/PointCloud/Core/PointCloudOctant.cs index bbbc84acd..786b9f3d2 100644 --- a/src/PointCloud/Core/PointCloudOctant.cs +++ b/src/PointCloud/Core/PointCloudOctant.cs @@ -132,7 +132,7 @@ public void ComputeScreenProjectedSize(double3 camPos, int screenHeight, float f var distance = (translatedCenter - camPos).Length; if (translatedCenter == camPos) distance = 0.0001f; - var slope = (float)System.Math.Tan(fov / 2d); + var slope = System.Math.Abs((float)System.Math.Tan(fov / 2d)); var maxRad = System.Math.Max(System.Math.Max(scaledRad.x, scaledRad.y), scaledRad.z); ProjectedScreenSize = screenHeight / 2d * maxRad / (slope * distance); From 943bf43d5c631fc9986dd7dd36fe81fa8434daa7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 07:23:10 +0000 Subject: [PATCH 034/294] Bump Microsoft.JSInterop.WebAssembly from 7.0.0 to 7.0.2 Bumps [Microsoft.JSInterop.WebAssembly](https://github.com/dotnet/aspnetcore) from 7.0.0 to 7.0.2. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.0...v7.0.2) --- updated-dependencies: - dependency-name: Microsoft.JSInterop.WebAssembly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 2 +- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 2 +- .../Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 2 +- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index 53865bb58..24982d0a2 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index aa004e421..0a207c8e9 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -17,7 +17,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index f0ec3aa52..a833c52ad 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index d9f892026..7bf3cc9be 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index a65b4abc8..ece2b8ca5 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 28c32af76..f2bec4380 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 3ca822bbc..10ef10879 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -25,7 +25,7 @@ - + From a14da4c261c3e2106ef440ef88f6165b116db644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 07:23:11 +0000 Subject: [PATCH 035/294] Bump Microsoft.JSInterop from 7.0.0 to 7.0.2 Bumps [Microsoft.JSInterop](https://github.com/dotnet/aspnetcore) from 7.0.0 to 7.0.2. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.0...v7.0.2) --- updated-dependencies: - dependency-name: Microsoft.JSInterop dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj | 2 +- .../Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj index f7c9d818b..a0a26f55d 100644 --- a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj +++ b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj @@ -16,6 +16,6 @@ analyzers - + diff --git a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj index 9d61f03f8..8bd75d6df 100644 --- a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj +++ b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj @@ -23,7 +23,7 @@ analyzers - + From 060b73b0c60ae8b0f9fc34ed8a2ab22c6e2bd797 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 07:26:06 +0000 Subject: [PATCH 036/294] Bump Microsoft.AspNetCore.Components.WebAssembly from 7.0.0 to 7.0.2 Bumps [Microsoft.AspNetCore.Components.WebAssembly](https://github.com/dotnet/aspnetcore) from 7.0.0 to 7.0.2. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.0...v7.0.2) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 2 +- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 2 +- .../Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 2 +- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index 24982d0a2..b003635c1 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index 0a207c8e9..a823f638a 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index a833c52ad..4836f2310 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 7bf3cc9be..75e7492d5 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index ece2b8ca5..3ed55c421 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index f2bec4380..0731235cc 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 10ef10879..1486f7cca 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -23,7 +23,7 @@ analyzers - + From 125b2e9e921b6831aa184657b03ff48e5f53ca74 Mon Sep 17 00:00:00 2001 From: RedImp1470 Date: Mon, 13 Feb 2023 08:52:12 +0000 Subject: [PATCH 037/294] Linting --- src/PointCloud/Common/IPointWriter.cs | 12 ++++++------ src/PointCloud/Potree/Potree2LAS.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index 29b8ef7d0..a6b4e220e 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -20,7 +20,7 @@ public interface IPointWriterHierarchy /// /// Hierarchy step size /// - public int StepSize { get; set; } + public int StepSize { get; set; } /// /// Depth of the Hierarchy @@ -63,12 +63,12 @@ public interface IPointWriterMetadata /// The point cloud hierarchy information /// public IPointWriterHierarchy Hierarchy { get; set; } - + /// /// Global offset of each point /// public double3 Offset { get; set; } - + /// /// Global scale value. Points are being converted to int /// During load this scale factor is being applied to convert int to double @@ -84,7 +84,7 @@ public interface IPointWriterMetadata /// Global of the point cloud /// public AABBd BoundingBox { get; set; } - + /// /// The encoding of every point, as we save the point cloud as elements /// Default is @@ -106,7 +106,7 @@ public interface IPointWriter /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . /// public IPointAccessor PointAccessor { get; } - + /// /// Returns the point type. /// @@ -131,4 +131,4 @@ public interface IPointWriter /// Necessary metadata, e. g. global scale, etc. public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); } -} +} \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index a2ffd92e5..bb4bf7f47 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -208,4 +208,4 @@ public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory Date: Mon, 13 Feb 2023 14:52:48 +0100 Subject: [PATCH 038/294] Picking via FBO "works" --- Examples/Complete/Picking/Core/Picking.cs | 23 ++++++++++----- .../Core/PointCloudPotree2Core.cs | 4 +-- src/Engine/Common/IRenderContextImp.cs | 13 +++++++++ src/Engine/Core/RenderContext.cs | 18 ++++++++++++ src/Engine/Core/ScenePicker.cs | 29 ++++++++++++------- .../Imp/Graphics/Android/RenderContextImp.cs | 27 +++++++++++++++++ .../Imp/Graphics/Blazor/RenderContextImp.cs | 27 +++++++++++++++++ .../Imp/Graphics/Desktop/RenderContextImp.cs | 28 ++++++++++++++++++ 8 files changed, 148 insertions(+), 21 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 1a0999a9e..dd0c44ed1 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -129,29 +129,36 @@ public override void Update() // RenderAFrame is called once a frame public override void RenderAFrame() { + // RC.EnableStencil(); + // RC.SetStencilMask(0xFF); + // _sceneRenderer.Render(RC); + //RC.DisableStencil(); + + // Picking - //if (_pick) - //{ + if (_pick) + { float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - var newPick = (MeshPickResult)_scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) + var newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); + if (newPick != null) Diagnostics.Debug(newPick.Node.Name); if (newPick?.Node != _currentPick?.Node) { - if (_currentPick != null) + if (_currentPick != null && _currentPick is MeshPickResult mpr) { - var ef = _currentPick.Node.GetComponent(); + var ef = mpr.Node.GetComponent(); ef.SurfaceInput.Albedo = _oldColor; } - if (newPick != null) + if (newPick != null && newPick is MeshPickResult newMpr) { - var ef = newPick.Node.GetComponent(); + var ef = newMpr.Node.GetComponent(); _oldColor = ef.SurfaceInput.Albedo; ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; } @@ -160,7 +167,7 @@ public override void RenderAFrame() } _pick = false; - //} + } //_guiRenderer.Render(RC); diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 4f70e561b..df77dcf12 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -75,7 +75,7 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloudNode.Components[3] = _pointCloud; // re-generate picker and octree - _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, _rc, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); @@ -173,7 +173,7 @@ public void Init() // new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree, null) //}); - _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, _rc, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); diff --git a/src/Engine/Common/IRenderContextImp.cs b/src/Engine/Common/IRenderContextImp.cs index bfe354d72..5eda1d2fa 100644 --- a/src/Engine/Common/IRenderContextImp.cs +++ b/src/Engine/Common/IRenderContextImp.cs @@ -59,6 +59,19 @@ public interface IRenderContextImp /// void DisableDepthClamp(); + /// + /// Retrieve pixels from bound framebuffer + /// + /// x pixel position + /// y pixel position + /// format to retrieve, this has to match the current bound FBO! + /// how many pixel in x direction + /// how many pixel in y direction + /// with pixel content + /// Does usually not throw on error (e. g. wrong pixel format, out of bounds, etc), uses GL.GetError() to retrieve + /// potential error + public ReadOnlySpan ReadPixels(int x, int y, ImagePixelFormat pixelFormat, int width, int height); + /// /// Creates a shader object from vertex shader source code and pixel shader source code. /// diff --git a/src/Engine/Core/RenderContext.cs b/src/Engine/Core/RenderContext.cs index 3e1d3c176..52b4455da 100644 --- a/src/Engine/Core/RenderContext.cs +++ b/src/Engine/Core/RenderContext.cs @@ -1,3 +1,4 @@ +using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core.Effects; @@ -1745,6 +1746,23 @@ public void DisableDepthClamp() _rci.DisableDepthClamp(); } + + /// + /// Retrieve pixels from bound framebuffer + /// + /// x pixel position + /// y pixel position + /// format to retrieve, this has to match the current bound FBO! + /// how many pixel in x direction + /// how many pixel in y direction + /// with pixel content + /// Does usually not throw on error (e. g. wrong pixel format, out of bounds, etc), uses GL.GetError() to retrieve + /// potential error + public ReadOnlySpan ReadPixels(int x, int y, ImagePixelFormat pixelFormat, int width, int height) + { + return _rci.ReadPixels(x, y, pixelFormat, width, height); + } + /// /// Returns the hardware capabilities. /// diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 19bb7d008..ae6aac08a 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -561,6 +561,7 @@ public void HandleMesh(Mesh mesh) default: // non-triangle picking is only possible with camera Guard.IsNotNull(CurrentCamera, nameof(CurrentCamera)); + Guard.IsNotNull(State, nameof(State)); var cam = new Camera(CurrentCamera.ProjectionMethod, 1, 1000, CurrentCamera.Fov); @@ -569,7 +570,7 @@ public void HandleMesh(Mesh mesh) var container = new SceneContainer { Children = new List - { + { new SceneNode { Components = new List @@ -578,22 +579,28 @@ public void HandleMesh(Mesh mesh) cam } } - // add current node without material here - } + } }; - // generate texture size 1,1, move the current camera at mouse position - _writableTexture ??= new WritableTexture(RenderTargetTextureTypes.Albedo, new ImagePixelFormat(ColorFormat.RGB), 10, 10, false); - var iVP = (State.Projection * State.View).Invert(); - _cameraPosition.Translation = iVP * new float3(PickPosClip.x, PickPosClip.y, 0); - //cam.RenderTexture = _writableTexture; + + _writableTexture ??= new WritableTexture(RenderTargetTextureTypes.Albedo, new ImagePixelFormat(ColorFormat.RGB), _rc.ViewportWidth, _rc.ViewportHeight, false); + _cameraPosition.Matrix = State.View.Invert(); + cam.RenderTexture = _writableTexture; cam.FrustumCullingOn = false; var compCopy = CurrentNode.Components.ToArray().ToList(); - container.Children.Insert(1, new SceneNode { Components = compCopy }); _sceneRenderer = new SceneRendererForward(container); - _sceneRenderer.Render(_rc); - Console.WriteLine("Render"); + _sceneRenderer.Render(_rc); + + var pixels = _rc.ReadPixels((int)Input.Mouse.X, (int)(_canvasHeight - Input.Mouse.Y), new ImagePixelFormat(ColorFormat.RGB), 1, 1); + + if (pixels[0] != 0) + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model + }); break; } diff --git a/src/Engine/Imp/Graphics/Android/RenderContextImp.cs b/src/Engine/Imp/Graphics/Android/RenderContextImp.cs index dec92429f..c639857b1 100644 --- a/src/Engine/Imp/Graphics/Android/RenderContextImp.cs +++ b/src/Engine/Imp/Graphics/Android/RenderContextImp.cs @@ -2786,6 +2786,33 @@ public void DeleteStorageBuffer(IBufferHandle storageBufferHandle) #region Picking related Members + /// + /// Retrieve pixels from bound framebuffer + /// + /// x pixel position + /// y pixel position + /// format to retrieve, this has to match the current bound FBO! + /// how many pixel in x direction + /// how many pixel in y direction + /// with pixel content + /// Does usually not throw on error (e. g. wrong pixel format, out of bounds, etc), uses GL.GetError() to retrieve + /// potential error + public ReadOnlySpan ReadPixels(int x, int y, ImagePixelFormat pixelFormat, int width, int height) + { + var format = GetTexturePixelInfo(pixelFormat); + var data = new byte[width * height * pixelFormat.BytesPerPixel]; + + GL.ReadPixels(x, y, 1, 1, format.Format, format.PxType, data); + + var err = GL.GetErrorCode(); + if (err != ErrorCode.NoError) + { + throw new Exception($"ReadPixel failed with error code {err}!"); + } + + return data; + } + /// /// Retrieves a sub-image of the given region. /// diff --git a/src/Engine/Imp/Graphics/Blazor/RenderContextImp.cs b/src/Engine/Imp/Graphics/Blazor/RenderContextImp.cs index 3c7bf2030..ecec100d0 100644 --- a/src/Engine/Imp/Graphics/Blazor/RenderContextImp.cs +++ b/src/Engine/Imp/Graphics/Blazor/RenderContextImp.cs @@ -2679,6 +2679,33 @@ public void DebugLine(float3 start, float3 end, float4 color) #region Picking related Members + /// + /// Retrieve pixels from bound framebuffer + /// + /// x pixel position + /// y pixel position + /// format to retrieve, this has to match the current bound FBO! + /// how many pixel in x direction + /// how many pixel in y direction + /// with pixel content + /// Does usually not throw on error (e. g. wrong pixel format, out of bounds, etc), uses GL.GetError() to retrieve + /// potential error + public ReadOnlySpan ReadPixels(int x, int y, ImagePixelFormat pixelFormat, int width, int height) + { + var format = GetTexturePixelInfo(pixelFormat); + var data = new byte[width * height * pixelFormat.BytesPerPixel]; + + gl2.ReadPixels(x, y, 1, 1, format.Format, format.PxType, data); + + var err = gl2.GetError(); + if (err != NO_ERROR) + { + throw new Exception($"ReadPixel failed with error code {err}!"); + } + + return data; + } + /// /// Retrieves a sub-image of the given region. /// diff --git a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs index 74792d954..28699ca36 100644 --- a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs +++ b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs @@ -6,6 +6,7 @@ using Fusee.Engine.Imp.SharedAll; using Fusee.Math.Core; using OpenTK.Graphics.OpenGL; +using SixLabors.ImageSharp.Formats; using System; using System.Collections.Generic; using System.Linq; @@ -1236,6 +1237,33 @@ public void EnableDepthClamp() GL.Enable(EnableCap.DepthClamp); } + /// + /// Retrieve pixels from bound framebuffer + /// + /// x pixel position + /// y pixel position + /// format to retrieve, this has to match the current bound FBO! + /// how many pixel in x direction + /// how many pixel in y direction + /// with pixel content + /// Does usually not throw on error (e. g. wrong pixel format, out of bounds, etc), uses GL.GetError() to retrieve + /// potential error + public ReadOnlySpan ReadPixels(int x, int y, ImagePixelFormat pixelFormat, int width, int height) + { + var format = GetTexturePixelInfo(pixelFormat); + var data = new byte[width * height * pixelFormat.BytesPerPixel]; + + GL.ReadPixels(x, y, 1, 1, format.Format, format.PxType, data); + + var err = GL.GetError(); + if (err != ErrorCode.NoError) + { + throw new Exception($"ReadPixel failed with error code {err}!"); + } + + return data; + } + /// /// Disables depths clamping. /// From a027db7109a0ef021d6023090c9ca4900fbad241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 02:58:27 +0000 Subject: [PATCH 039/294] Bump ImGui.NET from 1.88.0 to 1.89.4 Bumps [ImGui.NET](https://github.com/mellinoe/imgui.net) from 1.88.0 to 1.89.4. - [Release notes](https://github.com/mellinoe/imgui.net/releases) - [Commits](https://github.com/mellinoe/imgui.net/compare/v1.88...v1.89.4) --- updated-dependencies: - dependency-name: ImGui.NET dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj index 75cf8f47b..8eb4fac93 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj @@ -13,7 +13,7 @@ - + From 18ea7db39559d83bf780ca4351f1edc6de3ac386 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 02:58:31 +0000 Subject: [PATCH 040/294] Bump CommunityToolkit.Diagnostics from 8.0.0 to 8.1.0 Bumps [CommunityToolkit.Diagnostics](https://github.com/CommunityToolkit/dotnet) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/CommunityToolkit/dotnet/releases) - [Commits](https://github.com/CommunityToolkit/dotnet/compare/v8.0.0...v8.1.0) --- updated-dependencies: - dependency-name: CommunityToolkit.Diagnostics dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/Engine/Core/Fusee.Engine.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 33b1c9a3a..a43be1b91 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -9,7 +9,7 @@ - + From 832384cf91621d4ef5cb0b6ee42cd21ddab1eb02 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Feb 2023 16:08:47 +0100 Subject: [PATCH 041/294] First MemoryOwner & MemoryMappedAccess --- src/Engine/Core/Fusee.Engine.Core.csproj | 27 +- src/PointCloud/Common/Accessors/PointTypes.cs | 2 + .../Common/Fusee.PointCloud.Common.csproj | 9 +- src/PointCloud/Common/IPointReader.cs | 7 +- .../Core/Fusee.PointCloud.Core.csproj | 8 +- src/PointCloud/Core/MeshMaker.cs | 72 +-- src/PointCloud/Core/PointCloudDataHandler.cs | 22 +- .../Potree/Fusee.PointCloud.Potree.csproj | 1 + src/PointCloud/Potree/Potree2LAS.cs | 2 +- src/PointCloud/Potree/V2/Data/PotreePoint.cs | 14 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 296 +++++----- src/PointCloud/Potree/V2/Potree2RwBase.cs | 40 +- src/PointCloud/Potree/V2/Potree2Writer.cs | 512 +++++++++--------- 13 files changed, 527 insertions(+), 485 deletions(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 5921e0e13..6b8afd7d1 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -9,7 +9,8 @@ - + + @@ -40,18 +41,18 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointTypes.cs b/src/PointCloud/Common/Accessors/PointTypes.cs index 92d8c83f1..00d2f9453 100644 --- a/src/PointCloud/Common/Accessors/PointTypes.cs +++ b/src/PointCloud/Common/Accessors/PointTypes.cs @@ -1,5 +1,6 @@  using Fusee.Math.Core; +using System.Runtime.InteropServices; namespace Fusee.PointCloud.Common.Accessors { @@ -207,6 +208,7 @@ public struct PosD3NorF3ColF3 /// /// Point type: , , . /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3ColF3LblB { /// diff --git a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj index ef134cf84..7fca6ba19 100644 --- a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj +++ b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj @@ -1,5 +1,5 @@  - + netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml @@ -13,7 +13,12 @@ - + + + + + + diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 8a63fceea..ed91a9505 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -1,4 +1,7 @@ -using Fusee.PointCloud.Common.Accessors; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.PointCloud.Common.Accessors; +using System; +using System.Buffers; namespace Fusee.PointCloud.Common { @@ -34,7 +37,7 @@ public interface IPointReader /// /// The generic point type. /// The unique id of the octant. - public TPoint[] LoadNodeData(OctantId id) where TPoint : new(); + public MemoryOwner LoadNodeData(OctantId id) where TPoint : struct; } } \ No newline at end of file diff --git a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj index 7a280dd13..2dfe7178d 100644 --- a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj +++ b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj @@ -1,9 +1,13 @@  - + netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml + + + + @@ -12,5 +16,5 @@ - + diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 6381debad..3c07977c4 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -1,4 +1,5 @@ -using Fusee.Engine.Common; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; @@ -7,6 +8,7 @@ using Fusee.PointCloud.Core.Accessors; using System; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace Fusee.PointCloud.Core { @@ -22,7 +24,7 @@ public static class MeshMaker /// The generic point cloud points. /// The method that defines how to create a GpuMesh from the point cloud points. /// - public static IEnumerable CreateMeshes(PointAccessor pointAccessor, TPoint[] points, CreateGpuData createGpuDataHandler, OctantId octantId) + public static IEnumerable CreateMeshes(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { List meshes; @@ -41,18 +43,18 @@ public static IEnumerable CreateMeshes(PointAccessor else numberOfPointsInMesh = maxVertCount; - TPoint[] pointsPerMesh; + MemoryOwner pointsPerMesh; if (ptCnt > maxVertCount) { - pointsPerMesh = new TPoint[numberOfPointsInMesh]; - Array.Copy(points, i, pointsPerMesh, 0, numberOfPointsInMesh); + pointsPerMesh = MemoryOwner.Allocate(numberOfPointsInMesh); + points.Span.Slice(i, numberOfPointsInMesh).CopyTo(pointsPerMesh.Span.Slice(0)); } else { pointsPerMesh = points; } - meshes.Add(createGpuDataHandler(pointAccessor, pointsPerMesh, octantId)); + meshes.Add(createGpuDataHandler(pointsPerMesh, octantId)); meshCnt++; } return meshes; @@ -63,36 +65,37 @@ public static IEnumerable CreateMeshes(PointAccessor /// /// Can be of type or . The latter is used when rendering instanced. /// The generic point type. - /// The point accessor allows access to the point data without casting to a explicit point type."/> /// The generic point cloud points. /// The method that defines how to create a InstanceData from the point cloud points. /// - public static IEnumerable CreateInstanceData(PointAccessor pointAccessor, TPoint[] points, CreateGpuData createGpuDataHandler, OctantId octantId) + public static IEnumerable CreateInstanceData(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { return new List { - createGpuDataHandler(pointAccessor, points, octantId) + createGpuDataHandler(points, octantId) }; } /// /// Returns meshes for point clouds of type . /// - /// The point accessor allows access to the point data without casting to explicit a explicit point type."/> /// The lists of "raw" points. - /// The id of the octant. - public static GpuMesh CreateMeshPosD3(PointAccessor pointAccessor, TPoint[] points) + public static GpuMesh CreateMeshPosD3(MemoryOwner points) where TPoint : struct { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - var firstPos = (float3)pointAccessor.GetPositionFloat3_64(ref points[0]); + + var pointsCasted = MemoryMarshal.Cast(points.Span); + + + var firstPos = (float3)pointsCasted[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new uint[numberOfPointsInMesh]; var boundingBox = new AABBf(firstPos, firstPos); for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointAccessor.GetPositionFloat3_64(ref points[i]); + var pos = (float3)pointsCasted[i].Position; vertices[i] = pos; boundingBox |= pos; @@ -106,15 +109,17 @@ public static GpuMesh CreateMeshPosD3(PointAccessor pointAccesso /// /// Returns meshes for point clouds of type . /// - /// The point accessor allows access to the point data without casting to explicit a explicit point type."/> /// The lists of "raw" points. /// The id of the octant. - public static GpuMesh CreateMeshPosD3ColF3LblB(PointAccessor pointAccessor, TPoint[] points, OctantId octantId) + public static GpuMesh CreateMeshPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - var firstPos = (float3)pointAccessor.GetPositionFloat3_64(ref points[0]); + // we know that we have a MeshPosD3ColF3LblB + var pointsCasted = MemoryMarshal.Cast(points.Span); + + var firstPos = (float3)pointsCasted[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new uint[numberOfPointsInMesh]; var colors = new uint[numberOfPointsInMesh]; @@ -123,18 +128,18 @@ public static GpuMesh CreateMeshPosD3ColF3LblB(PointAccessor poi for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointAccessor.GetPositionFloat3_64(ref points[i]); + var pos = (float3)pointsCasted[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (uint)i; - var col = pointAccessor.GetColorFloat3_32(ref points[i]);//points[i].Color; + var col = pointsCasted[i].Color; colors[i] = ColorToUInt((int)col.r, (int)col.g, (int)col.b, 255); flags[i] = 1 << 30; //TODO: add labels correctly - var label = pointAccessor.GetLabelUInt_8(ref points[i]);//points[i].Label; + var label = pointsCasted[i].Label; } var mesh = ModuleExtensionPoint.CreateGpuMesh(PrimitiveType.Points, vertices, triangles, null, colors, null, null, null, null, null, null, null, flags); mesh.BoundingBox = boundingBox; @@ -144,15 +149,17 @@ public static GpuMesh CreateMeshPosD3ColF3LblB(PointAccessor poi /// /// Returns meshes for point clouds of type . /// - /// The point accessor allows access to the point data without casting to explicit a explicit point type."/> /// The lists of "raw" points. /// The id of the octant. - public static Mesh CreateDynamicMeshPosD3ColF3LblB(PointAccessor pointAccessor, TPoint[] points, OctantId octantId) + public static Mesh CreateDynamicMeshPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - var firstPos = (float3)pointAccessor.GetPositionFloat3_64(ref points[0]); + // we know that we have a MeshPosD3ColF3LblB + var pointsCasted = MemoryMarshal.Cast(points.Span); + + var firstPos = (float3)pointsCasted[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new uint[numberOfPointsInMesh]; var colors = new uint[numberOfPointsInMesh]; @@ -161,18 +168,18 @@ public static Mesh CreateDynamicMeshPosD3ColF3LblB(PointAccessor for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointAccessor.GetPositionFloat3_64(ref points[i]); + var pos = (float3)pointsCasted[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (uint)i; - var col = pointAccessor.GetColorFloat3_32(ref points[i]);//points[i].Color; + var col = pointsCasted[i].Color; colors[i] = ColorToUInt((int)col.r, (int)col.g, (int)col.b, 255); flags[i] = 1 << 30; //TODO: add labels correctly - var label = pointAccessor.GetLabelUInt_8(ref points[i]);//points[i].Label; + var label = pointsCasted[i].Label; } return new Mesh(triangles, vertices, null, null, null, null, null, null, colors, null, null, flags) @@ -185,15 +192,16 @@ public static Mesh CreateDynamicMeshPosD3ColF3LblB(PointAccessor /// /// Returns meshes for point clouds of type . /// - /// The point accessor allows access to the point data without casting to explicit a explicit point type."/> /// The lists of "raw" points. /// The id of the octant. - public static InstanceData CreateInstanceDataPosD3ColF3LblB(PointAccessor pointAccessor, TPoint[] points, OctantId octantId) + public static InstanceData CreateInstanceDataPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - var firstPos = (float3)pointAccessor.GetPositionFloat3_64(ref points[0]); + var pointsCasted = MemoryMarshal.Cast(points.Span); + + var firstPos = (float3)pointsCasted[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new ushort[numberOfPointsInMesh]; var colors = new float4[numberOfPointsInMesh]; @@ -201,16 +209,16 @@ public static InstanceData CreateInstanceDataPosD3ColF3LblB(PointAccesso for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointAccessor.GetPositionFloat3_64(ref points[i]); + var pos = (float3)pointsCasted[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (ushort)i; - colors[i] = new float4(pointAccessor.GetColorFloat3_32(ref points[i]) / 256, 1.0f); + colors[i] = new float4(pointsCasted[i].Color / 256, 1.0f); //TODO: add labels correctly - var label = pointAccessor.GetLabelUInt_8(ref points[i]);//points[i].Label; + var label = pointsCasted[i].Label; } return new InstanceData(points.Length, vertices, null, null, colors) diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 2cd8f27dc..b1fef2ba5 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -1,3 +1,5 @@ +using CommunityToolkit.HighPerformance; +using CommunityToolkit.HighPerformance.Buffers; using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; @@ -5,6 +7,7 @@ using Fusee.PointCloud.Core.Accessors; using Microsoft.Extensions.Caching.Memory; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -21,7 +24,7 @@ namespace Fusee.PointCloud.Core /// Delegate that allows to inject the loading method of the PointReader - loads the points from file. /// /// Unique ID of an octant. - public delegate TPoint[] LoadPointsHandler(OctantId guid); + public delegate MemoryOwner LoadPointsHandler(OctantId guid); /// /// Delegate for a method that tries to get the mesh(es) of an octant. If they are not cached yet, they should be created an added to the _gpuDataCache. @@ -52,7 +55,7 @@ namespace Fusee.PointCloud.Core /// The that can be used to access the point data without casting the points. /// The point cloud points as generic array. /// - public delegate TGpuData CreateGpuData(PointAccessor ptAccessor, TPoint[] points, OctantId octantId); + public delegate TGpuData CreateGpuData(MemoryOwner points, OctantId octantId); /// /// Manages the caching and loading of point and mesh data. @@ -64,7 +67,7 @@ namespace Fusee.PointCloud.Core /// /// Caches loaded points. /// - private MemoryCache _pointCache; + private MemoryCache> _pointCache; /// /// Caches loaded points. @@ -106,6 +109,15 @@ public PointCloudDataHandler(PointAccessor pointAccessor, CreateGpuData< DisposeQueue = new Dictionary>((8 ^ 8) / 8); _gpuDataCache.HandleEvictedItem = OnItemEvictedFromCache; + + _pointCache.HandleEvictedItem += (object key, object? value, EvictionReason reason, object? state) => + { + if(value != null && value is MemoryOwner mo) + { + mo.Dispose(); + } + }; + } /// @@ -131,9 +143,9 @@ public override IEnumerable GetGpuData(OctantId octantId) else if (_pointCache.TryGetValue(octantId, out var points)) { if (!_doRenderInstanced) - gpuData = MeshMaker.CreateMeshes(_pointAccessor, points, _createGpuDataHandler, octantId); + gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); else - gpuData = MeshMaker.CreateInstanceData(_pointAccessor, points, _createGpuDataHandler, octantId); + gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); _gpuDataCache.Add(octantId, gpuData); } //no points yet, probably in loading queue diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index 52c49bd00..5f8aa3d3c 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -7,6 +7,7 @@ + diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index bd10b72e5..158039684 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -154,7 +154,7 @@ public static void WritePotree2LAS(IEnumerable points, PotreeMetada Y = (uint)((ptFlipped.y) / scaleFactor.y), Z = (uint)((ptFlipped.z) / scaleFactor.z), Classification = p.Classification, - Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), + //Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), diff --git a/src/PointCloud/Potree/V2/Data/PotreePoint.cs b/src/PointCloud/Potree/V2/Data/PotreePoint.cs index d03d8be07..aaf890dd1 100644 --- a/src/PointCloud/Potree/V2/Data/PotreePoint.cs +++ b/src/PointCloud/Potree/V2/Data/PotreePoint.cs @@ -2,16 +2,16 @@ namespace Fusee.PointCloud.Potree.V2.Data { - public class PotreePoint + public struct PotreePoint { public double3 Position; - public short Intensity; - public byte ReturnNumber; - public byte NumberOfReturns; + //public short Intensity; + //public byte ReturnNumber; + //public byte NumberOfReturns; public byte Classification; - public byte ScanAngleRank; - public byte UserData; - public byte PointSourceId; + //public byte ScanAngleRank; + //public byte UserData; + //public byte PointSourceId; public float3 Color; } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 6b6c8bf0a..a46bd374d 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -1,3 +1,5 @@ +using CommunityToolkit.Diagnostics; +using CommunityToolkit.HighPerformance.Buffers; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; @@ -8,7 +10,13 @@ using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2.Data; using System; +using System.Buffers; +using System.Diagnostics; using System.IO; +using System.IO.MemoryMappedFiles; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Threading; namespace Fusee.PointCloud.Potree.V2 { @@ -21,7 +29,9 @@ public class Potree2Reader : Potree2RwBase, IPointReader /// Initializes a Potree 2 reader for the given Potree dataset /// /// - public Potree2Reader(ref PotreeData potreeData) : base(ref potreeData) { } + public Potree2Reader(ref PotreeData potreeData) : base(ref potreeData) + { + } /// /// Returns a renderable point cloud component. @@ -94,229 +104,189 @@ public IPointCloudOctree GetOctree() /// The generic point type. /// The unique id of the octant. /// - public TPoint[] LoadNodeData(OctantId id) where TPoint : new() + public MemoryOwner LoadNodeData(OctantId id) where TPoint : struct { - TPoint[] points = null; - var node = FindNode(ref _potreeData.Hierarchy, id); if (node != null) { - points = LoadNodeData(node); + return LoadNodeData(node); } - return points; + return null; } - public TPoint[] LoadNodeData(PotreeNode potreeNode) where TPoint : new() + public MemoryOwner LoadNodeData(PotreeNode potreeNode) where TPoint : struct { - TPoint[] points = null; - if (potreeNode != null) { - points = ReadNodeData(potreeNode); potreeNode.IsLoaded = true; + return ReadNodeData(potreeNode); } - return points; + return null; } - private TPoint[] ReadNodeData(PotreeNode node) where TPoint : new() + + private MemoryOwner ReadNodeData(PotreeNode node) where TPoint : struct { - var points = new TPoint[node.NumPoints]; - for (int i = 0; i < node.NumPoints; i++) - { - points[i] = new TPoint(); - } + // PosD3 ColF3 LblB + + Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + + var potreePointSize = (int)node.NumPoints * _potreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; + + var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); + + OctreeMappedViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); - var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); + var pointCount = 0; - // Commented code is to read the entire Potree2 file format. Since we don't use everything atm unused - // things are commented for performance. - for (int i = 0; i < node.NumPoints; i++) + for (var i = 0; i < pointArray.Length; i += _potreeData.Metadata.PointSize) { - if (offsetPosition > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + var posSlice = new Span(pointArray).Slice(i + offsetPosition, Marshal.SizeOf() * 3); + var pos = MemoryMarshal.Cast(posSlice); - double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; - double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; - double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; + double x = pos[0] * _potreeData.Metadata.Scale.x; + double y = pos[1] * _potreeData.Metadata.Scale.y; + double z = pos[2] * _potreeData.Metadata.Scale.z; - double3 position = new(x, y, z); - position = Potree2Consts.YZflip * position; + double3 position = new(x, y, z); + position = Potree2Consts.YZflip * position; - ((PointAccessor)PointAccessor).SetPositionFloat3_64(ref points[i], position); - } + var posSpan = MemoryMarshal.Cast(position.ToArray()); - //if (offsetIntensity > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // Int16 intensity = binaryReader.ReadInt16(); - //} - //if (offsetReturnNumber > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // byte returnNumber = binaryReader.ReadByte(); - //} - //if (offsetNumberOfReturns > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // byte numberOfReturns = binaryReader.ReadByte(); - //} - - if (offsetClassification > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + var colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); + var rgb = MemoryMarshal.Cast(colorSlice); - byte label = binaryReader.ReadByte(); + float3 color = float3.Zero; - ((PointAccessor)PointAccessor).SetLabelUInt_8(ref points[i], label); - } + color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); + color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); + color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); - //else if (offsetScanAngleRank > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // byte scanAngleRank = binaryReader.ReadByte(); - //} - //else if (offsetUserData > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // byte userData = binaryReader.ReadByte(); - //} - //else if (offsetPointSourceId > -1) - //{ - // binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // byte pointSourceId = binaryReader.ReadByte(); - //} - - if (offsetColor > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + var colorSpan = MemoryMarshal.Cast(color.ToArray()); - ushort r = binaryReader.ReadUInt16(); - ushort g = binaryReader.ReadUInt16(); - ushort b = binaryReader.ReadUInt16(); + byte label = new Span(pointArray).Slice(i + offsetClassification, Marshal.SizeOf())[0]; - float3 color = float3.Zero; + var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); + posSpan.CopyTo(currentMemoryPt.Slice(0)); + colorSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length)); + currentMemoryPt[^1] = label; - color.r = ((byte)(r > 255 ? r / 256 : r)); - color.g = ((byte)(g > 255 ? g / 256 : g)); - color.b = ((byte)(b > 255 ? b / 256 : b)); + var currentPt = MemoryMarshal.Cast(currentMemoryPt); - ((PointAccessor)PointAccessor).SetColorFloat3_32(ref points[i], color); - } + pointCount++; } - binaryReader.Close(); - binaryReader.Dispose(); - - return points; + return returnMemory; } - public TPotreePoint[] ReadRawPoints(OctantId oid) where TPotreePoint : PotreePoint, new() - { - var node = FindNode(ref _potreeData.Hierarchy, oid); + //public TPotreePoint[] ReadRawPoints(OctantId oid) where TPotreePoint : struct + //{ + // var node = FindNode(ref _potreeData.Hierarchy, oid); - var points = new TPotreePoint[node.NumPoints]; + // var points = new TPotreePoint[node.NumPoints]; - for (var i = 0; i < points.Length; i++) - points[i] = new TPotreePoint(); - var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); + // for (var i = 0; i < points.Length; i++) + // points[i] = new TPotreePoint(); - for (int i = 0; i < node.NumPoints; i++) - { - if (offsetPosition > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + // var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); - double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; - double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; - double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; + // for (int i = 0; i < node.NumPoints; i++) + // { + // if (offsetPosition > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - double3 position = new(x, y, z); - position = Potree2Consts.YZflip * position; + // double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; + // double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; + // double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; - points[i].Position = position; - } + // double3 position = new(x, y, z); + // position = Potree2Consts.YZflip * position; - if (offsetIntensity > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - Int16 intensity = binaryReader.ReadInt16(); + // points[i].Position = position; + // } - points[i].Intensity = intensity; - } - if (offsetReturnNumber > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - byte returnNumber = binaryReader.ReadByte(); + // if (offsetIntensity > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + // Int16 intensity = binaryReader.ReadInt16(); - points[i].ReturnNumber = returnNumber; - } - if (offsetNumberOfReturns > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - byte numberOfReturns = binaryReader.ReadByte(); + // points[i].Intensity = intensity; + // } + // if (offsetReturnNumber > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + // byte returnNumber = binaryReader.ReadByte(); - points[i].NumberOfReturns = numberOfReturns; - } + // points[i].ReturnNumber = returnNumber; + // } + // if (offsetNumberOfReturns > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + // byte numberOfReturns = binaryReader.ReadByte(); - if (offsetClassification > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + // points[i].NumberOfReturns = numberOfReturns; + // } - byte label = binaryReader.ReadByte(); + // if (offsetClassification > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - points[i].Classification = label; - } + // byte label = binaryReader.ReadByte(); - else if (offsetScanAngleRank > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - byte scanAngleRank = binaryReader.ReadByte(); + // points[i].Classification = label; + // } - points[i].ScanAngleRank = scanAngleRank; - } - else if (offsetUserData > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - byte userData = binaryReader.ReadByte(); + // else if (offsetScanAngleRank > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + // byte scanAngleRank = binaryReader.ReadByte(); - points[i].UserData = userData; - } - else if (offsetPointSourceId > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - byte pointSourceId = binaryReader.ReadByte(); + // points[i].ScanAngleRank = scanAngleRank; + // } + // else if (offsetUserData > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + // byte userData = binaryReader.ReadByte(); - points[i].PointSourceId = pointSourceId; - } + // points[i].UserData = userData; + // } + // else if (offsetPointSourceId > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + // byte pointSourceId = binaryReader.ReadByte(); - if (offsetColor > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + // points[i].PointSourceId = pointSourceId; + // } - ushort r = binaryReader.ReadUInt16(); - ushort g = binaryReader.ReadUInt16(); - ushort b = binaryReader.ReadUInt16(); + // if (offsetColor > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - float3 color = float3.Zero; + // ushort r = binaryReader.ReadUInt16(); + // ushort g = binaryReader.ReadUInt16(); + // ushort b = binaryReader.ReadUInt16(); - color.r = ((byte)(r > 255 ? r / 256 : r)); - color.g = ((byte)(g > 255 ? g / 256 : g)); - color.b = ((byte)(b > 255 ? b / 256 : b)); + // float3 color = float3.Zero; - points[i].Color = color; - } - } + // color.r = ((byte)(r > 255 ? r / 256 : r)); + // color.g = ((byte)(g > 255 ? g / 256 : g)); + // color.b = ((byte)(b > 255 ? b / 256 : b)); - binaryReader.Close(); - binaryReader.Dispose(); + // points[i].Color = color; + // } + // } - return points; - } + // binaryReader.Close(); + // binaryReader.Dispose(); + + // return points; + //} private static void MapChildNodesRecursive(IPointCloudOctant octreeNode, PotreeNode potreeNode) { diff --git a/src/PointCloud/Potree/V2/Potree2RwBase.cs b/src/PointCloud/Potree/V2/Potree2RwBase.cs index bd15c01d6..96580bb5b 100644 --- a/src/PointCloud/Potree/V2/Potree2RwBase.cs +++ b/src/PointCloud/Potree/V2/Potree2RwBase.cs @@ -1,8 +1,13 @@ -using Fusee.PointCloud.Common; +using CommunityToolkit.Diagnostics; +using Fusee.PointCloud.Common; using Fusee.PointCloud.Common.Accessors; using Fusee.PointCloud.Core.Accessors; using Fusee.PointCloud.Potree.V2.Data; +using System; +using System.Collections.Generic; using System.IO; +using System.IO.MemoryMappedFiles; +using System.Threading; namespace Fusee.PointCloud.Potree.V2 { @@ -22,7 +27,35 @@ public abstract class Potree2RwBase protected int offsetPointSourceId = -1; protected int offsetColor = -1; - protected string OctreeFilePath => Path.Combine(_potreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName); + private string _octreeFilePath; + private MemoryMappedFile _octreeMappedFile; + private MemoryMappedViewAccessor _octreeViewAccessor; + + protected MemoryMappedViewAccessor OctreeMappedViewAccessor + { + get + { + Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); + Guard.IsNotNull(_octreeMappedFile, nameof(_octreeMappedFile)); + + return _octreeViewAccessor; + + } + } + + protected string OctreeFilePath + { + set + { + Guard.IsNotNullOrEmpty(value, nameof(value)); + Guard.IsTrue(File.Exists(value)); + + _octreeFilePath = value; + _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); + _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); + } + get => _octreeFilePath; + } public Potree2RwBase(ref PotreeData potreeData) { @@ -30,6 +63,8 @@ public Potree2RwBase(ref PotreeData potreeData) PointAccessor = new PosD3ColF3LblBAccessor(); CacheMetadata(); + + OctreeFilePath = Path.Combine(_potreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName); } /// @@ -103,5 +138,6 @@ public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId { return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); } + } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 2e623d918..d13f84012 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -29,171 +29,171 @@ public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } BinaryReader binaryReader = new BinaryReader(readStream); BinaryWriter binaryWriter = new BinaryWriter(writeStream); - foreach (var node in _potreeData.Hierarchy.Nodes) - { - if (nodeSelector(node)) - { - octantCount++; - - var point = new PotreePoint(); - - for (int i = 0; i < node.NumPoints; i++) - { - if (offsetPosition > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; - double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; - double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; - - double3 position = new(x, y, z); - position = Potree2Consts.YZflip * position; - - point.Position = position; - } - - if (offsetIntensity > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - point.Intensity = binaryReader.ReadInt16(); - } - - if (offsetReturnNumber > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - point.ReturnNumber = binaryReader.ReadByte(); - } - - if (offsetNumberOfReturns > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - point.NumberOfReturns = binaryReader.ReadByte(); - } - - if (offsetClassification > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - point.Classification = binaryReader.ReadByte(); - } - - if (offsetScanAngleRank > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - point.ScanAngleRank = binaryReader.ReadByte(); - } - - if (offsetUserData > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - point.UserData = binaryReader.ReadByte(); - } - - if (offsetPointSourceId > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - point.PointSourceId = binaryReader.ReadByte(); - } - - if (offsetColor > -1) - { - binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - ushort r = binaryReader.ReadUInt16(); - ushort g = binaryReader.ReadUInt16(); - ushort b = binaryReader.ReadUInt16(); - - float3 color = float3.Zero; - - color.r = ((byte)(r > 255 ? r / 256 : r)); - color.g = ((byte)(g > 255 ? g / 256 : g)); - color.b = ((byte)(b > 255 ? b / 256 : b)); - - point.Color = color; - } - - if (pointSelector(point)) - { - action(point); - - if (!dryrun) - { - if (offsetPosition > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - var position = Potree2Consts.YZflip * point.Position; - - int x = Convert.ToInt32(position.x / _potreeData.Metadata.Scale.x); - int y = Convert.ToInt32(position.y / _potreeData.Metadata.Scale.y); - int z = Convert.ToInt32(position.z / _potreeData.Metadata.Scale.z); - - binaryWriter.Write(x); - binaryWriter.Write(y); - binaryWriter.Write(z); - } - - if (offsetIntensity > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Intensity); - } - - if (offsetReturnNumber > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ReturnNumber); - } - - if (offsetNumberOfReturns > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.NumberOfReturns); - } - - if (offsetClassification > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Classification); - } - - if (offsetScanAngleRank > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ScanAngleRank); - } - - if (offsetUserData > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.UserData); - } - - if (offsetPointSourceId > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.PointSourceId); - } - - if (offsetColor > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - ushort r = Convert.ToUInt16(point.Color.r * 256); - ushort g = Convert.ToUInt16(point.Color.g * 256); - ushort b = Convert.ToUInt16(point.Color.b * 256); - - binaryWriter.Write(r); - binaryWriter.Write(g); - binaryWriter.Write(b); - } - } - - pointsCount++; - } - } - } - } + //foreach (var node in _potreeData.Hierarchy.Nodes) + //{ + // if (nodeSelector(node)) + // { + // octantCount++; + + // var point = new PotreePoint(); + + // for (int i = 0; i < node.NumPoints; i++) + // { + // if (offsetPosition > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + + // double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; + // double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; + // double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; + + // double3 position = new(x, y, z); + // position = Potree2Consts.YZflip * position; + + // point.Position = position; + // } + + // if (offsetIntensity > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + // point.Intensity = binaryReader.ReadInt16(); + // } + + // if (offsetReturnNumber > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + // point.ReturnNumber = binaryReader.ReadByte(); + // } + + // if (offsetNumberOfReturns > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + // point.NumberOfReturns = binaryReader.ReadByte(); + // } + + // if (offsetClassification > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + // point.Classification = binaryReader.ReadByte(); + // } + + // if (offsetScanAngleRank > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + // point.ScanAngleRank = binaryReader.ReadByte(); + // } + + // if (offsetUserData > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + // point.UserData = binaryReader.ReadByte(); + // } + + // if (offsetPointSourceId > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + // point.PointSourceId = binaryReader.ReadByte(); + // } + + // if (offsetColor > -1) + // { + // binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + + // ushort r = binaryReader.ReadUInt16(); + // ushort g = binaryReader.ReadUInt16(); + // ushort b = binaryReader.ReadUInt16(); + + // float3 color = float3.Zero; + + // color.r = ((byte)(r > 255 ? r / 256 : r)); + // color.g = ((byte)(g > 255 ? g / 256 : g)); + // color.b = ((byte)(b > 255 ? b / 256 : b)); + + // point.Color = color; + // } + + // if (pointSelector(point)) + // { + // action(point); + + // if (!dryrun) + // { + // if (offsetPosition > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + + // var position = Potree2Consts.YZflip * point.Position; + + // int x = Convert.ToInt32(position.x / _potreeData.Metadata.Scale.x); + // int y = Convert.ToInt32(position.y / _potreeData.Metadata.Scale.y); + // int z = Convert.ToInt32(position.z / _potreeData.Metadata.Scale.z); + + // binaryWriter.Write(x); + // binaryWriter.Write(y); + // binaryWriter.Write(z); + // } + + // if (offsetIntensity > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Intensity); + // } + + // if (offsetReturnNumber > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ReturnNumber); + // } + + // if (offsetNumberOfReturns > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.NumberOfReturns); + // } + + // if (offsetClassification > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Classification); + // } + + // if (offsetScanAngleRank > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ScanAngleRank); + // } + + // if (offsetUserData > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.UserData); + // } + + // if (offsetPointSourceId > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.PointSourceId); + // } + + // if (offsetColor > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + + // ushort r = Convert.ToUInt16(point.Color.r * 256); + // ushort g = Convert.ToUInt16(point.Color.g * 256); + // ushort b = Convert.ToUInt16(point.Color.b * 256); + + // binaryWriter.Write(r); + // binaryWriter.Write(g); + // binaryWriter.Write(b); + // } + // } + + // pointsCount++; + // } + // } + // } + //} binaryWriter.Close(); binaryReader.Dispose(); @@ -256,96 +256,96 @@ public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } return (octantCount, pointsCount); } - public void WriteRawPoints(OctantId oid, TPotreePoint[] points) where TPotreePoint : PotreePoint - { - var node = FindNode(ref _potreeData.Hierarchy, oid); - - if (points.Length != node.NumPoints) - { - //TODO: (throw) correct error - throw new Exception(); - } - - using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - { - BinaryWriter binaryWriter = new BinaryWriter(writeStream); - - for (int i = 0; i < points.Length; i++) - { - var point = points[i]; - - if (offsetPosition > -1) - { - // TODO: Fix position conversion - //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - //var position = Potree2Consts.YZflip * point.Position; - - //binaryWriter.Write(position.x); - //binaryWriter.Write(position.y); - //binaryWriter.Write(position.z); - } - - if (offsetIntensity > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Intensity); - } - - if (offsetReturnNumber > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ReturnNumber); - } - - if (offsetNumberOfReturns > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.NumberOfReturns); - } - - if (offsetClassification > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Classification); - } - - if (offsetScanAngleRank > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ScanAngleRank); - } - - if (offsetUserData > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.UserData); - } - - if (offsetPointSourceId > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.PointSourceId); - } - - if (offsetColor > -1) - { - // TODO: Fix color conversion - //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); - //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); - //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); - - //binaryWriter.Write(r); - //binaryWriter.Write(g); - //binaryWriter.Write(b); - } - } - - binaryWriter.Close(); - binaryWriter.Dispose(); - } - } + //public void WriteRawPoints(OctantId oid, TPotreePoint[] points) where TPotreePoint : PotreePoint + //{ + // var node = FindNode(ref _potreeData.Hierarchy, oid); + + // if (points.Length != node.NumPoints) + // { + // //TODO: (throw) correct error + // throw new Exception(); + // } + + // using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + // { + // BinaryWriter binaryWriter = new BinaryWriter(writeStream); + + // for (int i = 0; i < points.Length; i++) + // { + // var point = points[i]; + + // if (offsetPosition > -1) + // { + // // TODO: Fix position conversion + // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + + // //var position = Potree2Consts.YZflip * point.Position; + + // //binaryWriter.Write(position.x); + // //binaryWriter.Write(position.y); + // //binaryWriter.Write(position.z); + // } + + // if (offsetIntensity > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Intensity); + // } + + // if (offsetReturnNumber > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ReturnNumber); + // } + + // if (offsetNumberOfReturns > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.NumberOfReturns); + // } + + // if (offsetClassification > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Classification); + // } + + // if (offsetScanAngleRank > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ScanAngleRank); + // } + + // if (offsetUserData > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.UserData); + // } + + // if (offsetPointSourceId > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.PointSourceId); + // } + + // if (offsetColor > -1) + // { + // // TODO: Fix color conversion + // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + + // //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); + // //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); + // //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); + + // //binaryWriter.Write(r); + // //binaryWriter.Write(g); + // //binaryWriter.Write(b); + // } + // } + + // binaryWriter.Close(); + // binaryWriter.Dispose(); + // } + //} } } \ No newline at end of file From 854cd4ec4cac694821fe837dbdf4341bf4eb2326 Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Tue, 14 Feb 2023 15:16:10 +0000 Subject: [PATCH 042/294] Linting --- src/PointCloud/Core/PointCloudDataHandler.cs | 2 +- src/PointCloud/Potree/Potree2LAS.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index b1fef2ba5..1948be1d8 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -112,7 +112,7 @@ public PointCloudDataHandler(PointAccessor pointAccessor, CreateGpuData< _pointCache.HandleEvictedItem += (object key, object? value, EvictionReason reason, object? state) => { - if(value != null && value is MemoryOwner mo) + if (value != null && value is MemoryOwner mo) { mo.Dispose(); } diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 66c17c9a0..9ba18fe9e 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -153,14 +153,14 @@ public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory Date: Wed, 15 Feb 2023 11:46:58 +0100 Subject: [PATCH 043/294] Removed PointAccessor --- .../Common/Accessors/IPointAccessor.cs | 53 - .../Common/Accessors/PointColorType.cs | 63 - .../Common/Accessors/PointCurvatureType.cs | 53 - .../Common/Accessors/PointGpsTimeType.cs | 53 - .../Common/Accessors/PointHitCountType.cs | 53 - .../Common/Accessors/PointIntensityType.cs | 63 - .../Common/Accessors/PointLabelType.cs | 53 - .../Common/Accessors/PointNormalType.cs | 24 - .../Common/Accessors/PointPositionType.cs | 24 - src/PointCloud/Common/IPointReader.cs | 5 - src/PointCloud/Common/IPointWriter.cs | 5 - .../Common/{Accessors => }/PointTypes.cs | 5 +- .../Core/Accessors/PointAccessor.cs | 2170 ----------------- .../Core/Accessors/PosD3Accessor.cs | 40 - .../Core/Accessors/PosD3ColF3Accessor.cs | 56 - .../Core/Accessors/PosD3ColF3InUsAccessor.cs | 73 - .../Accessors/PosD3ColF3InUsLblBAccessor.cs | 93 - .../Core/Accessors/PosD3ColF3LblBAccessor.cs | 74 - .../Core/Accessors/PosD3InUsAccessor.cs | 55 - .../Core/Accessors/PosD3LblBAccessor.cs | 55 - .../Core/Accessors/PosD3NorF3ColF3Accessor.cs | 74 - .../Accessors/PosD3NorF3ColF3InUsAccessor.cs | 91 - .../Core/Accessors/PosD3NorF3InUsAccessor.cs | 73 - src/PointCloud/Core/MeshMaker.cs | 2 - src/PointCloud/Core/PointCloudDataHandler.cs | 6 +- src/PointCloud/Potree/Potree2LAS.cs | 35 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 10 +- src/PointCloud/Potree/V2/Potree2RwBase.cs | 7 - 28 files changed, 18 insertions(+), 3350 deletions(-) delete mode 100644 src/PointCloud/Common/Accessors/IPointAccessor.cs delete mode 100644 src/PointCloud/Common/Accessors/PointColorType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointCurvatureType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointGpsTimeType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointHitCountType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointIntensityType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointLabelType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointNormalType.cs delete mode 100644 src/PointCloud/Common/Accessors/PointPositionType.cs rename src/PointCloud/Common/{Accessors => }/PointTypes.cs (98%) delete mode 100644 src/PointCloud/Core/Accessors/PointAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3Accessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3ColF3Accessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3ColF3InUsAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3ColF3InUsLblBAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3ColF3LblBAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3InUsAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3LblBAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3NorF3ColF3Accessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3NorF3ColF3InUsAccessor.cs delete mode 100644 src/PointCloud/Core/Accessors/PosD3NorF3InUsAccessor.cs diff --git a/src/PointCloud/Common/Accessors/IPointAccessor.cs b/src/PointCloud/Common/Accessors/IPointAccessor.cs deleted file mode 100644 index 8a6ca1479..000000000 --- a/src/PointCloud/Common/Accessors/IPointAccessor.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Every point cloud needs a point accessor. Provides access to the point parameters like position or color. - /// - public interface IPointAccessor - { - /// - /// Returns the point type this accessor can use. - /// - PointType PointType { get; } - - /// - /// Data type of the position values. - /// - PointPositionType PositionType { get; } - - /// - /// Data type of the intensity values. - /// - PointIntensityType IntensityType { get; } - - /// - /// Data type of the normal vectors. - /// - PointNormalType NormalType { get; } - - /// - /// Data type of the color values. - /// - PointColorType ColorType { get; } - - /// - /// Data type of the label values. - /// - PointLabelType LabelType { get; } - - /// - /// Data type of the curvature values. - /// - PointCurvatureType CurvatureType { get; } - - /// - /// Data type of the hit count values. - /// - PointHitCountType HitCountType { get; } - - /// - /// Data type of the gps time values. - /// - PointGpsTimeType GpsTimeType { get; } - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointColorType.cs b/src/PointCloud/Common/Accessors/PointColorType.cs deleted file mode 100644 index 8555e1d82..000000000 --- a/src/PointCloud/Common/Accessors/PointColorType.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Fusee.Math.Core; - -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's per point color data. - /// - public enum PointColorType - { - /// - /// A point cloud point without a color value. - /// - None, - /// - /// A point cloud point has a color value of type . - /// - SByte, - /// - /// A point cloud point has a color value of type . - /// - Short, - /// - /// A point cloud point has a color value of type . - /// - Int, - /// - /// A point cloud point has a color value of type . - /// - Long, - /// - /// A point cloud point has a color value of type . - /// - Byte, - /// - /// A point cloud point has a color value of type . - /// - Ushort, - /// - /// A point cloud point has a color value of type . - /// - Uint, - /// - /// A point cloud point has a color value of type . - /// - Ulong, - /// - /// A point cloud point has a color value of type . - /// - Float, - /// - /// A point cloud point has a color value of type . - /// - Double, - /// - /// A point cloud point has a color value of type . - /// - Float3, - /// - /// A point cloud point has a color value of type . - /// - Double3 - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointCurvatureType.cs b/src/PointCloud/Common/Accessors/PointCurvatureType.cs deleted file mode 100644 index 340ccc9f8..000000000 --- a/src/PointCloud/Common/Accessors/PointCurvatureType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's curvature data. - /// - public enum PointCurvatureType - { - /// - /// A point cloud without a curvature. - /// - None, - /// - /// A point cloud point has a curvature value of type . - /// - SByte, - /// - /// A point cloud point has a curvature value of type . - /// - Short, - /// - /// A point cloud point has a curvature value of type . - /// - Int, - /// - /// A point cloud point has a curvature value of type . - /// - Long, - /// - /// A point cloud point has a curvature value of type . - /// - Byte, - /// - /// A point cloud point has a curvature value of type . - /// - UShort, - /// - /// A point cloud point has a curvature value of type . - /// - Uint, - /// - /// A point cloud point has a curvature value of type . - /// - ULong, - /// - /// A point cloud point has a curvature value of type . - /// - Float, - /// - /// A point cloud point has a curvature value of type . - /// - Double, - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointGpsTimeType.cs b/src/PointCloud/Common/Accessors/PointGpsTimeType.cs deleted file mode 100644 index 3e1428a40..000000000 --- a/src/PointCloud/Common/Accessors/PointGpsTimeType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's gps time data. - /// - public enum PointGpsTimeType - { - /// - /// A point cloud point without gps time. - /// - None, - /// - /// A point cloud point has a gps time value of type . - /// - SByte, - /// - /// A point cloud point has a gps time value of type . - /// - Short, - /// - /// A point cloud point has a gps time value of type . - /// - Int, - /// - /// A point cloud point has a gps time value of type . - /// - Long, - /// - /// A point cloud point has a gps time value of type . - /// - Byte, - /// - /// A point cloud point has a gps time value of type . - /// - UShort, - /// - /// A point cloud point has a gps time value of type . - /// - Uint, - /// - /// A point cloud point has a gps time value of type . - /// - ULong, - /// - /// A point cloud point has a gps time value of type . - /// - Float, - /// - /// A point cloud point has a gps time value of type . - /// - Double - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointHitCountType.cs b/src/PointCloud/Common/Accessors/PointHitCountType.cs deleted file mode 100644 index c3013c8fd..000000000 --- a/src/PointCloud/Common/Accessors/PointHitCountType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's hit count data. - /// - public enum PointHitCountType - { - /// - /// A point cloud point has a label without a hit count. - /// - None, - /// - /// A point cloud point has a hit count value of type . - /// - SByte, - /// - /// A point cloud point has a hit count value of type . - /// - Short, - /// - /// A point cloud point has a hit count value of type . - /// - Int, - /// - /// A point cloud point has a hit count value of type . - /// - Long, - /// - /// A point cloud point has a hit count value of type . - /// - Byte, - /// - /// A point cloud point has a hit count value of type . - /// - UShort, - /// - /// A point cloud point has a hit count value of type . - /// - Uint, - /// - /// A point cloud point has a hit count value of type . - /// - ULong, - /// - /// A point cloud point has a hit count value of type . - /// - Float, - /// - /// A point cloud point has a hit count value of type . - /// - Double, - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointIntensityType.cs b/src/PointCloud/Common/Accessors/PointIntensityType.cs deleted file mode 100644 index 9e065119e..000000000 --- a/src/PointCloud/Common/Accessors/PointIntensityType.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's intensity data. - /// - public enum PointIntensityType - { - /// - /// A point cloud point without an intensity value. - /// - None, - - /// - /// A point cloud point has a intensity value of type . - /// - SByte, - - /// - ///A point cloud point has a intensity value of type . - /// - Short, - - /// - /// A point cloud point has a intensity value of type . - /// - Int, - - /// - /// A point cloud point has a intensity value of type . - /// - Long, - - /// - /// A point cloud point has a intensity value of type . - /// - Byte, - - /// - /// A point cloud point has a intensity value of type . - /// - UShort, - - /// - /// A point cloud point has a intensity value of type . - /// - UInt, - - /// - /// Returns a bool that tells if a point cloud point has a intensity value of type . - /// - ULong, - - /// - /// Returns a bool that tells if a point cloud point has a intensity value of type . - /// - Float, - - /// - /// Returns a bool that tells if a point cloud point has a intensity value of type . - /// - Double - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointLabelType.cs b/src/PointCloud/Common/Accessors/PointLabelType.cs deleted file mode 100644 index 4cf9d8de8..000000000 --- a/src/PointCloud/Common/Accessors/PointLabelType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's label data. - /// - public enum PointLabelType - { - /// - /// A point cloud without a label. - /// - None, - /// - /// A point cloud point has a label with a value of type . - /// - SByte, - /// - /// A point cloud point has a label with a value of type . - /// - Short, - /// - /// A point cloud point has a label with a value of type . - /// - Int, - /// - /// A point cloud point has a label with a value of type . - /// - Long, - /// - /// A point cloud point has a label with a value of type . - /// - Byte, - /// - /// A point cloud point has a label with a value of type . - /// - UShort, - /// - /// A point cloud point has a label with a value of type . - /// - UInt, - /// - /// A point cloud point has a label with a value of type . - /// - ULong, - /// - /// A point cloud point has a label with a value of type . - /// - Float, - /// - /// A point cloud point has a label with a value of type . - /// - Double, - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointNormalType.cs b/src/PointCloud/Common/Accessors/PointNormalType.cs deleted file mode 100644 index 5489b8401..000000000 --- a/src/PointCloud/Common/Accessors/PointNormalType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Fusee.Math.Core; - -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's normal vectors. - /// - public enum PointNormalType - { - /// - /// A point cloud point without an normal. - /// - None, - - /// - /// A point cloud point has a position value of type . - /// - Float3, - /// - /// A point cloud point has a position value of type . - /// - Double3 - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/Accessors/PointPositionType.cs b/src/PointCloud/Common/Accessors/PointPositionType.cs deleted file mode 100644 index 2089072ab..000000000 --- a/src/PointCloud/Common/Accessors/PointPositionType.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Fusee.Math.Core; - -namespace Fusee.PointCloud.Common.Accessors -{ - /// - /// Declares valid data types for a point cloud's position data. - /// - public enum PointPositionType - { - /// - /// The position of this point is undefined - renders the point unusable. - /// - Undefined, - - /// - /// A point cloud point has a position value of type . - /// - Float3, - /// - /// A point cloud point has a position value of type . - /// - Double3 - } -} \ No newline at end of file diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index ed91a9505..98396e446 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -10,11 +10,6 @@ namespace Fusee.PointCloud.Common /// public interface IPointReader { - /// - /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . - /// - public IPointAccessor PointAccessor { get; } - /// /// Returns a renderable point cloud component. /// diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index a6b4e220e..d97e51d9f 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -102,11 +102,6 @@ public interface IPointWriterMetadata /// public interface IPointWriter { - /// - /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . - /// - public IPointAccessor PointAccessor { get; } - /// /// Returns the point type. /// diff --git a/src/PointCloud/Common/Accessors/PointTypes.cs b/src/PointCloud/Common/PointTypes.cs similarity index 98% rename from src/PointCloud/Common/Accessors/PointTypes.cs rename to src/PointCloud/Common/PointTypes.cs index 00d2f9453..f3e8076de 100644 --- a/src/PointCloud/Common/Accessors/PointTypes.cs +++ b/src/PointCloud/Common/PointTypes.cs @@ -1,8 +1,7 @@ - -using Fusee.Math.Core; +using Fusee.Math.Core; using System.Runtime.InteropServices; -namespace Fusee.PointCloud.Common.Accessors +namespace Fusee.PointCloud.Common { /// /// Enum that contains all available point types. diff --git a/src/PointCloud/Core/Accessors/PointAccessor.cs b/src/PointCloud/Core/Accessors/PointAccessor.cs deleted file mode 100644 index 0ee9a1f4c..000000000 --- a/src/PointCloud/Core/Accessors/PointAccessor.cs +++ /dev/null @@ -1,2170 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// Every point cloud needs a point accessor. Provides access to the point parameters like position or color. - /// - public abstract class PointAccessor : IPointAccessor - { - /// - /// Returns the type of the point as list of the HasXY methods. - /// - /// - public List GetSetPropertyNames() - { - // Point Type (enum) - return GetType().GetProperties().Where(p => p.PropertyType == typeof(bool) && (bool)p.GetValue(this, null)).Select(p => p.Name).ToList(); - } - - /// - /// Returns the point type of this accessor. If it is "undefined" it will translate the type from its type properties. - /// - public PointType PointType - { - get - { - if (_pointType == PointType.Undefined) - GetPointType(); - - return _pointType; - } - - } - private PointType _pointType = PointType.Undefined; - - private void GetPointType() - { - // Pos64 - _pointType = PositionType switch - { - PointPositionType.Double3 when IntensityType == PointIntensityType.None && NormalType == PointNormalType.None && ColorType == PointColorType.None && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3, - PointPositionType.Double3 when IntensityType == PointIntensityType.UShort && NormalType == PointNormalType.None && ColorType == PointColorType.Float && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3ColF3InUs, - PointPositionType.Double3 when IntensityType == PointIntensityType.UShort && NormalType == PointNormalType.None && ColorType == PointColorType.None && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3InUs, - PointPositionType.Double3 when IntensityType == PointIntensityType.None && NormalType == PointNormalType.None && ColorType == PointColorType.Float && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3ColF3, - PointPositionType.Double3 when IntensityType == PointIntensityType.None && NormalType == PointNormalType.None && ColorType == PointColorType.None && LabelType == PointLabelType.Byte && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3LblB, - PointPositionType.Double3 when IntensityType == PointIntensityType.UShort && NormalType == PointNormalType.Float3 && ColorType == PointColorType.Float && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3NorF3ColF3InUs, - PointPositionType.Double3 when IntensityType == PointIntensityType.UShort && NormalType == PointNormalType.Float3 && ColorType == PointColorType.None && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3NorF3InUs, - PointPositionType.Double3 when IntensityType == PointIntensityType.None && NormalType == PointNormalType.Float3 && ColorType == PointColorType.Float && LabelType == PointLabelType.None && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3NorF3ColF3, - PointPositionType.Double3 when IntensityType == PointIntensityType.None && NormalType == PointNormalType.None && ColorType == PointColorType.Float && LabelType == PointLabelType.Byte && CurvatureType == PointCurvatureType.None && HitCountType == PointHitCountType.None && GpsTimeType == PointGpsTimeType.None => PointType.PosD3ColF3LblB, - _ => throw new Exception("Undefined Point Type!"), - }; - } - - /// - /// Data type of the position values. - /// - public PointPositionType PositionType { get; set; } = PointPositionType.Undefined; - /// - /// Data type of the intensity values. - /// - public PointIntensityType IntensityType { get; set; } = PointIntensityType.None; - /// - /// Data type of the normal vectors. - /// - public PointNormalType NormalType { get; set; } = PointNormalType.None; - /// - /// Data type of the color values. - /// - public PointColorType ColorType { get; set; } = PointColorType.None; - /// - /// Data type of the label values. - /// - public PointLabelType LabelType { get; set; } = PointLabelType.None; - /// - /// Data type of the curvature values. - /// - public PointCurvatureType CurvatureType { get; set; } = PointCurvatureType.None; - /// - /// Data type of the hit count values. - /// - public PointHitCountType HitCountType { get; set; } = PointHitCountType.None; - /// - /// Data type of the gps time values. - /// - public PointGpsTimeType GpsTimeType { get; set; } = PointGpsTimeType.None; - - /// - /// Returns the generic raw point. - /// - /// The point cloud point. - public byte[] GetRawPoint(ref TPoint point) - { - if (point == null) - throw new NullReferenceException("Given point is null!"); - - var position = GetRawPosition(ref point); - var intensity = GetRawIntensity(ref point); - var normals = GetRawNormals(ref point); - var rgb = GetRawColor(ref point); - var label = GetRawLabel(ref point); - var curvature = GetRawCurvature(ref point); - var hitCount = GetRawHitCount(ref point); - var GPSTime = GetRawGPSTime(ref point); - - return position.Concat(intensity).Concat(normals).Concat(rgb).Concat(label).Concat(curvature).Concat(hitCount).Concat(GPSTime).ToArray(); - } - - /// - /// Sets the values of a point cloud point. - /// - /// The generic point. - /// The values as byte array. - public void SetRawPoint(ref TPoint pointIn, byte[] byteIn) - { - if (pointIn == null || byteIn == null || byteIn.Length < 8) - throw new NullReferenceException("Invalid data given"); - - // Call all methods to recreate the point - SetRawPosition(ref pointIn, byteIn); - SetRawIntensity(ref pointIn, byteIn); - SetRawNormals(ref pointIn, byteIn); - SetRawColor(ref pointIn, byteIn); - SetRawLabel(ref pointIn, byteIn); - SetRawCurvature(ref pointIn, byteIn); - SetRawHitCount(ref pointIn, byteIn); - SetRawGPSTime(ref pointIn, byteIn); - } - - #region PointT_Methods - - #region Get/Set Position - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float3 GetPositionFloat3_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetPositionFloat32"); - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public virtual void SetPositionFloat3_32(ref TPoint point, float3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetPositionFloat32"); - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double3 GetPositionFloat3_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetPositionFloat64"); - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public virtual void SetPositionFloat3_64(ref TPoint point, double3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetPositionFloat64"); - } - #endregion - - #region Get/Set Intensity - - #region Getter - - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetIntensityInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityInt_8"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetIntensityInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityInt_16"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetIntensityInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityInt_32"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetIntensityInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityInt_64"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetIntensityUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityUInt_8"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetIntensityUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityUInt_16"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetIntensityUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityUInt_32"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetIntensityUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityUInt_64"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetIntensityFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityFloat32"); - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetIntensityFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetIntensityFloat64"); - } - #endregion - - #region Setter - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityInt_8"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityInt_16"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityInt_32"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityInt_64"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityUInt_8"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityUInt_16"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityUInt_32"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityUInt_64"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityFloat32"); - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public virtual void SetIntensityFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetIntensityFloat64"); - } - #endregion - - #endregion - - #region Get/Set Normal - - #region Getter - - /// - /// Returns the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float3 GetNormalFloat3_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetNormalFloat3_32"); - } - - /// - /// Returns the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double3 GetNormalFloat3_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetNormalFloat3_64"); - } - #endregion - - #region Setter - /// - /// Sets the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - /// The new normal vector. - public virtual void SetNormalFloat3_32(ref TPoint point, float3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetNormalFloat3_32"); - } - /// - /// Sets the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - /// The new normal vector. - public virtual void SetNormalFloat3_64(ref TPoint point, double3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetNormalFloat3_64"); - } - #endregion - - #endregion - - #region Get/Set Color - - #region Getter - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetColorInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorInt_8"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetColorInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorInt_16"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetColorInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorInt_32"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetColorInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorInt_64"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetColorUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorUInt_8"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetColorUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorUInt_16"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetColorUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorUInt_32"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetColorUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorUInt_64"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetColorFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorFloat32"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetColorFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorFloat64"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float3 GetColorFloat3_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorFloat3_32"); - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double3 GetColorFloat3_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetColorFloat3_64"); - } - #endregion - - #region Setter - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorInt_8"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorInt_16"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorInt_32"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorInt_64"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorUInt_8"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorUInt_16"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorUInt_32"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorUInt_64"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorFloat32"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorFloat64"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorFloat3_32(ref TPoint point, float3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorFloat3_32"); - } - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public virtual void SetColorFloat3_64(ref TPoint point, double3 val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetColorFloat3_64"); - } - #endregion - - #endregion - - #region Get/Set Label - - #region Getter - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetLabelInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelInt_8"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetLabelInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelInt_16"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetLabelInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelInt_32"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetLabelInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelInt_64"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetLabelUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelUInt_8"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetLabelUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelUInt_16"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetLabelUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelUInt_32"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetLabelUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelUInt_64"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetLabelFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelFloat32"); - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetLabelFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetLabelFloat64"); - } - #endregion - - #region Setter - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelInt_8"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelInt_16"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelInt_32"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelInt_64"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelUInt_8"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelUInt_16"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelUInt_32"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelUInt_64"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelFloat32"); - } - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public virtual void SetLabelFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetLabelFloat64"); - } - #endregion - - #endregion - - #region Get/Set Curvature - - #region Getter - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetCurvatureInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureInt_8"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetCurvatureInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureInt_16"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetCurvatureInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureInt_32"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetCurvatureInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureInt_64"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetCurvatureUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureUInt_8"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetCurvatureUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureUInt_16"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetCurvatureUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureUInt_32"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetCurvatureUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureUInt_64"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetCurvatureFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureFloat32"); - } - /// - /// Returns the curvature of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetCurvatureFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetCurvatureFloat64"); - } - #endregion - - #region Setter - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureInt_8"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureInt_16"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureInt_32"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureInt_64"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureUInt_8"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureUInt_16"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureUInt_32"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureUInt_64"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureFloat32"); - } - /// - /// Sets the curvature of a point cloud point if is true. - /// - /// The point cloud point. - /// The new curvature. - public virtual void SetCurvatureFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetCurvatureFloat64"); - } - #endregion - - #endregion - - #region Get/Set Hit Count - - #region Getter - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetHitCountInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountInt_8"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetHitCountInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountInt_16"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetHitCountInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountInt_32"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetHitCountInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountInt_64"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetHitCountUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountUInt_8"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetHitCountUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountUInt_16"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetHitCountUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountUInt_32"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetHitCountUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountUInt_64"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetHitCountFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountFloat32"); - } - /// - /// Returns the hit count of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetHitCountFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetHitCountFloat64"); - } - #endregion - - #region Setter - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountInt_8"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountInt_16"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountInt_32"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountInt_64"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountUInt_8"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountUInt_16"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountUInt_32"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountUInt_64"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountFloat32"); - } - /// - /// Sets the hit count of a point cloud point if is true. - /// - /// The point cloud point. - /// The new hit count. - public virtual void SetHitCountFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetHitCountFloat64"); - } - #endregion - - #endregion - - #region Get/Set GPS Time - - #region Getter - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref sbyte GetGPSTimeInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeInt_8"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref short GetGPSTimeInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeInt_16"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref int GetGPSTimeInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeInt_32"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref long GetGPSTimeInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeInt_64"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref byte GetGPSTimeUInt_8(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeUInt_8"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ushort GetGPSTimeUInt_16(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeUInt_16"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref uint GetGPSTimeUInt_32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeUInt_32"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref ulong GetGPSTimeUInt_64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeUInt_64"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref float GetGPSTimeFloat32(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeFloat32"); - } - /// - /// Returns the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - public virtual ref double GetGPSTimeFloat64(ref TPoint point) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support GetGPSTimeFloat64"); - } - #endregion - - #region Setter - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeInt_8(ref TPoint point, sbyte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeInt_8"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeInt_16(ref TPoint point, short val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeInt_16"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeInt_32(ref TPoint point, int val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeInt_32"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeInt_64(ref TPoint point, long val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeInt_64"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeUInt_8(ref TPoint point, byte val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeUInt_8"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeUInt_16(ref TPoint point, ushort val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeUInt_16"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeUInt_32(ref TPoint point, uint val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeUInt_32"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeUInt_64(ref TPoint point, ulong val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeUInt_64"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeFloat32(ref TPoint point, float val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeFloat32"); - } - /// - /// Sets the GPS time of a point cloud point if is true. - /// - /// The point cloud point. - /// The new GPS time. - public virtual void SetGPSTimeFloat64(ref TPoint point, double val) - { - throw new NotSupportedException($"Point {typeof(TPoint).Name} does not support SetGPSTimeFloat64"); - } - #endregion - - #endregion - - #endregion - - #region RawDataEncode - private byte[] GetRawPosition(ref TPoint point) - { - if (PositionType == PointPositionType.Float3) - { - - var x = BitConverter.GetBytes(GetPositionFloat3_32(ref point).x); - var y = BitConverter.GetBytes(GetPositionFloat3_32(ref point).y); - var z = BitConverter.GetBytes(GetPositionFloat3_32(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - else if (PositionType == PointPositionType.Double3) - { - - var x = BitConverter.GetBytes(GetPositionFloat3_64(ref point).x); - var y = BitConverter.GetBytes(GetPositionFloat3_64(ref point).y); - var z = BitConverter.GetBytes(GetPositionFloat3_64(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - - byte[] vs = Array.Empty(); - return vs; - - } - - private byte[] GetRawIntensity(ref TPoint point) - { - switch (IntensityType) - { - case PointIntensityType.SByte: - return BitConverter.GetBytes(((short)GetIntensityInt_8(ref point))); - case PointIntensityType.Short: - return BitConverter.GetBytes(GetIntensityInt_16(ref point)); - case PointIntensityType.Int: - return BitConverter.GetBytes(GetIntensityInt_32(ref point)); - case PointIntensityType.Long: - return BitConverter.GetBytes(GetIntensityInt_64(ref point)); - case PointIntensityType.Byte: - return BitConverter.GetBytes(((short)GetIntensityUInt_8(ref point))); - case PointIntensityType.UShort: - return BitConverter.GetBytes(GetIntensityUInt_16(ref point)); - case PointIntensityType.UInt: - return BitConverter.GetBytes(GetIntensityUInt_32(ref point)); - case PointIntensityType.ULong: - return BitConverter.GetBytes(GetIntensityUInt_64(ref point)); - case PointIntensityType.Float: - return BitConverter.GetBytes(GetIntensityFloat32(ref point)); - case PointIntensityType.Double: - return BitConverter.GetBytes(GetIntensityFloat64(ref point)); - } - - return Array.Empty(); - } - - private byte[] GetRawNormals(ref TPoint point) - { - - if (NormalType == PointNormalType.Float3) - { - var x = BitConverter.GetBytes(GetNormalFloat3_32(ref point).x); - var y = BitConverter.GetBytes(GetNormalFloat3_32(ref point).y); - var z = BitConverter.GetBytes(GetNormalFloat3_32(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - else if (NormalType == PointNormalType.Double3) - { - var x = BitConverter.GetBytes(GetNormalFloat3_64(ref point).x); - var y = BitConverter.GetBytes(GetNormalFloat3_64(ref point).y); - var z = BitConverter.GetBytes(GetNormalFloat3_64(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - return Array.Empty(); - - } - - private byte[] GetRawColor(ref TPoint point) - { - switch (ColorType) - { - case PointColorType.SByte: - return BitConverter.GetBytes(((short)GetColorInt_8(ref point))); - case PointColorType.Short: - return BitConverter.GetBytes(GetColorInt_16(ref point)); - case PointColorType.Int: - return BitConverter.GetBytes(GetColorInt_32(ref point)); - case PointColorType.Long: - return BitConverter.GetBytes(GetColorInt_64(ref point)); - case PointColorType.Byte: - return BitConverter.GetBytes(((short)GetColorUInt_8(ref point))); - case PointColorType.Ushort: - return BitConverter.GetBytes(GetColorUInt_16(ref point)); - case PointColorType.Uint: - return BitConverter.GetBytes(GetColorUInt_32(ref point)); - case PointColorType.Ulong: - return BitConverter.GetBytes(GetColorUInt_64(ref point)); - case PointColorType.Float: - return BitConverter.GetBytes(GetColorFloat32(ref point)); - case PointColorType.Double: - return BitConverter.GetBytes(GetColorFloat64(ref point)); - } - - if (ColorType == PointColorType.Float3) - { - - var x = BitConverter.GetBytes(GetColorFloat3_32(ref point).x); - var y = BitConverter.GetBytes(GetColorFloat3_32(ref point).y); - var z = BitConverter.GetBytes(GetColorFloat3_32(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - if (ColorType == PointColorType.Double3) - { - - var x = BitConverter.GetBytes(GetColorFloat3_64(ref point).x); - var y = BitConverter.GetBytes(GetColorFloat3_64(ref point).y); - var z = BitConverter.GetBytes(GetColorFloat3_64(ref point).z); - - return x.Concat(y).Concat(z).ToArray(); - - } - return Array.Empty(); - } - - private byte[] GetRawLabel(ref TPoint point) - { - switch (LabelType) - { - case PointLabelType.SByte: - return BitConverter.GetBytes(((short)GetLabelInt_8(ref point))); - case PointLabelType.Short: - return BitConverter.GetBytes(GetLabelInt_16(ref point)); - case PointLabelType.Int: - return BitConverter.GetBytes(GetLabelInt_32(ref point)); - case PointLabelType.Long: - return BitConverter.GetBytes(GetLabelInt_64(ref point)); - case PointLabelType.Byte: - return BitConverter.GetBytes(((short)GetLabelUInt_8(ref point))); - case PointLabelType.UShort: - return BitConverter.GetBytes(GetLabelUInt_16(ref point)); - case PointLabelType.UInt: - return BitConverter.GetBytes(GetLabelUInt_32(ref point)); - case PointLabelType.ULong: - return BitConverter.GetBytes(GetLabelUInt_64(ref point)); - case PointLabelType.Float: - return BitConverter.GetBytes(GetLabelFloat32(ref point)); - case PointLabelType.Double: - return BitConverter.GetBytes(GetLabelFloat64(ref point)); - } - - return Array.Empty(); - } - - private byte[] GetRawCurvature(ref TPoint point) - { - switch (CurvatureType) - { - case PointCurvatureType.SByte: - return BitConverter.GetBytes(((short)GetCurvatureInt_8(ref point))); - case PointCurvatureType.Short: - return BitConverter.GetBytes(GetCurvatureInt_16(ref point)); - case PointCurvatureType.Int: - return BitConverter.GetBytes(GetCurvatureInt_32(ref point)); - case PointCurvatureType.Long: - return BitConverter.GetBytes(GetCurvatureInt_64(ref point)); - case PointCurvatureType.Byte: - return BitConverter.GetBytes(((short)GetCurvatureUInt_8(ref point))); - case PointCurvatureType.UShort: - return BitConverter.GetBytes(GetCurvatureUInt_16(ref point)); - case PointCurvatureType.Uint: - return BitConverter.GetBytes(GetCurvatureUInt_32(ref point)); - case PointCurvatureType.ULong: - return BitConverter.GetBytes(GetCurvatureUInt_64(ref point)); - case PointCurvatureType.Float: - return BitConverter.GetBytes(GetCurvatureFloat32(ref point)); - case PointCurvatureType.Double: - return BitConverter.GetBytes(GetCurvatureFloat64(ref point)); - } - - return Array.Empty(); - } - - private byte[] GetRawHitCount(ref TPoint point) - { - switch (HitCountType) - { - case PointHitCountType.SByte: - return BitConverter.GetBytes(((short)GetHitCountInt_8(ref point))); - case PointHitCountType.Short: - return BitConverter.GetBytes(GetHitCountInt_16(ref point)); - case PointHitCountType.Int: - return BitConverter.GetBytes(GetHitCountInt_32(ref point)); - case PointHitCountType.Long: - return BitConverter.GetBytes(GetHitCountInt_64(ref point)); - case PointHitCountType.Byte: - return BitConverter.GetBytes(((short)GetHitCountUInt_8(ref point))); - case PointHitCountType.UShort: - return BitConverter.GetBytes(GetHitCountUInt_16(ref point)); - case PointHitCountType.Uint: - return BitConverter.GetBytes(GetHitCountUInt_32(ref point)); - case PointHitCountType.ULong: - return BitConverter.GetBytes(GetHitCountUInt_64(ref point)); - case PointHitCountType.Float: - return BitConverter.GetBytes(GetHitCountFloat32(ref point)); - case PointHitCountType.Double: - return BitConverter.GetBytes(GetHitCountFloat64(ref point)); - } - - return Array.Empty(); - } - - private byte[] GetRawGPSTime(ref TPoint point) - { - switch (GpsTimeType) - { - case PointGpsTimeType.SByte: - return BitConverter.GetBytes(((short)GetGPSTimeInt_8(ref point))); - case PointGpsTimeType.Short: - return BitConverter.GetBytes(GetGPSTimeInt_16(ref point)); - case PointGpsTimeType.Int: - return BitConverter.GetBytes(GetGPSTimeInt_32(ref point)); - case PointGpsTimeType.Long: - return BitConverter.GetBytes(GetGPSTimeInt_64(ref point)); - case PointGpsTimeType.Byte: - return BitConverter.GetBytes(((short)GetGPSTimeUInt_8(ref point))); - case PointGpsTimeType.UShort: - return BitConverter.GetBytes(GetGPSTimeUInt_16(ref point)); - case PointGpsTimeType.Uint: - return BitConverter.GetBytes(GetGPSTimeUInt_32(ref point)); - case PointGpsTimeType.ULong: - return BitConverter.GetBytes(GetGPSTimeUInt_64(ref point)); - case PointGpsTimeType.Float: - return BitConverter.GetBytes(GetGPSTimeFloat32(ref point)); - case PointGpsTimeType.Double: - return BitConverter.GetBytes(GetGPSTimeFloat64(ref point)); - } - - return Array.Empty(); - } - #endregion - - #region RawDataDecode - - /// - /// Needed for correct array offsets during read - /// - private struct ByteArrayOffsets - { - internal int PositionOffset; - internal int IntensityOffset; - internal int NormalsOffset; - internal int RGBOffset; - internal int LabelOffset; - internal int CurvatureOffset; - internal int HitCountOffset; - internal int GPSTimeOffset; - } - - private bool _offsetsCalculated = false; - private ByteArrayOffsets _offsets; - - private ByteArrayOffsets Offsets - { - get - { - if (_offsetsCalculated) - return _offsets; - - _offsets = new ByteArrayOffsets(); - - // position - switch (PositionType) - { - case PointPositionType.Float3: - _offsets.PositionOffset = 3 * Marshal.SizeOf(); - break; - case PointPositionType.Double3: - _offsets.PositionOffset = 3 * Marshal.SizeOf(); - break; - } - - // Intensity - switch (IntensityType) - { - case PointIntensityType.SByte: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Short: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Int: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Long: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Byte: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.UShort: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.UInt: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.ULong: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Float: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - case PointIntensityType.Double: - _offsets.IntensityOffset = Marshal.SizeOf(); - break; - } - - // Normal - switch (NormalType) - { - case PointNormalType.Float3: - _offsets.NormalsOffset = 3 * Marshal.SizeOf(); - break; - case PointNormalType.Double3: - _offsets.NormalsOffset = 3 * Marshal.SizeOf(); - break; - } - - // Color - switch (ColorType) - { - case PointColorType.SByte: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Short: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Int: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Long: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Byte: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Ushort: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Uint: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Ulong: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Float: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Double: - _offsets.RGBOffset = Marshal.SizeOf(); - break; - case PointColorType.Float3: - _offsets.RGBOffset = 3 * Marshal.SizeOf(); - break; - case PointColorType.Double3: - _offsets.RGBOffset = 3 * Marshal.SizeOf(); - break; - } - - // Label - switch (LabelType) - { - case PointLabelType.SByte: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Short: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Int: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Long: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Byte: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.UShort: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.UInt: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.ULong: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Float: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - case PointLabelType.Double: - _offsets.LabelOffset = Marshal.SizeOf(); - break; - } - - // Curvature - switch (CurvatureType) - { - case PointCurvatureType.SByte: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Short: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Int: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Long: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Byte: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.UShort: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Uint: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.ULong: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Float: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - case PointCurvatureType.Double: - _offsets.CurvatureOffset = Marshal.SizeOf(); - break; - } - - // Hit count - switch (HitCountType) - { - case PointHitCountType.SByte: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Short: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Int: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Long: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Byte: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.UShort: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Uint: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.ULong: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Float: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - case PointHitCountType.Double: - _offsets.HitCountOffset = Marshal.SizeOf(); - break; - } - - // GPSTime - switch (GpsTimeType) - { - case PointGpsTimeType.SByte: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Short: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Int: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Long: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Byte: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.UShort: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Uint: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.ULong: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Float: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - case PointGpsTimeType.Double: - _offsets.GPSTimeOffset = Marshal.SizeOf(); - break; - } - - _offsetsCalculated = true; - - return _offsets; - } - } - - private void SetRawPosition(ref TPoint pointIn, byte[] byteIn) - { - if (PositionType == PointPositionType.Float3) - { - var offset = Marshal.SizeOf(); - var x = BitConverter.ToSingle(byteIn, 0); - var y = BitConverter.ToSingle(byteIn, offset); - var z = BitConverter.ToSingle(byteIn, offset * 2); - - SetPositionFloat3_32(ref pointIn, new float3(x, y, z)); - } - else if (PositionType == PointPositionType.Double3) - { - var offset = Marshal.SizeOf(); - var x = BitConverter.ToDouble(byteIn, 0); - var y = BitConverter.ToDouble(byteIn, offset); - var z = BitConverter.ToDouble(byteIn, offset * 2); - - SetPositionFloat3_64(ref pointIn, new double3(x, y, z)); - } - } - - private void SetRawIntensity(ref TPoint pointIn, byte[] byteIn) - { - switch (IntensityType) - { - case PointIntensityType.SByte: - SetIntensityInt_8(ref pointIn, (sbyte)byteIn[Offsets.PositionOffset]); - break; - case PointIntensityType.Short: - SetIntensityInt_16(ref pointIn, byteIn[Offsets.PositionOffset]); - break; - case PointIntensityType.Int: - SetIntensityInt_32(ref pointIn, BitConverter.ToInt32(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.Long: - SetIntensityInt_64(ref pointIn, BitConverter.ToInt64(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.Byte: - SetIntensityUInt_8(ref pointIn, byteIn[Offsets.PositionOffset]); - break; - case PointIntensityType.UShort: - SetIntensityUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.UInt: - SetIntensityUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.ULong: - SetIntensityUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.Float: - SetIntensityFloat32(ref pointIn, BitConverter.ToSingle(byteIn, Offsets.PositionOffset)); - break; - case PointIntensityType.Double: - SetIntensityFloat64(ref pointIn, BitConverter.ToDouble(byteIn, Offsets.PositionOffset)); - break; - } - } - - private void SetRawNormals(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset; - - if (NormalType == PointNormalType.Float3) - { - - var dataOffset = Marshal.SizeOf(); - var x = BitConverter.ToSingle(byteIn, offset); - var y = BitConverter.ToSingle(byteIn, offset + dataOffset); - var z = BitConverter.ToSingle(byteIn, offset + dataOffset * 2); - - SetNormalFloat3_32(ref pointIn, new float3(x, y, z)); - - } - if (NormalType == PointNormalType.Double3) - { - - var dataOffset = Marshal.SizeOf(); - - var x = BitConverter.ToDouble(byteIn, offset); - var y = BitConverter.ToDouble(byteIn, offset + dataOffset); - var z = BitConverter.ToDouble(byteIn, offset + dataOffset * 2); - - SetNormalFloat3_64(ref pointIn, new double3(x, y, z)); - - } - } - - private void SetRawColor(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset + Offsets.NormalsOffset; - - switch (ColorType) - { - case PointColorType.SByte: - SetColorInt_8(ref pointIn, (sbyte)byteIn[offset]); - break; - case PointColorType.Short: - SetColorInt_16(ref pointIn, byteIn[offset]); - break; - case PointColorType.Int: - SetColorInt_32(ref pointIn, BitConverter.ToInt32(byteIn, offset)); - break; - case PointColorType.Long: - SetColorInt_64(ref pointIn, BitConverter.ToInt64(byteIn, offset)); - break; - case PointColorType.Byte: - SetColorUInt_8(ref pointIn, byteIn[offset + 1]); - break; - case PointColorType.Ushort: - SetColorUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, offset)); - break; - case PointColorType.Uint: - SetColorUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, offset)); - break; - case PointColorType.Ulong: - SetColorUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, offset)); - break; - case PointColorType.Float: - SetColorFloat32(ref pointIn, BitConverter.ToSingle(byteIn, offset)); - break; - case PointColorType.Double: - SetColorFloat64(ref pointIn, BitConverter.ToDouble(byteIn, offset)); - break; - case PointColorType.Float3: - { - - var dataOffset = Marshal.SizeOf(); - - var x = BitConverter.ToSingle(byteIn, offset); - var y = BitConverter.ToSingle(byteIn, offset + dataOffset); - var z = BitConverter.ToSingle(byteIn, offset + dataOffset * 2); - - SetColorFloat3_32(ref pointIn, new float3(x, y, z)); - - } - break; - - case PointColorType.Double3: - { - - var dataOffset = Marshal.SizeOf(); - - var x = BitConverter.ToDouble(byteIn, offset); - var y = BitConverter.ToDouble(byteIn, offset + dataOffset); - var z = BitConverter.ToDouble(byteIn, offset + dataOffset * 2); - - SetColorFloat3_64(ref pointIn, new double3(x, y, z)); - - } - break; - } - } - - private void SetRawLabel(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset + Offsets.NormalsOffset + Offsets.RGBOffset; - - switch (LabelType) - { - case PointLabelType.SByte: - SetLabelInt_8(ref pointIn, (sbyte)byteIn[offset]); - break; - case PointLabelType.Short: - SetLabelInt_16(ref pointIn, byteIn[offset]); - break; - case PointLabelType.Int: - SetLabelInt_32(ref pointIn, BitConverter.ToInt32(byteIn, offset)); - break; - case PointLabelType.Long: - SetLabelInt_64(ref pointIn, BitConverter.ToInt64(byteIn, offset)); - break; - case PointLabelType.Byte: - SetLabelUInt_8(ref pointIn, byteIn[offset]); - break; - case PointLabelType.UShort: - SetLabelUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, offset)); - break; - case PointLabelType.UInt: - SetLabelUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, offset)); - break; - case PointLabelType.ULong: - SetLabelUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, offset)); - break; - case PointLabelType.Float: - SetLabelFloat32(ref pointIn, BitConverter.ToSingle(byteIn, offset)); - break; - case PointLabelType.Double: - SetLabelFloat64(ref pointIn, BitConverter.ToDouble(byteIn, offset)); - break; - } - } - - private void SetRawCurvature(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset + Offsets.NormalsOffset + Offsets.RGBOffset + Offsets.LabelOffset; - - switch (CurvatureType) - { - case PointCurvatureType.SByte: - SetCurvatureInt_8(ref pointIn, (sbyte)byteIn[offset]); - break; - case PointCurvatureType.Short: - SetCurvatureInt_16(ref pointIn, byteIn[offset]); - break; - case PointCurvatureType.Int: - SetCurvatureInt_32(ref pointIn, BitConverter.ToInt32(byteIn, offset)); - break; - case PointCurvatureType.Long: - SetCurvatureInt_64(ref pointIn, BitConverter.ToInt64(byteIn, offset)); - break; - case PointCurvatureType.Byte: - SetCurvatureUInt_8(ref pointIn, byteIn[offset + 1]); - break; - case PointCurvatureType.UShort: - SetCurvatureUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, offset)); - break; - case PointCurvatureType.Uint: - SetCurvatureUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, offset)); - break; - case PointCurvatureType.ULong: - SetCurvatureUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, offset)); - break; - case PointCurvatureType.Float: - SetCurvatureFloat32(ref pointIn, BitConverter.ToSingle(byteIn, offset)); - break; - case PointCurvatureType.Double: - SetCurvatureFloat64(ref pointIn, BitConverter.ToDouble(byteIn, offset)); - break; - } - } - - private void SetRawHitCount(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset + Offsets.NormalsOffset + Offsets.RGBOffset + Offsets.LabelOffset; - - switch (HitCountType) - { - case PointHitCountType.SByte: - SetHitCountInt_8(ref pointIn, (sbyte)byteIn[offset]); - break; - case PointHitCountType.Short: - SetHitCountInt_16(ref pointIn, byteIn[offset]); - break; - case PointHitCountType.Int: - SetHitCountInt_32(ref pointIn, BitConverter.ToInt32(byteIn, offset)); - break; - case PointHitCountType.Long: - SetHitCountInt_64(ref pointIn, BitConverter.ToInt64(byteIn, offset)); - break; - case PointHitCountType.Byte: - SetHitCountUInt_8(ref pointIn, byteIn[offset + 1]); - break; - case PointHitCountType.UShort: - SetHitCountUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, offset)); - break; - case PointHitCountType.Uint: - SetHitCountUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, offset)); - break; - case PointHitCountType.ULong: - SetHitCountUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, offset)); - break; - case PointHitCountType.Float: - SetHitCountFloat32(ref pointIn, BitConverter.ToSingle(byteIn, offset)); - break; - case PointHitCountType.Double: - SetHitCountFloat64(ref pointIn, BitConverter.ToDouble(byteIn, offset)); - break; - } - } - - private void SetRawGPSTime(ref TPoint pointIn, byte[] byteIn) - { - var offset = Offsets.PositionOffset + Offsets.IntensityOffset + Offsets.NormalsOffset + Offsets.RGBOffset + Offsets.LabelOffset + Offsets.HitCountOffset; - - switch (GpsTimeType) - { - case PointGpsTimeType.SByte: - SetGPSTimeInt_8(ref pointIn, (sbyte)byteIn[offset]); - break; - case PointGpsTimeType.Short: - SetGPSTimeInt_16(ref pointIn, byteIn[offset]); - break; - case PointGpsTimeType.Int: - SetGPSTimeInt_32(ref pointIn, BitConverter.ToInt32(byteIn, offset)); - break; - case PointGpsTimeType.Long: - SetGPSTimeInt_64(ref pointIn, BitConverter.ToInt64(byteIn, offset)); - break; - case PointGpsTimeType.Byte: - SetGPSTimeUInt_8(ref pointIn, byteIn[offset + 1]); - break; - case PointGpsTimeType.UShort: - SetGPSTimeUInt_16(ref pointIn, BitConverter.ToUInt16(byteIn, offset)); - break; - case PointGpsTimeType.Uint: - SetGPSTimeUInt_32(ref pointIn, BitConverter.ToUInt32(byteIn, offset)); - break; - case PointGpsTimeType.ULong: - SetGPSTimeUInt_64(ref pointIn, BitConverter.ToUInt64(byteIn, offset)); - break; - case PointGpsTimeType.Float: - SetGPSTimeFloat32(ref pointIn, BitConverter.ToSingle(byteIn, offset)); - break; - case PointGpsTimeType.Double: - SetGPSTimeFloat64(ref pointIn, BitConverter.ToDouble(byteIn, offset)); - break; - } - } - - #endregion - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3Accessor.cs b/src/PointCloud/Core/Accessors/PosD3Accessor.cs deleted file mode 100644 index c7a8811dd..000000000 --- a/src/PointCloud/Core/Accessors/PosD3Accessor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - //Collection of PointAccessor classes. There has to be one for each PointType. - - /// - /// for Point Clouds which position information only. - /// - public class PosD3Accessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3Accessor() - { - PositionType = PointPositionType.Double3; - } - - /// - /// Sets the position of a point cloud point. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3 point, double3 val) - { - point.Position = val; - } - - /// - /// Returns the position of a point cloud point. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3 point) - { - return ref point.Position; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3ColF3Accessor.cs b/src/PointCloud/Core/Accessors/PosD3ColF3Accessor.cs deleted file mode 100644 index 7140a877d..000000000 --- a/src/PointCloud/Core/Accessors/PosD3ColF3Accessor.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position and color values. - /// - public class PosD3ColF3Accessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3ColF3Accessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float3; - } - - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3ColF3 point, float3 val) - { - point.Color = val; - } - - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3ColF3 point) - { - return ref point.Color; - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3ColF3 point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3ColF3 point) - { - return ref point.Position; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3ColF3InUsAccessor.cs b/src/PointCloud/Core/Accessors/PosD3ColF3InUsAccessor.cs deleted file mode 100644 index bd8a206c9..000000000 --- a/src/PointCloud/Core/Accessors/PosD3ColF3InUsAccessor.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, color and intensity values. - /// - public class PosD3ColF3InUsAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3ColF3InUsAccessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float3; - IntensityType = PointIntensityType.UShort; - } - - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3ColF3InUs point, float3 val) - { - point.Color = val; - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3ColF3InUs point) - { - return ref point.Color; - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3ColF3InUs point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3ColF3InUs point) - { - return ref point.Position; - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public override ref ushort GetIntensityUInt_16(ref PosD3ColF3InUs point) - { - return ref point.Intensity; - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public override void SetIntensityUInt_16(ref PosD3ColF3InUs point, ushort val) - { - point.Intensity = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3ColF3InUsLblBAccessor.cs b/src/PointCloud/Core/Accessors/PosD3ColF3InUsLblBAccessor.cs deleted file mode 100644 index 32ea4c162..000000000 --- a/src/PointCloud/Core/Accessors/PosD3ColF3InUsLblBAccessor.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, color and classification values. - /// - public class PosD3ColF3InUsLblBAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3ColF3InUsLblBAccessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float3; - LabelType = PointLabelType.Byte; - IntensityType = PointIntensityType.UShort; - } - - /// - /// Sets the color of a point cloud point. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3ColF3InUsLblB point, float3 val) - { - point.Color = val; - } - /// - /// Returns the normal color of a point cloud point. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3ColF3InUsLblB point) - { - return ref point.Color; - } - - /// - /// Sets the position of a point cloud point. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3ColF3InUsLblB point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3ColF3InUsLblB point) - { - return ref point.Position; - } - /// - /// Sets the label of a point cloud point. - /// - /// The point cloud point. - /// The new label. - public override void SetLabelUInt_8(ref PosD3ColF3InUsLblB point, byte val) - { - point.Label = val; - } - /// - /// Returns the label of a point cloud point. - /// - /// The point cloud point. - public override ref byte GetLabelUInt_8(ref PosD3ColF3InUsLblB point) - { - return ref point.Label; - } - - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public override ref ushort GetIntensityUInt_16(ref PosD3ColF3InUsLblB point) - { - return ref point.Intensity; - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public override void SetIntensityUInt_16(ref PosD3ColF3InUsLblB point, ushort val) - { - point.Intensity = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3ColF3LblBAccessor.cs b/src/PointCloud/Core/Accessors/PosD3ColF3LblBAccessor.cs deleted file mode 100644 index 1c386f2ff..000000000 --- a/src/PointCloud/Core/Accessors/PosD3ColF3LblBAccessor.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, color and classification values. - /// - public class PosD3ColF3LblBAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3ColF3LblBAccessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float; - LabelType = PointLabelType.Byte; - } - - /// - /// Sets the color of a point cloud point. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3ColF3LblB point, float3 val) - { - point.Color = val; - } - /// - /// Returns the normal color of a point cloud point. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3ColF3LblB point) - { - return ref point.Color; - } - - /// - /// Sets the position of a point cloud point. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3ColF3LblB point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3ColF3LblB point) - { - return ref point.Position; - } - /// - /// Sets the label of a point cloud point. - /// - /// The point cloud point. - /// The new label. - public override void SetLabelUInt_8(ref PosD3ColF3LblB point, byte val) - { - point.Label = val; - } - /// - /// Returns the label of a point cloud point. - /// - /// The point cloud point. - public override ref byte GetLabelUInt_8(ref PosD3ColF3LblB point) - { - return ref point.Label; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3InUsAccessor.cs b/src/PointCloud/Core/Accessors/PosD3InUsAccessor.cs deleted file mode 100644 index 8039ae9e7..000000000 --- a/src/PointCloud/Core/Accessors/PosD3InUsAccessor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position and intensity values. - /// - public class PosD3InUsAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3InUsAccessor() - { - PositionType = PointPositionType.Double3; - IntensityType = PointIntensityType.UShort; - } - - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3InUs point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3InUs point) - { - return ref point.Position; - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public override ref ushort GetIntensityUInt_16(ref PosD3InUs point) - { - return ref point.Intensity; - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public override void SetIntensityUInt_16(ref PosD3InUs point, ushort val) - { - point.Intensity = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3LblBAccessor.cs b/src/PointCloud/Core/Accessors/PosD3LblBAccessor.cs deleted file mode 100644 index f22a3dec2..000000000 --- a/src/PointCloud/Core/Accessors/PosD3LblBAccessor.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position and label values. - /// - public class PosD3LblBAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3LblBAccessor() - { - PositionType = PointPositionType.Double3; - LabelType = PointLabelType.Byte; - } - - /// - /// Sets the label of a point cloud point if is true. - /// - /// The point cloud point. - /// The new label. - public override void SetLabelUInt_8(ref PosD3LblB point, byte val) - { - point.Label = val; - } - /// - /// Returns the label of a point cloud point if is true. - /// - /// The point cloud point. - public override ref byte GetLabelUInt_8(ref PosD3LblB point) - { - return ref point.Label; - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3LblB point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3LblB point) - { - return ref point.Position; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3NorF3ColF3Accessor.cs b/src/PointCloud/Core/Accessors/PosD3NorF3ColF3Accessor.cs deleted file mode 100644 index 8338a5e26..000000000 --- a/src/PointCloud/Core/Accessors/PosD3NorF3ColF3Accessor.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, color and intensity values. - /// - public class PosD3NorF3ColF3Accessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3NorF3ColF3Accessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float3; - NormalType = PointNormalType.Float3; - } - - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3NorF3ColF3 point, float3 val) - { - point.Color = val; - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3NorF3ColF3 point) - { - return ref point.Color; - } - - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3NorF3ColF3 point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3NorF3ColF3 point) - { - return ref point.Position; - } - /// - /// Returns the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetNormalFloat3_32(ref PosD3NorF3ColF3 point) - { - return ref point.Normal; - } - /// - /// Sets the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - /// The new normal vector. - public override void SetNormalFloat3_32(ref PosD3NorF3ColF3 point, float3 val) - { - point.Normal = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3NorF3ColF3InUsAccessor.cs b/src/PointCloud/Core/Accessors/PosD3NorF3ColF3InUsAccessor.cs deleted file mode 100644 index f71f66993..000000000 --- a/src/PointCloud/Core/Accessors/PosD3NorF3ColF3InUsAccessor.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, normal vectors, color and intensity values. - /// - public class PosD3NorF3ColF3InUsAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3NorF3ColF3InUsAccessor() - { - PositionType = PointPositionType.Double3; - ColorType = PointColorType.Float3; - IntensityType = PointIntensityType.UShort; - NormalType = PointNormalType.Float3; - } - - /// - /// Sets the color of a point cloud point if is true. - /// - /// The point cloud point. - /// The new color. - public override void SetColorFloat3_32(ref PosD3NorF3ColF3InUs point, float3 val) - { - point.Color = val; - } - /// - /// Returns the normal color of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetColorFloat3_32(ref PosD3NorF3ColF3InUs point) - { - return ref point.Color; - } - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3NorF3ColF3InUs point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3NorF3ColF3InUs point) - { - return ref point.Position; - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public override ref ushort GetIntensityUInt_16(ref PosD3NorF3ColF3InUs point) - { - return ref point.Intensity; - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public override void SetIntensityUInt_16(ref PosD3NorF3ColF3InUs point, ushort val) - { - point.Intensity = val; - } - /// - /// Returns the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetNormalFloat3_32(ref PosD3NorF3ColF3InUs point) - { - return ref point.Normal; - } - /// - /// Sets the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - /// The new normal vector. - public override void SetNormalFloat3_32(ref PosD3NorF3ColF3InUs point, float3 val) - { - point.Normal = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/Accessors/PosD3NorF3InUsAccessor.cs b/src/PointCloud/Core/Accessors/PosD3NorF3InUsAccessor.cs deleted file mode 100644 index e294c5cb8..000000000 --- a/src/PointCloud/Core/Accessors/PosD3NorF3InUsAccessor.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; - -namespace Fusee.PointCloud.Core.Accessors -{ - /// - /// for Point Clouds which position, normal vectors and intensity values. - /// - public class PosD3NorF3InUsAccessor : PointAccessor - { - /// - /// Creates a new instance. - /// - public PosD3NorF3InUsAccessor() - { - PositionType = PointPositionType.Double3; - IntensityType = PointIntensityType.UShort; - NormalType = PointNormalType.Double3; - } - - /// - /// Sets the position of a point cloud point if is true. - /// - /// The point cloud point. - /// The new position value. - public override void SetPositionFloat3_64(ref PosD3NorF3InUs point, double3 val) - { - point.Position = val; - } - /// - /// Returns the position of a point cloud point if is true. - /// - /// The point cloud point. - public override ref double3 GetPositionFloat3_64(ref PosD3NorF3InUs point) - { - return ref point.Position; - } - /// - /// Returns the intensity of a point cloud point if is true. - /// - /// The point cloud point. - public override ref ushort GetIntensityUInt_16(ref PosD3NorF3InUs point) - { - return ref point.Intensity; - } - /// - /// Sets the intensity of a point cloud point if is true. - /// - /// The point cloud point. - /// The new intensity value. - public override void SetIntensityUInt_16(ref PosD3NorF3InUs point, ushort val) - { - point.Intensity = val; - } - /// - /// Returns the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - public override ref float3 GetNormalFloat3_32(ref PosD3NorF3InUs point) - { - return ref point.Normal; - } - /// - /// Sets the normal vector of a point cloud point if is true. - /// - /// The point cloud point. - /// The new normal vector. - public override void SetNormalFloat3_32(ref PosD3NorF3InUs point, float3 val) - { - point.Normal = val; - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 3c07977c4..5503b9263 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -5,7 +5,6 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Common.Accessors; -using Fusee.PointCloud.Core.Accessors; using System; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -20,7 +19,6 @@ public static class MeshMaker /// /// Generic method that creates meshes with 65k points maximum. /// - /// The point accessor allows access to the point data without casting to a explicit point type."/> /// The generic point cloud points. /// The method that defines how to create a GpuMesh from the point cloud points. /// diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 1948be1d8..b90526cbd 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -4,7 +4,6 @@ using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Core.Accessors; using Microsoft.Extensions.Caching.Memory; using System; using System.Buffers; @@ -74,7 +73,6 @@ namespace Fusee.PointCloud.Core /// private MemoryCache> _gpuDataCache; - private readonly PointAccessor _pointAccessor; private readonly CreateGpuData _createGpuDataHandler; private readonly LoadPointsHandler _loadPointsHandler; private const int _maxNumberOfDisposals = 1; @@ -86,11 +84,10 @@ namespace Fusee.PointCloud.Core /// /// Creates a new instance. /// - /// The point accessor that allows to access the point data. /// Method that knows how to create a mesh for the explicit point type (see ). /// The method that is able to load the points from the hard drive/file. /// - public PointCloudDataHandler(PointAccessor pointAccessor, CreateGpuData createMeshHandler, LoadPointsHandler loadPointsHandler, bool doRenderInstanced = false) + public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPointsHandler loadPointsHandler, bool doRenderInstanced = false) { _pointCache = new(); _gpuDataCache = new() @@ -101,7 +98,6 @@ public PointCloudDataHandler(PointAccessor pointAccessor, CreateGpuData< _createGpuDataHandler = createMeshHandler; _loadPointsHandler = loadPointsHandler; - _pointAccessor = pointAccessor; _doRenderInstanced = doRenderInstanced; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 9ba18fe9e..af3ba2f0a 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -2,7 +2,6 @@ using Fusee.Base.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Common.Accessors; -using Fusee.PointCloud.Core.Accessors; using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; @@ -102,21 +101,11 @@ public LASPoint() { } internal ushort B = 0; } - internal class PotreeAccessor : PointAccessor - { - - } - /// /// This class provides methods to convert and save clouds to LAS 1.4 /// public class Potree2LAS : IPointWriter { - /// - /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . - /// - public IPointAccessor PointAccessor => new PotreeAccessor(); - /// /// Returns the point type. /// @@ -153,18 +142,18 @@ public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory( - (PointAccessor)PointAccessor, MeshMaker.CreateMeshPosD3ColF3LblB, + var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshPosD3ColF3LblB, LoadNodeData); var imp = new Potree2Cloud(dataHandler, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.Instanced: { - var dataHandlerInstanced = new PointCloudDataHandler( - (PointAccessor)PointAccessor, MeshMaker.CreateInstanceDataPosD3ColF3LblB, + var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataPosD3ColF3LblB, LoadNodeData, true); var imp = new Potree2CloudInstanced(dataHandlerInstanced, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.DynamicMesh: { - var dataHandlerDynamic = new PointCloudDataHandler( - (PointAccessor)PointAccessor, MeshMaker.CreateDynamicMeshPosD3ColF3LblB, + var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshPosD3ColF3LblB, LoadNodeData); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); diff --git a/src/PointCloud/Potree/V2/Potree2RwBase.cs b/src/PointCloud/Potree/V2/Potree2RwBase.cs index 96580bb5b..9a5364997 100644 --- a/src/PointCloud/Potree/V2/Potree2RwBase.cs +++ b/src/PointCloud/Potree/V2/Potree2RwBase.cs @@ -1,7 +1,6 @@ using CommunityToolkit.Diagnostics; using Fusee.PointCloud.Common; using Fusee.PointCloud.Common.Accessors; -using Fusee.PointCloud.Core.Accessors; using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; @@ -60,7 +59,6 @@ protected string OctreeFilePath public Potree2RwBase(ref PotreeData potreeData) { _potreeData = potreeData; - PointAccessor = new PosD3ColF3LblBAccessor(); CacheMetadata(); @@ -72,11 +70,6 @@ public Potree2RwBase(ref PotreeData potreeData) /// public PointType PointType => PointType.PosD3ColF3LblB; - /// - /// A PointAccessor allows access to the point information (position, color, ect.) without casting it to a specific . - /// - public IPointAccessor PointAccessor { get; protected set; } - protected void CacheMetadata() { if (!cachedMetadata) From 99a30f0f72dda2eb67de731d4fe2391db810527f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 15 Feb 2023 11:58:21 +0100 Subject: [PATCH 044/294] Cleanup, fixing missing namespaces --- src/PointCloud/Common/IPointReader.cs | 1 - src/PointCloud/Common/IPointWriter.cs | 1 - src/PointCloud/Core/MeshMaker.cs | 2 - src/PointCloud/Potree/Potree2LAS.cs | 5 -- src/PointCloud/Potree/V2/Potree2Reader.cs | 42 ++++++--------- src/PointCloud/Potree/V2/Potree2RwBase.cs | 66 ++++++++++++++++++++--- src/PointCloud/Potree/V2/Potree2Writer.cs | 7 +++ 7 files changed, 82 insertions(+), 42 deletions(-) diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 98396e446..4fbadf813 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -1,5 +1,4 @@ using CommunityToolkit.HighPerformance.Buffers; -using Fusee.PointCloud.Common.Accessors; using System; using System.Buffers; diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index d97e51d9f..d9d1d6ecc 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -1,5 +1,4 @@ using Fusee.Math.Core; -using Fusee.PointCloud.Common.Accessors; using System; using System.IO; using System.Text; diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 5503b9263..bc2e9bec1 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -4,8 +4,6 @@ using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Common.Accessors; -using System; using System.Collections.Generic; using System.Runtime.InteropServices; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index af3ba2f0a..6c418c440 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,11 +1,6 @@ -using CommunityToolkit.Diagnostics; -using Fusee.Base.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Common.Accessors; -using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 3979925fe..f7cfbd000 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -4,18 +4,11 @@ using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Common.Accessors; using Fusee.PointCloud.Core; using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2.Data; using System; -using System.Buffers; -using System.Diagnostics; -using System.IO; -using System.IO.MemoryMappedFiles; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using System.Threading; namespace Fusee.PointCloud.Potree.V2 { @@ -71,6 +64,7 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta /// public IPointCloudOctree GetOctree() { + int pointSize = 0; if (_potreeData.Metadata != null) @@ -83,6 +77,8 @@ public IPointCloudOctree GetOctree() _potreeData.Metadata.PointSize = pointSize; } + Guard.IsNotNull(_potreeData.Metadata); + var center = _potreeData.Hierarchy.Root.Aabb.Center; var size = _potreeData.Hierarchy.Root.Aabb.Size.y; var maxLvl = _potreeData.Metadata.Hierarchy.Depth; @@ -104,30 +100,26 @@ public MemoryOwner LoadNodeData(OctantId id) where TPoint : stru { var node = FindNode(ref _potreeData.Hierarchy, id); - if (node != null) - { - return LoadNodeData(node); - } + // if node is null the hierarchy is broken and we look for an octant that isn't there... + Guard.IsNotNull(node); - return null; + return LoadNodeData(node); } - public MemoryOwner LoadNodeData(PotreeNode potreeNode) where TPoint : struct + private MemoryOwner LoadNodeData(PotreeNode potreeNode) where TPoint : struct { - if (potreeNode != null) - { - potreeNode.IsLoaded = true; - return ReadNodeData(potreeNode); - } + // if the potree node is null #nullable doesn't work! + Guard.IsNotNull(potreeNode); + + // TODO: Add switch to cast to the the correct point type - return null; + potreeNode.IsLoaded = true; + return ReadNodeData(potreeNode); } private MemoryOwner ReadNodeData(PotreeNode node) where TPoint : struct { - // PosD3 ColF3 LblB - Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); var potreePointSize = (int)node.NumPoints * _potreeData.Metadata.PointSize; @@ -156,7 +148,7 @@ private MemoryOwner ReadNodeData(PotreeNode node) where TPoint : var colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); var rgb = MemoryMarshal.Cast(colorSlice); - float3 color = float3.Zero; + var color = float3.Zero; color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); @@ -167,12 +159,10 @@ private MemoryOwner ReadNodeData(PotreeNode node) where TPoint : byte label = new Span(pointArray).Slice(i + offsetClassification, Marshal.SizeOf())[0]; var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); - posSpan.CopyTo(currentMemoryPt.Slice(0)); - colorSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length)); + posSpan.CopyTo(currentMemoryPt[..]); + colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); currentMemoryPt[^1] = label; - var currentPt = MemoryMarshal.Cast(currentMemoryPt); - pointCount++; } diff --git a/src/PointCloud/Potree/V2/Potree2RwBase.cs b/src/PointCloud/Potree/V2/Potree2RwBase.cs index 9a5364997..8138a690d 100644 --- a/src/PointCloud/Potree/V2/Potree2RwBase.cs +++ b/src/PointCloud/Potree/V2/Potree2RwBase.cs @@ -1,35 +1,70 @@ using CommunityToolkit.Diagnostics; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Common.Accessors; using Fusee.PointCloud.Potree.V2.Data; -using System; -using System.Collections.Generic; using System.IO; using System.IO.MemoryMappedFiles; -using System.Threading; namespace Fusee.PointCloud.Potree.V2 { + /// + /// This is the base class for reading and writing s. + /// public abstract class Potree2RwBase { + /// + /// The + /// protected PotreeData _potreeData; - protected bool cachedMetadata = false; + /// + /// Save if metadata has already been cached + /// + protected bool isMetadataCached = false; + /// + /// Offset in bytes to the position value in bytes in raw Potree stream + /// protected int offsetPosition = -1; + /// + /// Offset in bytes to the intensity value in bytes in raw Potree stream + /// protected int offsetIntensity = -1; + /// + /// Offset in bytes to the return number value in bytes in raw Potree stream + /// protected int offsetReturnNumber = -1; + /// + /// Offset in bytes to the number of returns value in bytes in raw Potree stream + /// protected int offsetNumberOfReturns = -1; + /// + /// Offset in bytes to the classification value in bytes in raw Potree stream + /// protected int offsetClassification = -1; + /// + /// Offset in bytes to the scan angle rank value in bytes in raw Potree stream + /// protected int offsetScanAngleRank = -1; + /// + /// Offset in bytes to the user data value in bytes in raw Potree stream + /// protected int offsetUserData = -1; + /// + /// Offset in bytes to the point source id value in bytes in raw Potree stream + /// protected int offsetPointSourceId = -1; + /// + /// Offset in bytes to the color value in bytes in raw Potree stream + /// protected int offsetColor = -1; private string _octreeFilePath; private MemoryMappedFile _octreeMappedFile; private MemoryMappedViewAccessor _octreeViewAccessor; + /// + /// The to the underlying octree.bin file + /// protected MemoryMappedViewAccessor OctreeMappedViewAccessor { get @@ -42,6 +77,10 @@ protected MemoryMappedViewAccessor OctreeMappedViewAccessor } } + /// + /// The path to the octree.bin file + /// On assign the and the is being created. + /// protected string OctreeFilePath { set @@ -56,6 +95,10 @@ protected string OctreeFilePath get => _octreeFilePath; } + /// + /// Ctor for RW base. Reads and chaches metadata and sets the path to the octree.bin file + /// + /// public Potree2RwBase(ref PotreeData potreeData) { _potreeData = potreeData; @@ -70,9 +113,12 @@ public Potree2RwBase(ref PotreeData potreeData) /// public PointType PointType => PointType.PosD3ColF3LblB; + /// + /// Read and cache metadata from the metadata.json file + /// protected void CacheMetadata() { - if (!cachedMetadata) + if (!isMetadataCached) { if (_potreeData.Metadata.Attributes.ContainsKey("position")) { @@ -123,10 +169,16 @@ protected void CacheMetadata() _potreeData.Metadata.PointSize = pointSize; } - cachedMetadata = true; + isMetadataCached = true; } } + /// + /// Iterate the hierarchy, find the node from the given . + /// + /// + /// + /// public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) { return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index d13f84012..dd011972b 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -6,8 +6,15 @@ namespace Fusee.PointCloud.Potree.V2 { + /// + /// Writes Potree data + /// public class Potree2Writer : Potree2RwBase { + /// + /// Generate a instance. + /// + /// public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } /// From d651893eaddc95c80703aa046f0eabc6f105447e Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Wed, 15 Feb 2023 11:06:15 +0000 Subject: [PATCH 045/294] Linting --- src/PointCloud/Potree/Potree2LAS.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6c418c440..6d0e7fe08 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -137,18 +137,18 @@ public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory Date: Wed, 15 Feb 2023 12:38:58 +0100 Subject: [PATCH 046/294] Remove PA from reading node data --- src/PointCloud/Common/IPointReader.cs | 8 ++--- src/PointCloud/Common/PointTypes.cs | 9 ++++++ src/PointCloud/Core/PointCloudDataHandler.cs | 1 - src/PointCloud/Potree/V2/Potree2Reader.cs | 32 +++++++++----------- src/PointCloud/Potree/V2/Potree2RwBase.cs | 12 +++++--- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 4fbadf813..7420d567d 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -27,11 +27,11 @@ public interface IPointReader public IPointCloudOctree GetOctree(); /// - /// Returns the points for one octant as generic array. + /// /// - /// The generic point type. - /// The unique id of the octant. - public MemoryOwner LoadNodeData(OctantId id) where TPoint : struct; + /// + /// + MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id); } } \ No newline at end of file diff --git a/src/PointCloud/Common/PointTypes.cs b/src/PointCloud/Common/PointTypes.cs index f3e8076de..384c495e8 100644 --- a/src/PointCloud/Common/PointTypes.cs +++ b/src/PointCloud/Common/PointTypes.cs @@ -68,6 +68,7 @@ public enum PointType /// /// Point type: Position double3. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3 { /// @@ -79,6 +80,7 @@ public struct PosD3 /// /// Point type: Position, color, intensity. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3ColF3InUs { /// @@ -98,6 +100,7 @@ public struct PosD3ColF3InUs /// /// Point type: Position, intensity. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3InUs { /// @@ -113,6 +116,7 @@ public struct PosD3InUs /// /// Point type: Position, color. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3ColF3 { /// @@ -128,6 +132,7 @@ public struct PosD3ColF3 /// /// Point type: Position and label color. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3LblB { /// @@ -143,6 +148,7 @@ public struct PosD3LblB /// /// Point type: Position, normal, color, intensity. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3NorF3ColF3InUs { /// @@ -168,6 +174,7 @@ public struct PosD3NorF3ColF3InUs /// /// Point type: Position, normal, intensity. /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3NorF3InUs { /// @@ -187,6 +194,7 @@ public struct PosD3NorF3InUs /// /// Point type: , , . /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3NorF3ColF3 { /// @@ -227,6 +235,7 @@ public struct PosD3ColF3LblB /// /// Point type: , , and . /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PosD3ColF3InUsLblB { /// diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index b90526cbd..071158cb4 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -51,7 +51,6 @@ namespace Fusee.PointCloud.Core /// /// /// Generic that describes the point type. - /// The that can be used to access the point data without casting the points. /// The point cloud points as generic array. /// public delegate TGpuData CreateGpuData(MemoryOwner points, OctantId octantId); diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index f7cfbd000..420461626 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -8,6 +8,7 @@ using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2.Data; using System; +using System.Buffers; using System.Runtime.InteropServices; namespace Fusee.PointCloud.Potree.V2 @@ -37,21 +38,21 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta case RenderMode.StaticMesh: { var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshPosD3ColF3LblB, - LoadNodeData); + LoadNodeDataPosD3ColF3LblB); var imp = new Potree2Cloud(dataHandler, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.Instanced: { var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataPosD3ColF3LblB, - LoadNodeData, true); + LoadNodeDataPosD3ColF3LblB, true); var imp = new Potree2CloudInstanced(dataHandlerInstanced, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.DynamicMesh: { var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshPosD3ColF3LblB, - LoadNodeData); + LoadNodeDataPosD3ColF3LblB); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); } @@ -90,35 +91,32 @@ public IPointCloudOctree GetOctree() return octree; } - /// - /// Returns the points for one octant as generic array. - /// - /// The generic point type. - /// The unique id of the octant. - /// - public MemoryOwner LoadNodeData(OctantId id) where TPoint : struct + public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) { var node = FindNode(ref _potreeData.Hierarchy, id); // if node is null the hierarchy is broken and we look for an octant that isn't there... Guard.IsNotNull(node); - return LoadNodeData(node); + return LoadNodeData(node, PointType.PosD3ColF3LblB); } - private MemoryOwner LoadNodeData(PotreeNode potreeNode) where TPoint : struct + + private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointType type) where TPoint : struct { // if the potree node is null #nullable doesn't work! Guard.IsNotNull(potreeNode); - - // TODO: Add switch to cast to the the correct point type - potreeNode.IsLoaded = true; - return ReadNodeData(potreeNode); + return type switch + { + // TODO: add missing cases + PointType.PosD3ColF3LblB => ReadNodeDataPosD3ColF3LblB(potreeNode), + _ => ThrowHelper.ThrowArgumentOutOfRangeException>(nameof(type)), + }; } - private MemoryOwner ReadNodeData(PotreeNode node) where TPoint : struct + private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) where TPoint : struct { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); diff --git a/src/PointCloud/Potree/V2/Potree2RwBase.cs b/src/PointCloud/Potree/V2/Potree2RwBase.cs index 8138a690d..ac59b99c5 100644 --- a/src/PointCloud/Potree/V2/Potree2RwBase.cs +++ b/src/PointCloud/Potree/V2/Potree2RwBase.cs @@ -58,9 +58,9 @@ public abstract class Potree2RwBase /// protected int offsetColor = -1; - private string _octreeFilePath; - private MemoryMappedFile _octreeMappedFile; - private MemoryMappedViewAccessor _octreeViewAccessor; + private string? _octreeFilePath; + private MemoryMappedFile? _octreeMappedFile; + private MemoryMappedViewAccessor? _octreeViewAccessor; /// /// The to the underlying octree.bin file @@ -92,7 +92,11 @@ protected string OctreeFilePath _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); } - get => _octreeFilePath; + get + { + Guard.IsNotNullOrEmpty(_octreeFilePath); + return _octreeFilePath; + } } /// From 9bbe0c51651988231d46ed3dc6ad1dbfa2eb058e Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 15 Feb 2023 14:36:28 +0100 Subject: [PATCH 047/294] Fixed compilation, removed obsolete LASReader --- Fusee.sln | 31 ----------------- src/PointCloud/Common/IPointReader.cs | 8 ----- src/PointCloud/Las/Desktop/LasPointReader.cs | 33 ++++++------------- src/PointCloud/Las/Shared/LasInternalPoint.cs | 1 + 4 files changed, 11 insertions(+), 62 deletions(-) diff --git a/Fusee.sln b/Fusee.sln index 0803971ea..62665bb4d 100644 --- a/Fusee.sln +++ b/Fusee.sln @@ -258,8 +258,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Tools.Build.Blazorpat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Tools.Build.Versionupdate", "src\Tools\Build\Versionupdate\Fusee.Tools.Build.Versionupdate.csproj", "{59693479-2561-45B0-BB9E-10337CA9DF5C}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Las", "Las", "{E364F712-2400-4D9F-8F40-8B5870086262}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PointCloudPotree2", "PointCloudPotree2", "{B2C0746D-E5CC-45F1-BD88-3E0D5A2A7191}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Examples.PointCloudPotree2.Desktop", "Examples\Complete\PointCloudPotree2\Desktop\Fusee.Examples.PointCloudPotree2.Desktop.csproj", "{72974011-C264-4D3D-979A-A4E750C5787A}" @@ -270,14 +268,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Examples.Deferred.Bla EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Examples.PointCloudPotree2.Wpf", "Examples\Complete\PointCloudPotree2\Wpf\Fusee.Examples.PointCloudPotree2.Wpf.csproj", "{35839EA8-889B-4C26-94CB-9DB309E9C9F3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Desktop", "Desktop", "{FA703895-434C-4ECF-8E0D-3145280DA2C6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{6F281DDE-0376-40C4-9693-51C7CD2F49D4}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Fusee.PointCloud.Las.Shared", "src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.shproj", "{DCC7DA71-3E2E-476C-8391-1F9651637503}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.PointCloud.Las.Desktop", "src\PointCloud\Las\Desktop\Fusee.PointCloud.Las.Desktop.csproj", "{F28634E7-52B8-4935-B19E-CB8A6844E6F1}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.PointCloud.Potree", "src\PointCloud\Potree\Fusee.PointCloud.Potree.csproj", "{1B99F3FD-C685-4D72-8EBA-94A1214469B5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RenderContextOnly", "RenderContextOnly", "{E6E73351-7DED-4B97-B254-A0E903D769C1}" @@ -1595,20 +1585,6 @@ Global {35839EA8-889B-4C26-94CB-9DB309E9C9F3}.Release-Blazor|Any CPU.ActiveCfg = Release|Any CPU {35839EA8-889B-4C26-94CB-9DB309E9C9F3}.Release-Desktop|Any CPU.ActiveCfg = Release|Any CPU {35839EA8-889B-4C26-94CB-9DB309E9C9F3}.Release-NuGet|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug-Android|Any CPU.ActiveCfg = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug-Blazor|Any CPU.ActiveCfg = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug-Desktop|Any CPU.ActiveCfg = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Debug-Desktop|Any CPU.Build.0 = Debug|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release|Any CPU.Build.0 = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-Android|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-Blazor|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-Desktop|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-Desktop|Any CPU.Build.0 = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-NuGet|Any CPU.ActiveCfg = Release|Any CPU - {F28634E7-52B8-4935-B19E-CB8A6844E6F1}.Release-NuGet|Any CPU.Build.0 = Release|Any CPU {1B99F3FD-C685-4D72-8EBA-94A1214469B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1B99F3FD-C685-4D72-8EBA-94A1214469B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {1B99F3FD-C685-4D72-8EBA-94A1214469B5}.Debug-Android|Any CPU.ActiveCfg = Debug|Any CPU @@ -1854,16 +1830,11 @@ Global {594F2025-8C23-40D2-8100-C9989CA16DC9} = {0C344D5F-BE70-4828-B95A-1D313B894BC3} {8CF98098-64E7-421B-9CBF-67E02EDE6D32} = {594F2025-8C23-40D2-8100-C9989CA16DC9} {59693479-2561-45B0-BB9E-10337CA9DF5C} = {594F2025-8C23-40D2-8100-C9989CA16DC9} - {E364F712-2400-4D9F-8F40-8B5870086262} = {549D61C5-EFE6-4C2B-AD19-BB84A36C5917} {B2C0746D-E5CC-45F1-BD88-3E0D5A2A7191} = {E68628DA-312F-4171-A5ED-08072B5CCA3C} {72974011-C264-4D3D-979A-A4E750C5787A} = {B2C0746D-E5CC-45F1-BD88-3E0D5A2A7191} {76FE409A-DCA8-4714-8E95-4FE189751EE7} = {B2C0746D-E5CC-45F1-BD88-3E0D5A2A7191} {84C77C62-0721-4C6F-9789-88D5F9C98974} = {E7E4600F-D815-49F6-B79C-C456C87F01DC} {35839EA8-889B-4C26-94CB-9DB309E9C9F3} = {B2C0746D-E5CC-45F1-BD88-3E0D5A2A7191} - {FA703895-434C-4ECF-8E0D-3145280DA2C6} = {E364F712-2400-4D9F-8F40-8B5870086262} - {6F281DDE-0376-40C4-9693-51C7CD2F49D4} = {E364F712-2400-4D9F-8F40-8B5870086262} - {DCC7DA71-3E2E-476C-8391-1F9651637503} = {6F281DDE-0376-40C4-9693-51C7CD2F49D4} - {F28634E7-52B8-4935-B19E-CB8A6844E6F1} = {FA703895-434C-4ECF-8E0D-3145280DA2C6} {1B99F3FD-C685-4D72-8EBA-94A1214469B5} = {549D61C5-EFE6-4C2B-AD19-BB84A36C5917} {E6E73351-7DED-4B97-B254-A0E903D769C1} = {E68628DA-312F-4171-A5ED-08072B5CCA3C} {81209326-E0D1-48C7-878F-F3DEA131BFB1} = {E6E73351-7DED-4B97-B254-A0E903D769C1} @@ -1885,8 +1856,6 @@ Global src\Engine\Imp\Graphics\SharedAll\Fusee.Engine.Imp.Graphics.SharedAll.projitems*{7638e21e-193f-4ac7-8f01-b595fe8b3caa}*SharedItemsImports = 13 src\Engine\Imp\Graphics\SharedAll\Fusee.Engine.Imp.Graphics.SharedAll.projitems*{b3ce4f39-fcc4-4388-8130-9d0b9d65d034}*SharedItemsImports = 5 src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{b3ce4f39-fcc4-4388-8130-9d0b9d65d034}*SharedItemsImports = 5 - src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{dcc7da71-3e2e-476c-8391-1f9651637503}*SharedItemsImports = 13 - src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{f28634e7-52b8-4935-b19e-cb8a6844e6f1}*SharedItemsImports = 5 src\Engine\Imp\Graphics\SharedAll\Fusee.Engine.Imp.Graphics.SharedAll.projitems*{f7ad2bb5-d2b0-4697-bddb-4cc26152a6b6}*SharedItemsImports = 5 src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{f7ad2bb5-d2b0-4697-bddb-4cc26152a6b6}*SharedItemsImports = 5 EndGlobalSection diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 7420d567d..985a6a7b0 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -25,13 +25,5 @@ public interface IPointReader /// Reads the Potree file and returns an octree. /// public IPointCloudOctree GetOctree(); - - /// - /// - /// - /// - /// - MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id); - } } \ No newline at end of file diff --git a/src/PointCloud/Las/Desktop/LasPointReader.cs b/src/PointCloud/Las/Desktop/LasPointReader.cs index 77f3212b9..ac9d58d32 100644 --- a/src/PointCloud/Las/Desktop/LasPointReader.cs +++ b/src/PointCloud/Las/Desktop/LasPointReader.cs @@ -1,8 +1,6 @@ using Fusee.Base.Imp.Desktop; using Fusee.Math.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Common.Accessors; -using Fusee.PointCloud.Core.Accessors; using System; using System.IO; using System.Runtime.InteropServices; @@ -15,8 +13,6 @@ namespace Fusee.PointCloud.Las.Desktop /// public class LasPointReader : IDisposable, IPointReader { - public IPointAccessor PointAccessor { get; private set; } - /// /// The point cloud meta data, usually stored in the header of the las file. /// @@ -106,6 +102,7 @@ private void OpenFile(string filename) /// The path to a las encoded file. public LasPointReader(string filePath) { + throw new NotImplementedException(""); _filePath = filePath; OpenFile(filePath); } @@ -129,17 +126,16 @@ public LasPointReader() { } /// Reads the given amount of points from stream /// /// - /// /// /// - public TPoint[] ReadNPoints(int n, IPointAccessor pa) where TPoint : new() + public TPoint[] ReadNPoints(int n) where TPoint : new() { if (_ptrToLASClass == IntPtr.Zero) throw new FileNotFoundException("No file was specified yet. Call 'OpenFile' first"); var points = new TPoint[n]; for (var i = 0; i < points.Length; i++) { - if (!ReadNextPoint(ref points[i], pa)) break; + if (!ReadNextPoint(ref points[i])) break; } return points; } @@ -150,7 +146,7 @@ public LasPointReader() { } /// /// /// - public bool ReadNextPoint(ref TPoint point, IPointAccessor pa) where TPoint : new() + public bool ReadNextPoint(ref TPoint point) where TPoint : new() { if (point == null) throw new ArgumentOutOfRangeException("No writable point found!"); @@ -162,21 +158,12 @@ public LasPointReader() { } var currentPoint = new LasInternalPoint(); GetPoint(_ptrToLASClass, ref currentPoint); - var typedAccessor = (PointAccessor)pa; - - if ((MetaInfo.PointDataFormat == 2 || MetaInfo.PointDataFormat == 3) && typedAccessor.ColorType == PointColorType.Float3) - typedAccessor.SetColorFloat3_32(ref point, new float3(currentPoint.R, currentPoint.G, currentPoint.B)); - - //TODO: Complete - //if (typedAccessor.PositionType == PointPositionType.Double3) - // -> always the case right now - typedAccessor.SetPositionFloat3_64(ref point, new double3(currentPoint.X * MetaInfo.ScaleFactorX, currentPoint.Y * MetaInfo.ScaleFactorY, currentPoint.Z * MetaInfo.ScaleFactorZ)); + var pos = new double3(currentPoint.X * MetaInfo.ScaleFactorX, currentPoint.Y * MetaInfo.ScaleFactorY, currentPoint.Z * MetaInfo.ScaleFactorZ); + var intensity = currentPoint.Intensity; + var color = new float3(currentPoint.R, currentPoint.G, currentPoint.B); - //if (currentFormat.HasIntensity && typedAccessor.IntensityType == PointIntensityType.UInt_16) - // -> always true right now! - typedAccessor.SetIntensityUInt_16(ref point, currentPoint.Intensity); - // -> never the case right now! + // -> never the case right now! //if (currentFormat.HasClassification && typedAccessor.LabelType == PointLabelType.UInt_8) //{ // //TODO: HACK!! label was somehow written to UserData and not to classification @@ -208,12 +195,12 @@ public PointType PointType } } - public Task LoadPointsForNodeAsync(string guid, IPointAccessor pointAccessor) where TPoint : new() + public Task LoadPointsForNodeAsync(string guid) where TPoint : new() { throw new NotImplementedException(); } - public TPoint[] LoadNodeData(string id, IPointAccessor pointAccessor) where TPoint : new() + public TPoint[] LoadNodeData(string id) where TPoint : new() { throw new NotImplementedException(); } diff --git a/src/PointCloud/Las/Shared/LasInternalPoint.cs b/src/PointCloud/Las/Shared/LasInternalPoint.cs index 48ec112e5..31f468dcb 100644 --- a/src/PointCloud/Las/Shared/LasInternalPoint.cs +++ b/src/PointCloud/Las/Shared/LasInternalPoint.cs @@ -15,4 +15,5 @@ internal struct LasInternalPoint public ushort G; public ushort B; } + } \ No newline at end of file From 4af2dc0a25521ac2f4f943ac74edd5e45df75d4e Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 16 Feb 2023 11:23:16 +0100 Subject: [PATCH 048/294] Refine GS picking --- src/Engine/Core/ScenePicker.cs | 48 ++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index ae6aac08a..1043d4cf0 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -88,6 +88,7 @@ public class PickerState : VisitorState private readonly CollapsingStateStack _model = new(); private readonly CollapsingStateStack _uiRect = new(); private readonly CollapsingStateStack _cullMode = new(); + private readonly CollapsingStateStack _shaderFX = new(); public float2 PickPosClip { get; set; } @@ -118,6 +119,12 @@ public float4x4 CanvasXForm set => _canvasXForm.Tos = value; } + public ShaderEffect ShaderEffect + { + get => _shaderFX.Tos; + set => _shaderFX.Tos = value; + } + /// /// The registered cull mode. /// @@ -139,6 +146,7 @@ public PickerState() RegisterState(_uiRect); RegisterState(_canvasXForm); RegisterState(_cullMode); + RegisterState(_shaderFX); } /// @@ -194,6 +202,7 @@ protected override void InitState() State.View = float4x4.Identity; State.Projection = float4x4.Identity; State.CanvasXForm = float4x4.Identity; + } /// @@ -504,6 +513,17 @@ public void RenderTransform(Transform transform) State.Model *= transform.Matrix; } + /// + /// Save the current shader effect + /// Later we can check if we have a geometry shader source and use FBO picking + /// + /// + [VisitMethod] + public void RenderShaderEffect(ShaderEffect effect) + { + State.ShaderEffect = effect; + } + /// /// Handles custom pick component with pick layer and custom picking methods. /// If is not active, the picking is being skipped @@ -527,10 +547,6 @@ public void HandleMesh(Mesh mesh) if (!mesh.Active) return; if (mesh == null) return; - // TODO (MR): - // Geometry shader -> PickComp? - // PointCloud -> module - if (State?.CurrentPickComp != null) { if (State?.CurrentPickComp?.CustomPickMethod != null) @@ -559,6 +575,10 @@ public void HandleMesh(Mesh mesh) Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); break; default: + // check if we have a geometry shader --> use FBO picking + if (State == null) return; + if (State.ShaderEffect.GeometryShaderSrc == null) return; + // non-triangle picking is only possible with camera Guard.IsNotNull(CurrentCamera, nameof(CurrentCamera)); Guard.IsNotNull(State, nameof(State)); @@ -581,18 +601,25 @@ public void HandleMesh(Mesh mesh) } } }; - - _writableTexture ??= new WritableTexture(RenderTargetTextureTypes.Albedo, new ImagePixelFormat(ColorFormat.RGB), _rc.ViewportWidth, _rc.ViewportHeight, false); + var w = _canvasWidth; + var h = _canvasHeight; + _writableTexture ??= WritableTexture.CreateAlbedoTex(w, h, new ImagePixelFormat(ColorFormat.Intensity)); _cameraPosition.Matrix = State.View.Invert(); cam.RenderTexture = _writableTexture; cam.FrustumCullingOn = false; - var compCopy = CurrentNode.Components.ToArray().ToList(); - container.Children.Insert(1, new SceneNode { Components = compCopy }); - _sceneRenderer = new SceneRendererForward(container); + container.Children.Insert(1, CurrentNode); + _sceneRenderer = new SceneRendererForward(container); _sceneRenderer.Render(_rc); - var pixels = _rc.ReadPixels((int)Input.Mouse.X, (int)(_canvasHeight - Input.Mouse.Y), new ImagePixelFormat(ColorFormat.RGB), 1, 1); + // convert mouse from ClipSpace back to world coordinates + // reverse pickPosClip calculation + // var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); + var mouseModelPos = PickPosClip - new float2(-1, 1); + mouseModelPos = new float2(mouseModelPos.x / (2.0f / w), mouseModelPos.y / (-2.0f / h)); + + // read pixel value at mouse coordinates/position + var pixels = _rc.ReadPixels((int)mouseModelPos.x, (int)mouseModelPos.y, new ImagePixelFormat(ColorFormat.Intensity), 1, 1); if (pixels[0] != 0) YieldItem(new PickResult @@ -601,7 +628,6 @@ public void HandleMesh(Mesh mesh) Node = CurrentNode, Model = State.Model }); - break; } } From 9e8fe3fbf374db815b3bcb657fd2c96fddeea6c8 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 16 Feb 2023 11:28:31 +0100 Subject: [PATCH 049/294] Return on no camera --- src/Engine/Core/ScenePicker.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 1043d4cf0..930cf078d 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -578,10 +578,9 @@ public void HandleMesh(Mesh mesh) // check if we have a geometry shader --> use FBO picking if (State == null) return; if (State.ShaderEffect.GeometryShaderSrc == null) return; - // non-triangle picking is only possible with camera - Guard.IsNotNull(CurrentCamera, nameof(CurrentCamera)); - Guard.IsNotNull(State, nameof(State)); + if (CurrentCamera == null) return; + var cam = new Camera(CurrentCamera.ProjectionMethod, 1, 1000, CurrentCamera.Fov); From 86d540c3ef6f704b16fc226e18a270c7a6097ed9 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 20 Feb 2023 09:26:41 +0100 Subject: [PATCH 050/294] Divided Potree Reader and Writer base classes Reader: removed PotreeData from ctor --- src/PointCloud/Potree/V2/Potree2Reader.cs | 152 ++--------- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 240 ++++++++++++++++++ src/PointCloud/Potree/V2/Potree2Writer.cs | 18 +- ...{Potree2RwBase.cs => Potree2WriterBase.cs} | 54 +++- 4 files changed, 313 insertions(+), 151 deletions(-) create mode 100644 src/PointCloud/Potree/V2/Potree2ReaderBase.cs rename src/PointCloud/Potree/V2/{Potree2RwBase.cs => Potree2WriterBase.cs} (82%) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 420461626..47114053e 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -8,7 +8,6 @@ using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2.Data; using System; -using System.Buffers; using System.Runtime.InteropServices; namespace Fusee.PointCloud.Potree.V2 @@ -16,16 +15,8 @@ namespace Fusee.PointCloud.Potree.V2 /// /// Reads Potree V2 files and is able to create a point cloud scene component, that can be rendered. /// - public class Potree2Reader : Potree2RwBase, IPointReader + public class Potree2Reader : Potree2ReaderBase, IPointReader { - /// - /// Initializes a Potree 2 reader for the given Potree dataset - /// - /// - public Potree2Reader(ref PotreeData potreeData) : base(ref potreeData) - { - } - /// /// Returns a renderable point cloud component. /// @@ -68,32 +59,38 @@ public IPointCloudOctree GetOctree() int pointSize = 0; - if (_potreeData.Metadata != null) + if (PotreeData.Metadata != null) { - foreach (var metaAttributeItem in _potreeData.Metadata.AttributesList) + foreach (var metaAttributeItem in PotreeData.Metadata.AttributesList) { pointSize += metaAttributeItem.Size; } - _potreeData.Metadata.PointSize = pointSize; + PotreeData.Metadata.PointSize = pointSize; } - Guard.IsNotNull(_potreeData.Metadata); + Guard.IsNotNull(PotreeData.Metadata); - var center = _potreeData.Hierarchy.Root.Aabb.Center; - var size = _potreeData.Hierarchy.Root.Aabb.Size.y; - var maxLvl = _potreeData.Metadata.Hierarchy.Depth; + var center = PotreeData.Hierarchy.Root.Aabb.Center; + var size = PotreeData.Hierarchy.Root.Aabb.Size.y; + var maxLvl = PotreeData.Metadata.Hierarchy.Depth; var octree = new PointCloudOctree(center, size, maxLvl); - MapChildNodesRecursive(octree.Root, _potreeData.Hierarchy.Root); + MapChildNodesRecursive(octree.Root, PotreeData.Hierarchy.Root); return octree; } + /// + /// Reads the points for a specific octant of type . + /// + /// Id of the octant. + /// public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) { - var node = FindNode(ref _potreeData.Hierarchy, id); + Guard.IsNotNull(PotreeData); + var node = FindNode(ref PotreeData.Hierarchy, id); // if node is null the hierarchy is broken and we look for an octant that isn't there... Guard.IsNotNull(node); @@ -101,7 +98,6 @@ public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) return LoadNodeData(node, PointType.PosD3ColF3LblB); } - private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointType type) where TPoint : struct { // if the potree node is null #nullable doesn't work! @@ -115,12 +111,11 @@ private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointTyp }; } - private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) where TPoint : struct { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); - var potreePointSize = (int)node.NumPoints * _potreeData.Metadata.PointSize; + var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; var pointArray = new byte[potreePointSize]; var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); @@ -129,14 +124,14 @@ private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) var pointCount = 0; - for (var i = 0; i < pointArray.Length; i += _potreeData.Metadata.PointSize) + for (var i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) { var posSlice = new Span(pointArray).Slice(i + offsetPosition, Marshal.SizeOf() * 3); var pos = MemoryMarshal.Cast(posSlice); - double x = pos[0] * _potreeData.Metadata.Scale.x; - double y = pos[1] * _potreeData.Metadata.Scale.y; - double z = pos[2] * _potreeData.Metadata.Scale.z; + double x = pos[0] * PotreeData.Metadata.Scale.x; + double y = pos[1] * PotreeData.Metadata.Scale.y; + double z = pos[2] * PotreeData.Metadata.Scale.z; double3 position = new(x, y, z); position = Potree2Consts.YZflip * position; @@ -167,111 +162,6 @@ private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) return returnMemory; } - //public TPotreePoint[] ReadRawPoints(OctantId oid) where TPotreePoint : struct - //{ - // var node = FindNode(ref _potreeData.Hierarchy, oid); - - // var points = new TPotreePoint[node.NumPoints]; - - - // for (var i = 0; i < points.Length; i++) - // points[i] = new TPotreePoint(); - - // var binaryReader = new BinaryReader(File.OpenRead(OctreeFilePath)); - - // for (int i = 0; i < node.NumPoints; i++) - // { - // if (offsetPosition > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - // double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; - // double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; - // double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; - - // double3 position = new(x, y, z); - // position = Potree2Consts.YZflip * position; - - // points[i].Position = position; - // } - - // if (offsetIntensity > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // Int16 intensity = binaryReader.ReadInt16(); - - // points[i].Intensity = intensity; - // } - // if (offsetReturnNumber > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // byte returnNumber = binaryReader.ReadByte(); - - // points[i].ReturnNumber = returnNumber; - // } - // if (offsetNumberOfReturns > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // byte numberOfReturns = binaryReader.ReadByte(); - - // points[i].NumberOfReturns = numberOfReturns; - // } - - // if (offsetClassification > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - - // byte label = binaryReader.ReadByte(); - - // points[i].Classification = label; - // } - - // else if (offsetScanAngleRank > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // byte scanAngleRank = binaryReader.ReadByte(); - - // points[i].ScanAngleRank = scanAngleRank; - // } - // else if (offsetUserData > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // byte userData = binaryReader.ReadByte(); - - // points[i].UserData = userData; - // } - // else if (offsetPointSourceId > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // byte pointSourceId = binaryReader.ReadByte(); - - // points[i].PointSourceId = pointSourceId; - // } - - // if (offsetColor > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - // ushort r = binaryReader.ReadUInt16(); - // ushort g = binaryReader.ReadUInt16(); - // ushort b = binaryReader.ReadUInt16(); - - // float3 color = float3.Zero; - - // color.r = ((byte)(r > 255 ? r / 256 : r)); - // color.g = ((byte)(g > 255 ? g / 256 : g)); - // color.b = ((byte)(b > 255 ? b / 256 : b)); - - // points[i].Color = color; - // } - // } - - // binaryReader.Close(); - // binaryReader.Dispose(); - - // return points; - //} - private static void MapChildNodesRecursive(IPointCloudOctant octreeNode, PotreeNode potreeNode) { octreeNode.NumberOfPointsInNode = (int)potreeNode.NumPoints; diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs new file mode 100644 index 000000000..b56bfeeb9 --- /dev/null +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -0,0 +1,240 @@ +using CommunityToolkit.Diagnostics; +using Fusee.PointCloud.Common; +using Fusee.PointCloud.Potree.V2.Data; +using System; +using System.IO; +using System.IO.MemoryMappedFiles; + +namespace Fusee.PointCloud.Potree.V2 +{ + /// + /// This is the base class for reading and writing s. + /// + public abstract class Potree2ReaderBase : IDisposable + { + /// + /// The + /// + public PotreeData? PotreeData + { + get => _potreeData; + private set + { + _potreeData = value; + } + } + private PotreeData? _potreeData; + + /// + /// Save if metadata has already been cached + /// + protected bool isMetadataCached = false; + + /// + /// Offset in bytes to the position value in bytes in raw Potree stream + /// + protected int offsetPosition = -1; + /// + /// Offset in bytes to the intensity value in bytes in raw Potree stream + /// + protected int offsetIntensity = -1; + /// + /// Offset in bytes to the return number value in bytes in raw Potree stream + /// + protected int offsetReturnNumber = -1; + /// + /// Offset in bytes to the number of returns value in bytes in raw Potree stream + /// + protected int offsetNumberOfReturns = -1; + /// + /// Offset in bytes to the classification value in bytes in raw Potree stream + /// + protected int offsetClassification = -1; + /// + /// Offset in bytes to the scan angle rank value in bytes in raw Potree stream + /// + protected int offsetScanAngleRank = -1; + /// + /// Offset in bytes to the user data value in bytes in raw Potree stream + /// + protected int offsetUserData = -1; + /// + /// Offset in bytes to the point source id value in bytes in raw Potree stream + /// + protected int offsetPointSourceId = -1; + /// + /// Offset in bytes to the color value in bytes in raw Potree stream + /// + protected int offsetColor = -1; + + private string? _octreeFilePath; + private MemoryMappedFile? _octreeMappedFile; + private MemoryMappedViewAccessor? _octreeViewAccessor; + private bool disposedValue; + + /// + /// The to the underlying octree.bin file + /// + protected MemoryMappedViewAccessor OctreeMappedViewAccessor + { + get + { + Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); + Guard.IsNotNull(_octreeMappedFile, nameof(_octreeMappedFile)); + + return _octreeViewAccessor; + + } + } + + /// + /// The path to the octree.bin file + /// On assign the and the is being created. + /// + protected string OctreeFilePath + { + set + { + Guard.IsNotNullOrEmpty(value, nameof(value)); + Guard.IsTrue(File.Exists(value)); + + _octreeFilePath = value; + + _octreeMappedFile?.Dispose(); + _octreeViewAccessor?.Dispose(); + + _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); + _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); + } + get + { + Guard.IsNotNullOrEmpty(_octreeFilePath); + return _octreeFilePath; + } + } + + /// + /// Returns the point type. + /// + public PointType PointType => PointType.PosD3ColF3LblB; + + /// + /// Reads a potree file. + /// + /// Path to the file. + /// Meta and octree data of the potree file. + public void ReadNewFile(string path, out PotreeData potreeData) + { + PotreeData = new PotreeData(path); + + CacheMetadata(); + OctreeFilePath = Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName); + + potreeData = PotreeData; + } + + /// + /// Read and cache metadata from the metadata.json file + /// + protected void CacheMetadata() + { + Guard.IsNotNull(_potreeData); + + if (!isMetadataCached) + { + if (_potreeData.Metadata.Attributes.ContainsKey("position")) + { + offsetPosition = _potreeData.Metadata.Attributes["position"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("intensity")) + { + offsetIntensity = _potreeData.Metadata.Attributes["intensity"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("return number")) + { + offsetReturnNumber = _potreeData.Metadata.Attributes["return number"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("number of returns")) + { + offsetNumberOfReturns = _potreeData.Metadata.Attributes["number of returns"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("classification")) + { + offsetClassification = _potreeData.Metadata.Attributes["classification"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("scan angle rank")) + { + offsetScanAngleRank = _potreeData.Metadata.Attributes["scan angle rank"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("user data")) + { + offsetUserData = _potreeData.Metadata.Attributes["user data"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("point source id")) + { + offsetPointSourceId = _potreeData.Metadata.Attributes["point source id"].AttributeOffset; + } + if (_potreeData.Metadata.Attributes.ContainsKey("rgb")) + { + offsetColor = _potreeData.Metadata.Attributes["rgb"].AttributeOffset; + } + + int pointSize = 0; + + if (_potreeData.Metadata != null) + { + foreach (var metaAttributeItem in _potreeData.Metadata.AttributesList) + { + pointSize += metaAttributeItem.Size; + } + + _potreeData.Metadata.PointSize = pointSize; + } + + isMetadataCached = true; + } + } + + /// + /// Iterate the hierarchy, find the node from the given . + /// + /// + /// + /// + public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) + { + return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); + } + + /// + /// Disposes of the MemoryMapped objects to free the file. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + _octreeMappedFile?.Dispose(); + _octreeViewAccessor?.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + /// + /// Manually calling this disposes of the MemoryMapped objects to free the file. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index dd011972b..34c374bcc 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -1,5 +1,4 @@ using Fusee.Math.Core; -using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; @@ -9,13 +8,12 @@ namespace Fusee.PointCloud.Potree.V2 /// /// Writes Potree data /// - public class Potree2Writer : Potree2RwBase + public class Potree2Writer : Potree2WriterBase { /// /// Generate a instance. /// - /// - public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } + public Potree2Writer(PotreeData potreeData) : base(potreeData) { } /// /// Directly writes the action of one given set of selectors to disk. @@ -225,7 +223,7 @@ public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } double3 point = double3.Zero; - foreach (var node in _potreeData.Hierarchy.Nodes) + foreach (var node in PotreeData.Hierarchy.Nodes) { if (nodeSelector(node)) { @@ -233,17 +231,17 @@ public Potree2Writer(ref PotreeData potreeData) : base(ref potreeData) { } for (int i = 0; i < node.NumPoints; i++) { - binaryReader.BaseStream.Position = node.ByteOffset + 0 + i * _potreeData.Metadata.PointSize; + binaryReader.BaseStream.Position = node.ByteOffset + 0 + i * PotreeData.Metadata.PointSize; - point.x = (binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x); - point.z = (binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y); - point.y = (binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z); + point.x = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.x); + point.z = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.y); + point.y = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.z); if (pointSelector(point)) { if (!dryrun) { - binaryWriter.BaseStream.Position = node.ByteOffset + 16 + i * _potreeData.Metadata.PointSize; + binaryWriter.BaseStream.Position = node.ByteOffset + 16 + i * PotreeData.Metadata.PointSize; binaryWriter.Write(Label); } diff --git a/src/PointCloud/Potree/V2/Potree2RwBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs similarity index 82% rename from src/PointCloud/Potree/V2/Potree2RwBase.cs rename to src/PointCloud/Potree/V2/Potree2WriterBase.cs index ac59b99c5..3ae1dc29a 100644 --- a/src/PointCloud/Potree/V2/Potree2RwBase.cs +++ b/src/PointCloud/Potree/V2/Potree2WriterBase.cs @@ -1,20 +1,30 @@ using CommunityToolkit.Diagnostics; using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; +using System; using System.IO; using System.IO.MemoryMappedFiles; namespace Fusee.PointCloud.Potree.V2 { + //TODO: cleanup fields/methods that arent necessary for the writer but are present due to this classes historic relation to the reader. /// /// This is the base class for reading and writing s. /// - public abstract class Potree2RwBase + public abstract class Potree2WriterBase : IDisposable { /// - /// The + /// The /// - protected PotreeData _potreeData; + public PotreeData? PotreeData + { + get => _potreeData; + private set + { + _potreeData = value; + } + } + private PotreeData? _potreeData; /// /// Save if metadata has already been cached @@ -61,6 +71,7 @@ public abstract class Potree2RwBase private string? _octreeFilePath; private MemoryMappedFile? _octreeMappedFile; private MemoryMappedViewAccessor? _octreeViewAccessor; + private bool disposedValue; /// /// The to the underlying octree.bin file @@ -89,6 +100,10 @@ protected string OctreeFilePath Guard.IsTrue(File.Exists(value)); _octreeFilePath = value; + + _octreeMappedFile?.Dispose(); + _octreeViewAccessor?.Dispose(); + _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); } @@ -102,14 +117,9 @@ protected string OctreeFilePath /// /// Ctor for RW base. Reads and chaches metadata and sets the path to the octree.bin file /// - /// - public Potree2RwBase(ref PotreeData potreeData) + public Potree2WriterBase(PotreeData potreeData) { - _potreeData = potreeData; - - CacheMetadata(); - - OctreeFilePath = Path.Combine(_potreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName); + PotreeData = potreeData; } /// @@ -117,6 +127,7 @@ public Potree2RwBase(ref PotreeData potreeData) /// public PointType PointType => PointType.PosD3ColF3LblB; + /// /// Read and cache metadata from the metadata.json file /// @@ -188,5 +199,28 @@ public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); } + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + _octreeMappedFile?.Dispose(); + _octreeViewAccessor?.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } \ No newline at end of file From 86a9ee2086a719f3e4fe5b0c7e1fbc1111c7cf99 Mon Sep 17 00:00:00 2001 From: RedImp1470 Date: Mon, 20 Feb 2023 09:05:12 +0000 Subject: [PATCH 051/294] Linting --- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 2 +- src/PointCloud/Potree/V2/Potree2WriterBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index b56bfeeb9..01c40a989 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -18,7 +18,7 @@ public abstract class Potree2ReaderBase : IDisposable public PotreeData? PotreeData { get => _potreeData; - private set + private set { _potreeData = value; } diff --git a/src/PointCloud/Potree/V2/Potree2WriterBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs index 3ae1dc29a..f265707a1 100644 --- a/src/PointCloud/Potree/V2/Potree2WriterBase.cs +++ b/src/PointCloud/Potree/V2/Potree2WriterBase.cs @@ -19,7 +19,7 @@ public abstract class Potree2WriterBase : IDisposable public PotreeData? PotreeData { get => _potreeData; - private set + private set { _potreeData = value; } From 933b6f299fe1b4d2a984e5ca48d0e4585f62f684 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 20 Feb 2023 11:32:17 +0100 Subject: [PATCH 052/294] Potree2ReaderBase: reset _isMetadataCached --- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index 01c40a989..ee5f4f14e 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -28,7 +28,7 @@ private set /// /// Save if metadata has already been cached /// - protected bool isMetadataCached = false; + protected bool _isMetadataCached = false; /// /// Offset in bytes to the position value in bytes in raw Potree stream @@ -83,7 +83,6 @@ protected MemoryMappedViewAccessor OctreeMappedViewAccessor Guard.IsNotNull(_octreeMappedFile, nameof(_octreeMappedFile)); return _octreeViewAccessor; - } } @@ -93,7 +92,7 @@ protected MemoryMappedViewAccessor OctreeMappedViewAccessor /// protected string OctreeFilePath { - set + private set { Guard.IsNotNullOrEmpty(value, nameof(value)); Guard.IsTrue(File.Exists(value)); @@ -125,6 +124,7 @@ protected string OctreeFilePath /// Meta and octree data of the potree file. public void ReadNewFile(string path, out PotreeData potreeData) { + _isMetadataCached = false; PotreeData = new PotreeData(path); CacheMetadata(); @@ -140,7 +140,7 @@ protected void CacheMetadata() { Guard.IsNotNull(_potreeData); - if (!isMetadataCached) + if (!_isMetadataCached) { if (_potreeData.Metadata.Attributes.ContainsKey("position")) { @@ -191,7 +191,7 @@ protected void CacheMetadata() _potreeData.Metadata.PointSize = pointSize; } - isMetadataCached = true; + _isMetadataCached = true; } } From a54956996d9e36ba853812bcbd2442fdd328e55f Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 20 Feb 2023 20:54:07 +0100 Subject: [PATCH 053/294] float3 --- src/Math/Core/float3.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Math/Core/float3.cs b/src/Math/Core/float3.cs index 57caa77ec..a0f49b238 100644 --- a/src/Math/Core/float3.cs +++ b/src/Math/Core/float3.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The float3 structure is suitable for inter-operation with unmanaged code requiring three consecutive floats. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct float3 : IEquatable @@ -20,18 +22,21 @@ public struct float3 : IEquatable /// /// The x component of the float3. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public float x; /// /// The y component of the float3. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public float y; /// /// The z component of the float3. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public float z; From 2ea628974e58799da57165ea86fe337bb2f4d4a7 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 20 Feb 2023 20:54:15 +0100 Subject: [PATCH 054/294] float4 --- src/Math/Core/float4.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Math/Core/float4.cs b/src/Math/Core/float4.cs index 03bbf1123..0c713addb 100644 --- a/src/Math/Core/float4.cs +++ b/src/Math/Core/float4.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -9,6 +10,7 @@ namespace Fusee.Math.Core /// /// The float4 structure is suitable for interoperation with unmanaged code requiring four consecutive floats. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct float4 : IEquatable @@ -18,24 +20,28 @@ public struct float4 : IEquatable /// /// The x component of the float4. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public float x; /// /// The y component of the float4. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public float y; /// /// The z component of the float4. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public float z; /// /// The w component of the float4. /// + [JsonProperty(PropertyName = "W")] [ProtoMember(4)] public float w; From d24184241c43974cf3ba991b9979d2212a046e29 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 20 Feb 2023 20:54:23 +0100 Subject: [PATCH 055/294] float4x4 --- src/Math/Core/float4x4.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Math/Core/float4x4.cs b/src/Math/Core/float4x4.cs index 236b752a8..737bd484f 100644 --- a/src/Math/Core/float4x4.cs +++ b/src/Math/Core/float4x4.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -44,6 +45,7 @@ namespace Fusee.Math.Core /// of methods are postfixed with "RH". /// /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct float4x4 : IEquatable @@ -53,24 +55,28 @@ public struct float4x4 : IEquatable /// /// Top row of the matrix /// + [JsonProperty(PropertyName = "Row1")] [ProtoMember(1)] public float4 Row1; /// /// 2nd row of the matrix /// + [JsonProperty(PropertyName = "Row2")] [ProtoMember(2)] public float4 Row2; /// /// 3rd row of the matrix /// + [JsonProperty(PropertyName = "Row3")] [ProtoMember(3)] public float4 Row3; /// /// Bottom row of the matrix /// + [JsonProperty(PropertyName = "Row4")] [ProtoMember(4)] public float4 Row4; From 4bdd428c6673f77203b946a6702996ac380e81bd Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 20 Feb 2023 20:57:13 +0100 Subject: [PATCH 056/294] Missing package reference --- src/Math/Core/Fusee.Math.Core.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index 7dac979e0..49c26d3a1 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -12,6 +12,7 @@ + From a5b9fce16cadcf87c38a36798b4c114d7aaf0158 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Feb 2023 02:21:02 +0000 Subject: [PATCH 057/294] Bump Microsoft.NET.Test.Sdk from 17.4.0 to 17.5.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.4.0 to 17.5.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.4.0...v17.5.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Desktop/Fusee.Tests.AssetStorage.Desktop.csproj | 2 +- src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj | 2 +- src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj | 2 +- src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj | 2 +- src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 2 +- src/Tests/Xene/Fusee.Tests.Xene.csproj | 2 +- src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj | 2 +- .../Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj b/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj index 54f3ed031..eec8b48bd 100644 --- a/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj +++ b/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj @@ -24,7 +24,7 @@ - + all diff --git a/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj b/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj index 9e7a11d22..ab8275f5a 100644 --- a/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj +++ b/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj index 509e269cd..14bfac019 100644 --- a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj +++ b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj b/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj index f7dda8ecf..cbe6aafdd 100644 --- a/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj +++ b/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj @@ -8,7 +8,7 @@ - + all diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index 04c06af7f..15dd79ba9 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -8,7 +8,7 @@ - + all diff --git a/src/Tests/Xene/Fusee.Tests.Xene.csproj b/src/Tests/Xene/Fusee.Tests.Xene.csproj index e1ce294d6..c98731b4b 100644 --- a/src/Tests/Xene/Fusee.Tests.Xene.csproj +++ b/src/Tests/Xene/Fusee.Tests.Xene.csproj @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj b/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj index f2e6fab8c..5ac3580d5 100644 --- a/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj +++ b/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj b/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj index fb1e3c024..559018ce8 100644 --- a/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj +++ b/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj @@ -6,7 +6,7 @@ - + all From 1e2c110b9c06cb3e0f2ae3ca4bd888e23fa9b901 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 10:22:00 +0100 Subject: [PATCH 058/294] Split picking for lines and lineadjacency --- Examples/Complete/Picking/Core/Picking.cs | 2 +- src/Engine/Core/ScenePicker.cs | 90 +++++------------------ 2 files changed, 19 insertions(+), 73 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index dd0c44ed1..7e7307b88 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -61,7 +61,7 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene, RC, RC.CurrentRenderState.CullMode); + _scenePicker = new ScenePicker(_scene, RC.CurrentRenderState.CullMode); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 7bb84fccf..d3021305b 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -70,13 +70,6 @@ public class ScenePicker : Viserator - /// Current mouse position (in clip space) - /// - public int PickPosClip { get; set; } - } #region State /// @@ -90,7 +83,7 @@ public class PickerState : VisitorState private readonly CollapsingStateStack _cullMode = new(); private readonly CollapsingStateStack _shaderFX = new(); - public float2 PickPosClip { get; set; } + public static float2 PickPosClip { get; set; } /// /// The registered model. @@ -173,23 +166,17 @@ public PickerState() #endregion - // deferred renderer for RGB FBO picking - private SceneRendererForward _sceneRenderer; - private WritableTexture _writableTexture; - private Transform _cameraPosition = new(); - private RenderContext _rc; - /// /// The constructor to initialize a new ScenePicker. /// /// + /// /// The to pick from. - public ScenePicker(SceneContainer scene, RenderContext rc = null, Cull cullMode = Cull.None, IEnumerable customPickModule = null) + public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable? customPickModule = null) : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; State.CullMode = cullMode; - _rc = rc; } /// @@ -219,7 +206,7 @@ public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasH _canvasHeight = canvasHeight; PickPosClip = pickPos; - State.PickPosClip = pickPos; + PickerState.PickPosClip = pickPos; SetState(); var res = Viserate().ToList(); @@ -569,68 +556,27 @@ public void HandleMesh(Mesh mesh) if (State != null) PickTriangleGeometry(mesh, State.Projection, State.View); break; - // everything else should be pickable with an color coded FBO at mouse position + case PrimitiveType.Lines: + PickLineGeometry(mesh); + break; + case PrimitiveType.LineAdjacency: + PickLineAdjacencyGeometry(mesh); + break; // point cloud will be picked via PointCloudPicker Module case PrimitiveType.Points: - Diagnostics.Warn($"Unknown primitive type {mesh.MeshType}, picking not possible!"); + Diagnostics.Warn($"Picking of points not possible! Use the PointCloudPicker module!"); break; default: - // check if we have a geometry shader --> use FBO picking - if (State == null) return; - if (State.ShaderEffect.GeometryShaderSrc == null) return; - // non-triangle picking is only possible with camera - if (CurrentCamera == null) return; - - - var cam = new Camera(CurrentCamera.ProjectionMethod, 1, 1000, CurrentCamera.Fov); - - // if scene renderer is empty, generate new with camera and custom shader stuff - // attach current scene node to shader - var container = new SceneContainer - { - Children = new List - { - new SceneNode - { - Components = new List - { - _cameraPosition, - cam - } - } - } - }; - var w = _canvasWidth; - var h = _canvasHeight; - _writableTexture ??= WritableTexture.CreateAlbedoTex(w, h, new ImagePixelFormat(ColorFormat.Intensity)); - _cameraPosition.Matrix = State.View.Invert(); - cam.RenderTexture = _writableTexture; - cam.FrustumCullingOn = false; - container.Children.Insert(1, CurrentNode); - - _sceneRenderer = new SceneRendererForward(container); - _sceneRenderer.Render(_rc); - - // convert mouse from ClipSpace back to world coordinates - // reverse pickPosClip calculation - // var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); - var mouseModelPos = PickPosClip - new float2(-1, 1); - mouseModelPos = new float2(mouseModelPos.x / (2.0f / w), mouseModelPos.y / (-2.0f / h)); - - // read pixel value at mouse coordinates/position - var pixels = _rc.ReadPixels((int)mouseModelPos.x, (int)mouseModelPos.y, new ImagePixelFormat(ColorFormat.Intensity), 1, 1); - - if (pixels[0] != 0) - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model - }); + Diagnostics.Warn($"Picking failed, unknown primitive type {mesh.MeshType}. Use PickComponent.CustomPickMethod!"); break; } } + private void PickLineAdjacencyGeometry(Mesh mesh) + { + + } + private void PickLineGeometry(Mesh mesh) { var mvp = State.Projection * State.View * State.Model; @@ -657,7 +603,7 @@ private void PickLineGeometry(Mesh mesh) var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = State.PickPosClip; + var pt0 = PickerState.PickPosClip; // Line Eq = ax + by + c = 0 // A = (y1 - y2) From 19b78a3957c316aa78ee265442c9eb3d9f8dd539 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 14:28:13 +0100 Subject: [PATCH 059/294] Rework line picking, implement LineAdjacency picking --- Examples/Complete/Picking/Core/Picking.cs | 55 ++++++- .../Core/PointCloudPotree2Core.cs | 8 +- src/Engine/Core/ScenePicker.cs | 141 +++++++++++++++--- .../Core/Scene/PointCloudPickerModule.cs | 2 +- 4 files changed, 177 insertions(+), 29 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 7e7307b88..8f94ebe52 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -3,6 +3,7 @@ using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Effects; +using Fusee.Engine.Core.Primitives; using Fusee.Engine.Core.Scene; using Fusee.Engine.Gui; using Fusee.Math.Core; @@ -12,6 +13,49 @@ namespace Fusee.Examples.Picking.Core { + public class LineCircle : Mesh + { + public LineCircle(int segments) + { + var verts = new List(); + var triangles = new List(); + var angleStep = 2 * M.Pi / segments; + for (uint i = 0; i < segments; i++) + { + var vert = new float3 + { + x = 0.5f * (float)System.Math.Cos(i * angleStep), + y = 0.5f * (float)System.Math.Sin(i * angleStep) + }; + verts.Add(vert); + } + + for (uint i = 0; i < verts.Count; i++) + { + if (i == 0) + triangles.Add((uint)verts.Count - 1); + else + triangles.Add(i - 1); + + triangles.Add(i); + + if (i + 1 < verts.Count) + triangles.Add(i + 1); + else + triangles.Add(0); + + if (i + 2 < verts.Count) + triangles.Add(i + 2); + else + triangles.Add((uint)(i + 2 - verts.Count)); + } + + Vertices = new MeshAttributes(verts); + Triangles = new MeshAttributes(triangles); + MeshType = Fusee.Engine.Common.PrimitiveType.LineAdjacency; + } + } + [FuseeApplication(Name = "FUSEE Picking Example", Description = "How to use the Scene Picker.")] public class Picking : RenderCanvas { @@ -47,13 +91,20 @@ private async Task Load() // Create the robot model _scene = CreateScene(); + _scene.Children.Add(new SceneNode { Components = new List { - new Transform(), - MakeEffect.LineEffect(5, new float4(1,0,0,0)), + new Transform + { + //Translation = new float3(1,-2,1), + //Scale = float3.One * 100, + ////Rotation = new float3(0, M.PiOver2, 0) + }, + MakeEffect.LineEffect(5, new float4(1,0,0,1)), new Mesh(new uint[] {0, 1}, new float3[] {new float3(0,150,0), new float3(5,170,5) }) { + Name = "Line", MeshType = PrimitiveType.Lines } } diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index df77dcf12..db2c1a83f 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -64,7 +64,7 @@ public void OnLoadNewFile(object sender, EventArgs e) return; _potreeData = new PotreeData(path); - _potreeReader = new Potree2Reader(ref _potreeData); + _potreeReader = new Potree2Reader(); _pointCloud = (PointCloudComponent)_potreeReader.GetPointCloudComponent(RenderMode.DynamicMesh); _pointCloud.PointCloudImp.MinProjSizeModifier = PointRenderingParams.Instance.ProjectedSizeModifier; @@ -75,7 +75,7 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloudNode.Components[3] = _pointCloud; // re-generate picker and octree - _picker = new ScenePicker(_scene, _rc, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); @@ -84,7 +84,7 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { _potreeData = new PotreeData(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); - _potreeReader = new Potree2Reader(ref _potreeData); + _potreeReader = new Potree2Reader(); _rc = rc; } @@ -173,7 +173,7 @@ public void Init() // new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree, null) //}); - _picker = new ScenePicker(_scene, _rc, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index d3021305b..0bca543fd 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -573,11 +573,6 @@ public void HandleMesh(Mesh mesh) } private void PickLineAdjacencyGeometry(Mesh mesh) - { - - } - - private void PickLineGeometry(Mesh mesh) { var mvp = State.Projection * State.View * State.Model; var matOfNode = CurrentNode.GetComponent(); @@ -596,28 +591,68 @@ private void PickLineGeometry(Mesh mesh) return; } - for (var i = 0; i < mesh.Triangles.Length; i += 2) - { - var viewportHeight = CurrentCamera.Viewport.w; - var thickness = (thicknessFromShader / viewportHeight); + var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); + var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); + var aspect = viewportHeight / viewportWidth; + var line_width = M.Max(1.0f, thicknessFromShader); - var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; - var pt2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt0 = PickerState.PickPosClip; + for (var i = 1; i < mesh.Triangles.Length - 1; i += 2) + { + // recreate the mesh of the geometry shader, pt in triangle check for three vertices + // not very perfomant, however this is currently the only way to yield the resulting vertices. + // The GLSL.TransformFeedback is cluttered with problems and GLSL performance warnings + var ndc0 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i - 1]]); + var ndc1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]); + var ndc2 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]); + var ndc3 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 2]]); + + //direction of the three segments (previous, current, next) */ + var line_vector0 = ndc1 - ndc0; + var line_vector1 = ndc2 - ndc1; + var line_vector2 = ndc3 - ndc2; + var dir0 = new float2(line_vector0.x, line_vector0.y * aspect).Normalize(); + var dir1 = new float2(line_vector1.x, line_vector1.y * aspect).Normalize(); + var dir2 = new float2(line_vector2.x, line_vector2.y * aspect).Normalize(); + + //normals of the three segments (previous, current, next) + var n0 = new float2(-dir0.y, dir0.x); + var n1 = new float2(-dir1.y, dir1.x); + var n2 = new float2(-dir2.y, dir2.x); + + // determine miter lines by averaging the normals of the 2 segments + var miter_a = (n0 + n1).Normalize();// miter at start of current segment + var miter_b = (n1 + n2).Normalize();// miter at end of current segment + + // determine the length of the miter by projecting it onto normal and then inverse it + float an1 = float2.Dot(miter_a, n1); + float bn1 = float2.Dot(miter_b, n2); + if (an1 == 0) an1 = 1; + if (bn1 == 0) bn1 = 1; + + float length_a = line_width / an1; + if (float2.Dot(dir0, dir1) < -0.1) + { + miter_a = n1; + length_a = line_width; + } - // Line Eq = ax + by + c = 0 - // A = (y1 - y2) - // B = (x2 - x1) - // C = (x1 * y2 - x2 * y1) + float length_b = line_width / bn1; + if (float2.Dot(dir1, dir2) < -0.1) + { + miter_b = n1; + length_b = line_width; + } - // dist(line, pt) = |Ax + By + C| / A² + B² - var a = pt1.y - pt2.y; - var b = pt2.x - pt1.x; - var c = (pt1.x * pt2.y) - (pt2.x * pt1.y); + miter_a = new float2(length_a / viewportWidth, length_a / viewportHeight) * miter_a; + miter_b = new float2(length_b / viewportWidth, length_b / viewportHeight) * miter_b; - var d = MathF.Abs((a * pt0.x) + (b * pt0.y) + c) / ((a * a) + (b * b)); + var vert0 = new float3(ndc1.x + miter_a.x, ndc1.y + miter_a.y, ndc1.z); + var vert1 = new float3(ndc1.x - miter_a.x, ndc1.y - miter_a.y, ndc1.z); + var vert2 = new float3(ndc2.x + miter_b.x, ndc2.y + miter_b.y, ndc2.z); + var vert3 = new float3(ndc2.x - miter_b.x, ndc2.y - miter_b.y, ndc2.z); - if (d <= thickness) + if (float2.PointInTriangle(vert0.xy, vert1.xy, vert2.xy, PickPosClip, out _, out _) || + float2.PointInTriangle(vert2.xy, vert1.xy, vert3.xy, PickPosClip, out _, out _)) { YieldItem(new PickResult { @@ -632,7 +667,69 @@ private void PickLineGeometry(Mesh mesh) } } + private void PickLineGeometry(Mesh mesh) + { + var mvp = State.Projection * State.View * State.Model; + var matOfNode = CurrentNode.GetComponent(); + if (matOfNode == null) + { + Diagnostics.Debug("No shader effect for line renderer found!"); + return; + } + var thicknessFromShader = matOfNode.GetFxParam("Thickness"); + if (mesh.Triangles == null) return; + if (mesh.Vertices == null) return; + if (CurrentCamera == null) + { + Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); + return; + } + + for (var i = 0; i < mesh.Triangles.Length; i += 2) + { + var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); + var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); + var w = thicknessFromShader / viewportWidth; // transform thickness from pixel to NDC + + + // see: https://math.stackexchange.com/a/3633025 + // for calculation + var pt0 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; + var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; + var pt = PickerState.PickPosClip; + + var nom = (pt.x - pt0.x) * (pt1.x - pt0.x) + (pt.y - pt0.y) * (pt1.y - pt0.y); + var denom = MathF.Pow((pt1.x - pt.x), 2) + MathF.Pow((pt1.y - pt0.y), 2); + var t = nom / denom; + + // point is somewhere on the line + if(t >= 0 && t <= 1) + { + var dSqrd = MathF.Pow(pt.x - pt0.x - t * (pt1.x - pt0.x), 2) + MathF.Pow(pt.y - pt0.y - t * (pt1.y - pt0.y), 2); + + if(dSqrd < 0.25 * (w * w)) { + + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), + View = State.View, + Projection = State.Projection + }); + } + } + } + } + + /// + /// Pick triangle geometry via ray cast. + /// + /// + /// + /// private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 viewMatrix) { if (mesh == null) return; diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index b22c7eab8..8790dcf01 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -39,7 +39,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (!pointCloud.Active) return; - var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); + var ray = new RayD(new double2(PickerState.PickPosClip.x, PickerState.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); var tmpList = new List(); var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); if (allHitBoxes != null && allHitBoxes.Any()) From 0d210e3c30bef5d6cc449ef46b12dca32d5523ef Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 14:31:09 +0100 Subject: [PATCH 060/294] Reset picking example to default --- Examples/Complete/Picking/Core/Picking.cs | 104 ++++------------------ 1 file changed, 16 insertions(+), 88 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 8f94ebe52..1ebf5ee80 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -3,7 +3,6 @@ using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Effects; -using Fusee.Engine.Core.Primitives; using Fusee.Engine.Core.Scene; using Fusee.Engine.Gui; using Fusee.Math.Core; @@ -13,49 +12,6 @@ namespace Fusee.Examples.Picking.Core { - public class LineCircle : Mesh - { - public LineCircle(int segments) - { - var verts = new List(); - var triangles = new List(); - var angleStep = 2 * M.Pi / segments; - for (uint i = 0; i < segments; i++) - { - var vert = new float3 - { - x = 0.5f * (float)System.Math.Cos(i * angleStep), - y = 0.5f * (float)System.Math.Sin(i * angleStep) - }; - verts.Add(vert); - } - - for (uint i = 0; i < verts.Count; i++) - { - if (i == 0) - triangles.Add((uint)verts.Count - 1); - else - triangles.Add(i - 1); - - triangles.Add(i); - - if (i + 1 < verts.Count) - triangles.Add(i + 1); - else - triangles.Add(0); - - if (i + 2 < verts.Count) - triangles.Add(i + 2); - else - triangles.Add((uint)(i + 2 - verts.Count)); - } - - Vertices = new MeshAttributes(verts); - Triangles = new MeshAttributes(triangles); - MeshType = Fusee.Engine.Common.PrimitiveType.LineAdjacency; - } - } - [FuseeApplication(Name = "FUSEE Picking Example", Description = "How to use the Scene Picker.")] public class Picking : RenderCanvas { @@ -68,7 +24,6 @@ public class Picking : RenderCanvas private SceneContainer _scene; private Transform _camPivotTransform; private SceneRendererForward _sceneRenderer; - private SceneRendererDeferred _pickRenderer; private ScenePicker _scenePicker; private bool _keys; @@ -91,28 +46,9 @@ private async Task Load() // Create the robot model _scene = CreateScene(); - - _scene.Children.Add(new SceneNode - { - Components = new List { - new Transform - { - //Translation = new float3(1,-2,1), - //Scale = float3.One * 100, - ////Rotation = new float3(0, M.PiOver2, 0) - }, - MakeEffect.LineEffect(5, new float4(1,0,0,1)), - new Mesh(new uint[] {0, 1}, new float3[] {new float3(0,150,0), new float3(5,170,5) }) - { - Name = "Line", - MeshType = PrimitiveType.Lines - } - } - }); - // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene, RC.CurrentRenderState.CullMode); + _scenePicker = new ScenePicker(_scene); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler @@ -180,36 +116,28 @@ public override void Update() // RenderAFrame is called once a frame public override void RenderAFrame() { - // RC.EnableStencil(); - // RC.SetStencilMask(0xFF); - // _sceneRenderer.Render(RC); - //RC.DisableStencil(); - - - // Picking + //Picking if (_pick) { float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - var newPick = _scenePicker.Pick(pickPosClip, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z) + PickResult newPick = _scenePicker.Pick(pickPosClip, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); - - if (newPick != null) - Diagnostics.Debug(newPick.Node.Name); + Diagnostics.Debug(newPick); if (newPick?.Node != _currentPick?.Node) { - if (_currentPick != null && _currentPick is MeshPickResult mpr) + if (_currentPick != null) { - var ef = mpr.Node.GetComponent(); + var ef = _currentPick.Node.GetComponent(); ef.SurfaceInput.Albedo = _oldColor; } - if (newPick != null && newPick is MeshPickResult newMpr) + if (newPick != null) { - var ef = newMpr.Node.GetComponent(); + var ef = newPick.Node.GetComponent(); _oldColor = ef.SurfaceInput.Albedo; ef.SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; } @@ -220,16 +148,16 @@ public override void RenderAFrame() _pick = false; } - //_guiRenderer.Render(RC); + _guiRenderer.Render(RC); // Constantly check for interactive objects. - //if (!Input.Mouse.Desc.Contains("Android")) - // _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); - // - //if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) - //{ - // _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); - //} + if (!Input.Mouse.Desc.Contains("Android")) + _sih.CheckForInteractiveObjects(Input.Mouse.Position, Width, Height); + + if (Input.Touch != null && Input.Touch.GetTouchActive(TouchPoints.Touchpoint_0) && !Input.Touch.TwoPoint) + { + _sih.CheckForInteractiveObjects(Input.Touch.GetPosition(TouchPoints.Touchpoint_0), Width, Height); + } // Swap buffers: Show the contents of the back buffer (containing the currently rendered frame) on the front buffer. Present(); From f04f33dd6ec302ac2bf6591be22df7ef84f9987f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 15:17:18 +0100 Subject: [PATCH 061/294] Rework line picking --- src/Engine/Core/ScenePicker.cs | 80 +++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 0bca543fd..af23c6fca 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -686,41 +686,71 @@ private void PickLineGeometry(Mesh mesh) return; } + var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); + var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); + var aspect = viewportHeight / viewportWidth; + var line_width = M.Max(1.0f, thicknessFromShader); + for (var i = 0; i < mesh.Triangles.Length; i += 2) { - var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); - var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); - var w = thicknessFromShader / viewportWidth; // transform thickness from pixel to NDC - - - // see: https://math.stackexchange.com/a/3633025 - // for calculation var pt0 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; var pt = PickerState.PickPosClip; - var nom = (pt.x - pt0.x) * (pt1.x - pt0.x) + (pt.y - pt0.y) * (pt1.y - pt0.y); - var denom = MathF.Pow((pt1.x - pt.x), 2) + MathF.Pow((pt1.y - pt0.y), 2); - var t = nom / denom; + var lineVector = pt1 - pt0; + var viewport_line_vector = lineVector * new float2(viewportWidth, viewportHeight); + var dir = new float2(lineVector.x, lineVector.y * aspect).Normalize(); - // point is somewhere on the line - if(t >= 0 && t <= 1) - { - var dSqrd = MathF.Pow(pt.x - pt0.x - t * (pt1.x - pt0.x), 2) + MathF.Pow(pt.y - pt0.y - t * (pt1.y - pt0.y), 2); + var lineLength = viewport_line_vector.Length; - if(dSqrd < 0.25 * (w * w)) { + var normal = new float2(-dir.y, dir.x); + var normal_a = new float2(line_width / viewportWidth, line_width / viewportHeight) * normal; + var normal_b = new float2(line_width / viewportWidth, line_width / viewportHeight) * normal; - YieldItem(new PickResult - { - Mesh = mesh, - Node = CurrentNode, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), - View = State.View, - Projection = State.Projection - }); - } + var vert0 = pt0 + normal_a; + var vert1 = pt0 - normal_a; + var vert2 = pt1 + normal_b; + var vert3 = pt1 - normal_b; + + if (float2.PointInTriangle(vert0.xy, vert1.xy, vert2.xy, PickPosClip, out _, out _) || + float2.PointInTriangle(vert2.xy, vert1.xy, vert3.xy, PickPosClip, out _, out _)) + { + YieldItem(new PickResult + { + Mesh = mesh, + Node = CurrentNode, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), + View = State.View, + Projection = State.Projection + }); } + + + //// see: https://math.stackexchange.com/a/3633025 + //// for calculation, does not work :( + //var nom = (pt.x - pt0.x) * (pt1.x - pt0.x) + (pt.y - pt0.y) * (pt1.y - pt0.y); + //var denom = MathF.Pow((pt1.x - pt.x), 2) + MathF.Pow((pt1.y - pt0.y), 2); + //var t = nom / denom; + + //// point is somewhere on the line + //if(t >= 0 && t <= 1) + //{ + // var dSqrd = MathF.Pow(pt.x - pt0.x - t * (pt1.x - pt0.x), 2) + MathF.Pow(pt.y - pt0.y - t * (pt1.y - pt0.y), 2); + + // if(dSqrd < 0.25 * (w * w)) { + + // YieldItem(new PickResult + // { + // Mesh = mesh, + // Node = CurrentNode, + // Model = State.Model, + // ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), + // View = State.View, + // Projection = State.Projection + // }); + // } + //} } } From 4c99b8f1f80dcf1f50f233e8f63652889a72db72 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 15:18:05 +0100 Subject: [PATCH 062/294] Auto stash before merge of "feature/670-re-visit-scenepicker" and "origin/feature/670-re-visit-scenepicker" --- src/Engine/Core/ScenePicker.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index af23c6fca..92cdf1fc2 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -166,6 +166,8 @@ public PickerState() #endregion + private SceneContainer sc; + /// /// The constructor to initialize a new ScenePicker. /// @@ -177,6 +179,7 @@ public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable< { IgnoreInactiveComponents = true; State.CullMode = cullMode; + sc = scene; } /// From 4c6174570aee685c688fdf2a2f1959d062122ea1 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 22 Feb 2023 16:28:54 +0100 Subject: [PATCH 063/294] Use camera calculated viewport size --- src/Engine/Core/Scene/SceneNode.cs | 10 +++++----- src/Engine/Core/ScenePicker.cs | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Engine/Core/Scene/SceneNode.cs b/src/Engine/Core/Scene/SceneNode.cs index 907ec5c34..bdca5ab70 100644 --- a/src/Engine/Core/Scene/SceneNode.cs +++ b/src/Engine/Core/Scene/SceneNode.cs @@ -39,16 +39,16 @@ public class SceneNode : Xene.INode /// /// The components this node is made of. - /// + /// public List Components; /// - /// This SceneNodeContainer's snc. + /// This SceneNodeContainer's snc. /// public SceneNode Parent; /// - /// Creates a new instance of this SceneNode class. + /// Creates a new instance of this SceneNode class. /// public SceneNode() { @@ -57,8 +57,8 @@ public SceneNode() } /// - /// Possible children. - /// + /// Possible children. + /// public ChildList Children { get => _children; diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 92cdf1fc2..cd5657fd0 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -278,10 +278,15 @@ public void UpdateCamera(Camera cam) } State.View = view.Invert(); + + var sizeInPx = CurrentCamera.RenderTexture == null ? + CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight) + : CurrentCamera.GetViewportInPx(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height); + // TODO(mr): TEST Renderlayer State.Projection = CurrentCamera.RenderTexture != null ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) - : CurrentCamera.GetProjectionMat(_canvasWidth, _canvasHeight, out var _); + : CurrentCamera.GetProjectionMat((int)sizeInPx.z, (int)sizeInPx.w, out var _); } @@ -594,8 +599,9 @@ private void PickLineAdjacencyGeometry(Mesh mesh) return; } - var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); - var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); + var size = CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight); + var viewportHeight = size.w; + var viewportWidth = size.z; var aspect = viewportHeight / viewportWidth; var line_width = M.Max(1.0f, thicknessFromShader); @@ -688,9 +694,9 @@ private void PickLineGeometry(Mesh mesh) Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); return; } - - var viewportHeight = CurrentCamera.Viewport.w * (_canvasHeight / 100.0f); - var viewportWidth = CurrentCamera.Viewport.z * (_canvasWidth / 100.0f); + var size = CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight); + var viewportHeight = size.w; + var viewportWidth = size.z; var aspect = viewportHeight / viewportWidth; var line_width = M.Max(1.0f, thicknessFromShader); @@ -729,7 +735,6 @@ private void PickLineGeometry(Mesh mesh) }); } - //// see: https://math.stackexchange.com/a/3633025 //// for calculation, does not work :( //var nom = (pt.x - pt0.x) * (pt1.x - pt0.x) + (pt.y - pt0.y) * (pt1.y - pt0.y); From 9cc5f0e01906afda9bf15a8d5bbbb2577801966d Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 23 Feb 2023 13:20:53 +0100 Subject: [PATCH 064/294] Fixed exception when Transform() is missing --- src/Engine/Core/ScenePicker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index cd5657fd0..11fd0c490 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -729,7 +729,7 @@ private void PickLineGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), + ClipPos = float4x4.TransformPerspective(State.Projection * State.View, State.Model.Translation()), // prevents exception when node has no Transform View = State.View, Projection = State.Projection }); From ea070d408e648ff51ffdc0f86c6e9ec19e6fda6e Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 27 Feb 2023 08:58:16 +0100 Subject: [PATCH 065/294] AABBf --- src/Math/Core/AABBf.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Math/Core/AABBf.cs b/src/Math/Core/AABBf.cs index 764a1ac45..1e6915dd1 100644 --- a/src/Math/Core/AABBf.cs +++ b/src/Math/Core/AABBf.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents an axis aligned bounding box. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct AABBf @@ -14,11 +16,13 @@ public struct AABBf /// /// The minimum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty] [ProtoMember(1)] public float3 min; /// /// The maximum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty] [ProtoMember(2)] public float3 max; /// @@ -299,7 +303,7 @@ public static bool Intersects(AABBf aabb, float3 point) /// public override bool Equals(object? obj) { - if (obj?.GetType() != typeof(AABBf)) throw new ArgumentException($"{obj} is not of Type 'AABBf'."); + if (obj?.GetType() != typeof(AABBf)) return false; var other = (AABBf)obj; return max.Equals(other.max) && min.Equals(other.min); From 456aa0aac6a26b54df0f7e53e8176049a8e699c8 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 27 Feb 2023 08:58:16 +0100 Subject: [PATCH 066/294] AABBf --- src/Math/Core/AABBf.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Math/Core/AABBf.cs b/src/Math/Core/AABBf.cs index 764a1ac45..1e6915dd1 100644 --- a/src/Math/Core/AABBf.cs +++ b/src/Math/Core/AABBf.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents an axis aligned bounding box. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct AABBf @@ -14,11 +16,13 @@ public struct AABBf /// /// The minimum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty] [ProtoMember(1)] public float3 min; /// /// The maximum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty] [ProtoMember(2)] public float3 max; /// @@ -299,7 +303,7 @@ public static bool Intersects(AABBf aabb, float3 point) /// public override bool Equals(object? obj) { - if (obj?.GetType() != typeof(AABBf)) throw new ArgumentException($"{obj} is not of Type 'AABBf'."); + if (obj?.GetType() != typeof(AABBf)) return false; var other = (AABBf)obj; return max.Equals(other.max) && min.Equals(other.min); From 0257e4e0b1ccef7e94ceac8b8f8b894063ffdee7 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 27 Feb 2023 10:59:17 +0100 Subject: [PATCH 067/294] Camera pre-pass visitor --- Examples/Complete/Picking/Core/Picking.cs | 4 +- src/Engine/Core/ScenePicker.cs | 98 ++++++++++------------- src/Engine/GUI/SceneInteractionHandler.cs | 4 +- src/Math/Core/float4x4.cs | 5 ++ src/PointCloud/Common/IPointReader.cs | 7 +- 5 files changed, 51 insertions(+), 67 deletions(-) diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 1ebf5ee80..13e603cbc 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -121,9 +121,7 @@ public override void RenderAFrame() //Picking if (_pick) { - float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - - PickResult newPick = _scenePicker.Pick(pickPosClip, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(pr => pr.ClipPos.z) + PickResult newPick = _scenePicker.Pick(Input.Mouse.Position, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); Diagnostics.Debug(newPick); diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index cd5657fd0..1b4d41918 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -63,6 +63,8 @@ public class MeshPickResult : PickResult public class ScenePicker : Viserator { private CanvasTransform? _ctc; + private PrePassVisitor _prePassVisitor; + private SceneContainer _scene; private bool isCtcInitialized = false; private MinMaxRect _parentRect; @@ -70,7 +72,6 @@ public class ScenePicker : Viserator /// The picker state upon scene traversal. @@ -162,12 +163,10 @@ public PickerState() public float4x4 InvProjection => State.Projection.Invert(); - public Camera? CurrentCamera { get; private set; } + private Camera CurrentCamera = default; #endregion - private SceneContainer sc; - /// /// The constructor to initialize a new ScenePicker. /// @@ -179,7 +178,8 @@ public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable< { IgnoreInactiveComponents = true; State.CullMode = cullMode; - sc = scene; + _scene = scene; + _prePassVisitor = new PrePassVisitor(); } /// @@ -198,19 +198,53 @@ protected override void InitState() /// /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// - /// The pick position. + /// The pick position in canvas coordinates (e. g. 1280x720). /// The width of the current canvas, gets overwrite if a is bound /// The height of the current canvas, gets overwrite if a is bound /// public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { - _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; - PickPosClip = pickPos; + _prePassVisitor.PrePassTraverse(_scene); + var cams = _prePassVisitor.CameraPrepassResults; + + if (cams.Count == 0) + { + Diagnostics.Warn("No camera in scene. Unable to pick."); + return Array.Empty(); + } + + CameraResult pickCam = default; + Rectangle pickCamRect = new(); + + foreach (var camRes in cams) + { + Rectangle camRect = new() + { + Left = (int)(camRes.Camera.Viewport.x * canvasWidth / 100), + Top = (int)(camRes.Camera.Viewport.y * canvasHeight / 100) + }; + camRect.Right = ((int)(camRes.Camera.Viewport.z * canvasWidth) / 100) + camRect.Left; + camRect.Bottom = ((int)(camRes.Camera.Viewport.w * canvasHeight) / 100) + camRect.Top; + + if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) + continue; + + if (pickCam == default || camRes.Camera.Layer > pickCam.Camera.Layer) + { + pickCam = camRes; + pickCamRect = camRect; + } + } + + // Calculate pickPosClip + PickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); PickerState.PickPosClip = pickPos; + CurrentCamera = pickCam.Camera; + SetState(); var res = Viserate().ToList(); res.AddRange(CheckVisitorModuleResults()); @@ -242,54 +276,6 @@ private void SetState() #region Visitors - /// - /// Set the current camera, update View and Projection matrices - /// - /// - [VisitMethod] - public void UpdateCamera(Camera cam) - { - if (!cam.Active) return; - - CurrentCamera = cam; - - var view = State.Model; - var scale = float4x4.GetScale(State.View); - - if (scale.x != 1) - { - view.M11 /= scale.x; - view.M21 /= scale.x; - view.M31 /= scale.x; - } - - if (scale.y != 1) - { - view.M12 /= scale.y; - view.M22 /= scale.y; - view.M32 /= scale.y; - } - - if (scale.z != 1) - { - view.M13 /= scale.z; - view.M23 /= scale.z; - view.M33 /= scale.z; - } - - State.View = view.Invert(); - - var sizeInPx = CurrentCamera.RenderTexture == null ? - CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight) - : CurrentCamera.GetViewportInPx(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height); - - // TODO(mr): TEST Renderlayer - State.Projection = CurrentCamera.RenderTexture != null - ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) - : CurrentCamera.GetProjectionMat((int)sizeInPx.z, (int)sizeInPx.w, out var _); - - } - /// /// Sets the state of the model matrices and UiRects. /// diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index e7a150d96..aae9bc88f 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -64,9 +64,9 @@ private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IListCanvas height - needed to determine the mouse position in clip space. public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int canvasHeight) { - var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); + //var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); - var pickResults = _scenePicker.Pick(pickPosClip, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); + var pickResults = _scenePicker.Pick(mousePos, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); var pickResNodes = pickResults.ConvertAll(x => x.Node); var firstPickRes = pickResults.FirstOrDefault(); diff --git a/src/Math/Core/float4x4.cs b/src/Math/Core/float4x4.cs index 236b752a8..45fc1d68f 100644 --- a/src/Math/Core/float4x4.cs +++ b/src/Math/Core/float4x4.cs @@ -2140,6 +2140,11 @@ public static float4x4 RotationDecomposition(float4x4 mat) var scalevector = GetScale(mat); var rotationMtx = float4x4.Identity; + if(scalevector.x <= 0 || scalevector.y <= 0 || scalevector.z <= 0) + { + throw new ArgumentException("Scale vector <= 0!"); + } + rotationMtx.M11 = mat.M11 / scalevector.x; rotationMtx.M21 = mat.M21 / scalevector.x; rotationMtx.M31 = mat.M31 / scalevector.x; diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 985a6a7b0..8a33d4fee 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -1,8 +1,4 @@ -using CommunityToolkit.HighPerformance.Buffers; -using System; -using System.Buffers; - -namespace Fusee.PointCloud.Common +namespace Fusee.PointCloud.Common { /// /// Implement this into any Point Cloud Reader. @@ -12,7 +8,6 @@ public interface IPointReader /// /// Returns a renderable point cloud component. /// - /// Path to the file. /// Determines which is used to display the returned point cloud."/> public IPointCloud GetPointCloudComponent(RenderMode renderMode); From 963f683f2f57bab45f46e1b0689fececa220658b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 27 Feb 2023 13:27:55 +0100 Subject: [PATCH 068/294] Restored ScenePicker after force push --- src/Engine/Core/ScenePicker.cs | 98 +++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 2edd53e3d..11fd0c490 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -63,8 +63,6 @@ public class MeshPickResult : PickResult public class ScenePicker : Viserator { private CanvasTransform? _ctc; - private PrePassVisitor _prePassVisitor; - private SceneContainer _scene; private bool isCtcInitialized = false; private MinMaxRect _parentRect; @@ -72,6 +70,7 @@ public class ScenePicker : Viserator /// The picker state upon scene traversal. @@ -163,10 +162,12 @@ public PickerState() public float4x4 InvProjection => State.Projection.Invert(); - private Camera CurrentCamera = default; + public Camera? CurrentCamera { get; private set; } #endregion + private SceneContainer sc; + /// /// The constructor to initialize a new ScenePicker. /// @@ -178,8 +179,7 @@ public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable< { IgnoreInactiveComponents = true; State.CullMode = cullMode; - _scene = scene; - _prePassVisitor = new PrePassVisitor(); + sc = scene; } /// @@ -198,53 +198,19 @@ protected override void InitState() /// /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// - /// The pick position in canvas coordinates (e. g. 1280x720). + /// The pick position. /// The width of the current canvas, gets overwrite if a is bound /// The height of the current canvas, gets overwrite if a is bound /// public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { + _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; - _prePassVisitor.PrePassTraverse(_scene); - var cams = _prePassVisitor.CameraPrepassResults; - - if (cams.Count == 0) - { - Diagnostics.Warn("No camera in scene. Unable to pick."); - return Array.Empty(); - } - - CameraResult pickCam = default; - Rectangle pickCamRect = new(); - - foreach (var camRes in cams) - { - Rectangle camRect = new() - { - Left = (int)(camRes.Camera.Viewport.x * canvasWidth / 100), - Top = (int)(camRes.Camera.Viewport.y * canvasHeight / 100) - }; - camRect.Right = ((int)(camRes.Camera.Viewport.z * canvasWidth) / 100) + camRect.Left; - camRect.Bottom = ((int)(camRes.Camera.Viewport.w * canvasHeight) / 100) + camRect.Top; - - if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) - continue; - - if (pickCam == default || camRes.Camera.Layer > pickCam.Camera.Layer) - { - pickCam = camRes; - pickCamRect = camRect; - } - } - - // Calculate pickPosClip - PickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); + PickPosClip = pickPos; PickerState.PickPosClip = pickPos; - CurrentCamera = pickCam.Camera; - SetState(); var res = Viserate().ToList(); res.AddRange(CheckVisitorModuleResults()); @@ -276,6 +242,54 @@ private void SetState() #region Visitors + /// + /// Set the current camera, update View and Projection matrices + /// + /// + [VisitMethod] + public void UpdateCamera(Camera cam) + { + if (!cam.Active) return; + + CurrentCamera = cam; + + var view = State.Model; + var scale = float4x4.GetScale(State.View); + + if (scale.x != 1) + { + view.M11 /= scale.x; + view.M21 /= scale.x; + view.M31 /= scale.x; + } + + if (scale.y != 1) + { + view.M12 /= scale.y; + view.M22 /= scale.y; + view.M32 /= scale.y; + } + + if (scale.z != 1) + { + view.M13 /= scale.z; + view.M23 /= scale.z; + view.M33 /= scale.z; + } + + State.View = view.Invert(); + + var sizeInPx = CurrentCamera.RenderTexture == null ? + CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight) + : CurrentCamera.GetViewportInPx(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height); + + // TODO(mr): TEST Renderlayer + State.Projection = CurrentCamera.RenderTexture != null + ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) + : CurrentCamera.GetProjectionMat((int)sizeInPx.z, (int)sizeInPx.w, out var _); + + } + /// /// Sets the state of the model matrices and UiRects. /// From 75982a99007f68b40ad432c528aac95888b10eb9 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 27 Feb 2023 14:52:24 +0100 Subject: [PATCH 069/294] Prepared PotreeReader and Writer for serialization imp --- src/PointCloud/Common/PointTypes.cs | 7 +- src/PointCloud/Potree/V2/Data/PotreePoint.cs | 12 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 16 ++ src/PointCloud/Potree/V2/Potree2Writer.cs | 183 +++++++++--------- src/PointCloud/Potree/V2/Potree2WriterBase.cs | 2 +- 5 files changed, 121 insertions(+), 99 deletions(-) diff --git a/src/PointCloud/Common/PointTypes.cs b/src/PointCloud/Common/PointTypes.cs index 384c495e8..8582dacf7 100644 --- a/src/PointCloud/Common/PointTypes.cs +++ b/src/PointCloud/Common/PointTypes.cs @@ -62,7 +62,12 @@ public enum PointType /// /// Position (double), Color (float), Classification (byte), Intensity (ushort) /// - PosD3ColF3InUsLblB + PosD3ColF3InUsLblB, + + /// + /// Everything.... + /// + Raw } /// diff --git a/src/PointCloud/Potree/V2/Data/PotreePoint.cs b/src/PointCloud/Potree/V2/Data/PotreePoint.cs index aaf890dd1..a35c76e4d 100644 --- a/src/PointCloud/Potree/V2/Data/PotreePoint.cs +++ b/src/PointCloud/Potree/V2/Data/PotreePoint.cs @@ -5,13 +5,13 @@ namespace Fusee.PointCloud.Potree.V2.Data public struct PotreePoint { public double3 Position; - //public short Intensity; - //public byte ReturnNumber; - //public byte NumberOfReturns; + public short Intensity; + public byte ReturnNumber; + public byte NumberOfReturns; public byte Classification; - //public byte ScanAngleRank; - //public byte UserData; - //public byte PointSourceId; + public byte ScanAngleRank; + public byte UserData; + public byte PointSourceId; public float3 Color; } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 47114053e..3278edd34 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -98,6 +98,22 @@ public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) return LoadNodeData(node, PointType.PosD3ColF3LblB); } + /// + /// Reads the points for a specific octant of type . + /// + /// Id of the octant. + /// + public MemoryOwner LoadNodeDataPotreePoint(OctantId id) + { + Guard.IsNotNull(PotreeData); + var node = FindNode(ref PotreeData.Hierarchy, id); + + // if node is null the hierarchy is broken and we look for an octant that isn't there... + Guard.IsNotNull(node); + + return LoadNodeData(node, PointType.Raw); + } + private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointType type) where TPoint : struct { // if the potree node is null #nullable doesn't work! diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 34c374bcc..ca00e9be8 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -1,4 +1,5 @@ using Fusee.Math.Core; +using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; @@ -261,96 +262,96 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } return (octantCount, pointsCount); } - //public void WriteRawPoints(OctantId oid, TPotreePoint[] points) where TPotreePoint : PotreePoint - //{ - // var node = FindNode(ref _potreeData.Hierarchy, oid); - - // if (points.Length != node.NumPoints) - // { - // //TODO: (throw) correct error - // throw new Exception(); - // } - - // using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - // { - // BinaryWriter binaryWriter = new BinaryWriter(writeStream); - - // for (int i = 0; i < points.Length; i++) - // { - // var point = points[i]; - - // if (offsetPosition > -1) - // { - // // TODO: Fix position conversion - // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - // //var position = Potree2Consts.YZflip * point.Position; - - // //binaryWriter.Write(position.x); - // //binaryWriter.Write(position.y); - // //binaryWriter.Write(position.z); - // } - - // if (offsetIntensity > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Intensity); - // } - - // if (offsetReturnNumber > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ReturnNumber); - // } - - // if (offsetNumberOfReturns > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.NumberOfReturns); - // } - - // if (offsetClassification > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Classification); - // } - - // if (offsetScanAngleRank > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ScanAngleRank); - // } - - // if (offsetUserData > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.UserData); - // } - - // if (offsetPointSourceId > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.PointSourceId); - // } - - // if (offsetColor > -1) - // { - // // TODO: Fix color conversion - // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - // //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); - // //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); - // //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); - - // //binaryWriter.Write(r); - // //binaryWriter.Write(g); - // //binaryWriter.Write(b); - // } - // } - - // binaryWriter.Close(); - // binaryWriter.Dispose(); - // } - //} + public void WriteRawPoints(OctantId oid, PotreePoint[] points) + { + var node = FindNode(ref _potreeData.Hierarchy, oid); + + if (points.Length != node.NumPoints) + { + //TODO: (throw) correct error + throw new Exception(); + } + + using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + { + BinaryWriter binaryWriter = new BinaryWriter(writeStream); + + for (int i = 0; i < points.Length; i++) + { + var point = points[i]; + + if (offsetPosition > -1) + { + // TODO: Fix position conversion + //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + + //var position = Potree2Consts.YZflip * point.Position; + + //binaryWriter.Write(position.x); + //binaryWriter.Write(position.y); + //binaryWriter.Write(position.z); + } + + if (offsetIntensity > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.Intensity); + } + + if (offsetReturnNumber > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.ReturnNumber); + } + + if (offsetNumberOfReturns > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.NumberOfReturns); + } + + if (offsetClassification > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.Classification); + } + + if (offsetScanAngleRank > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.ScanAngleRank); + } + + if (offsetUserData > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.UserData); + } + + if (offsetPointSourceId > -1) + { + binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + binaryWriter.Write(point.PointSourceId); + } + + if (offsetColor > -1) + { + // TODO: Fix color conversion + //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + + //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); + //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); + //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); + + //binaryWriter.Write(r); + //binaryWriter.Write(g); + //binaryWriter.Write(b); + } + } + + binaryWriter.Close(); + binaryWriter.Dispose(); + } + } } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2WriterBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs index f265707a1..45ee7332e 100644 --- a/src/PointCloud/Potree/V2/Potree2WriterBase.cs +++ b/src/PointCloud/Potree/V2/Potree2WriterBase.cs @@ -24,7 +24,7 @@ private set _potreeData = value; } } - private PotreeData? _potreeData; + protected PotreeData? _potreeData; /// /// Save if metadata has already been cached From 849e3d432948b442fe9e3e67c57ced8dd3350663 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 27 Feb 2023 16:00:49 +0100 Subject: [PATCH 070/294] Fixed PointCloudPotree2 example --- .../PointCloudPotree2/Core/PointCloudPotree2Core.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index fbbf04ee3..c653e7192 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -59,8 +59,7 @@ public void OnLoadNewFile(object sender, EventArgs e) if (path == null || path == string.Empty) return; - _potreeData = new PotreeData(path); - _potreeReader = new Potree2Reader(ref _potreeData); + _potreeReader.ReadNewFile(path, out _potreeData); _pointCloud = (PointCloudComponent)_potreeReader.GetPointCloudComponent(RenderMode.DynamicMesh); _pointCloud.PointCloudImp.MinProjSizeModifier = PointRenderingParams.Instance.ProjectedSizeModifier; @@ -73,8 +72,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeData = new PotreeData(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); - _potreeReader = new Potree2Reader(ref _potreeData); + _potreeReader = new Potree2Reader(); + _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile), out _potreeData); _rc = rc; } From 30871ca42cd42088f3cf45875740a34dddaca7c7 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 27 Feb 2023 16:26:10 +0100 Subject: [PATCH 071/294] JSON Serialization and better handling for Ray classes --- src/Math/Core/Rayd.cs | 52 ++++++++++++++++++++++++++++++++++++------- src/Math/Core/Rayf.cs | 52 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/Math/Core/Rayd.cs b/src/Math/Core/Rayd.cs index 886edc8b2..945169d92 100644 --- a/src/Math/Core/Rayd.cs +++ b/src/Math/Core/Rayd.cs @@ -1,24 +1,56 @@ -namespace Fusee.Math.Core +using Newtonsoft.Json; + +namespace Fusee.Math.Core { /// /// Represents a ray with a given origin and direction. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct RayD { /// /// The point in world coordinates from which the ray originates. /// + [JsonProperty(PropertyName = "Origin")] public double3 Origin; + private double3 _direction; + /// /// The direction of the ray. /// - public double3 Direction { get; private set; } + [JsonProperty(PropertyName = "Direction")] + public double3 Direction + { + get { return _direction; } + set + { + _direction = double3.Normalize(value); + + _inverseDirty = true; + } + } + + private double3 _inverse; + private bool _inverseDirty; /// /// The inverse of the direction vector of the ray (1 / direction). /// - public double3 Inverse { get; private set; } + public double3 Inverse + { + get + { + if (_inverseDirty) + { + _inverse = new double3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + + _inverseDirty = false; + } + + return _inverse; + } + } /// /// Create a new ray. @@ -28,8 +60,11 @@ public struct RayD public RayD(double3 origin_, double3 direction_) { Origin = origin_; - Direction = double3.Normalize(direction_); - Inverse = new double3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + + _direction = double3.Normalize(direction_); + + _inverse = default; + _inverseDirty = true; } /// @@ -42,14 +77,15 @@ public RayD(double2 pickPosClip, double4x4 view, double4x4 projection) { Origin = double4x4.Invert(view).Translation(); + _inverse = default; + _inverseDirty = true; + double4x4 invViewProjection = double4x4.Invert(projection * view); var pickPosWorld4 = double4x4.Transform(invViewProjection, new double4(pickPosClip.x, pickPosClip.y, 1, 1)); var pickPosWorld = (pickPosWorld4 / pickPosWorld4.w).xyz; - Direction = (pickPosWorld - Origin).Normalize(); - - Inverse = new double3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + _direction = (pickPosWorld - Origin).Normalize(); } } } \ No newline at end of file diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index c1de24b42..20f4e56d7 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -1,24 +1,56 @@ -namespace Fusee.Math.Core +using Newtonsoft.Json; + +namespace Fusee.Math.Core { /// /// Represents a ray with a given origin and direction. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct RayF { /// /// The point in world coordinates from which the ray originates. /// + [JsonProperty(PropertyName = "Origin")] public float3 Origin; + private float3 _direction; + /// /// The direction of the ray. /// - public float3 Direction { get; private set; } + [JsonProperty(PropertyName = "Direction")] + public float3 Direction + { + get { return _direction; } + set + { + _direction = float3.Normalize(value); + + _inverseDirty = true; + } + } + + private float3 _inverse; + private bool _inverseDirty; /// /// The inverse of the direction vector of the ray (1 / direction). /// - public float3 Inverse { get; private set; } + public float3 Inverse + { + get + { + if (_inverseDirty) + { + _inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + + _inverseDirty = false; + } + + return _inverse; + } + } /// /// Create a new ray. @@ -28,8 +60,11 @@ public struct RayF public RayF(float3 origin_, float3 direction_) { Origin = origin_; - Direction = float3.Normalize(direction_); - Inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + + _direction = float3.Normalize(direction_); + + _inverse = default; + _inverseDirty = true; } /// @@ -42,14 +77,15 @@ public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) { Origin = float4x4.Invert(view).Translation(); + _inverse = default; + _inverseDirty = true; + float4x4 invViewProjection = float4x4.Invert(projection * view); var pickPosWorld4 = float4x4.Transform(invViewProjection, new float4(pickPosClip.x, pickPosClip.y, 1, 1)); var pickPosWorld = (pickPosWorld4 / pickPosWorld4.w).xyz; - Direction = (pickPosWorld - Origin).Normalize(); - - Inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); + _direction = (pickPosWorld - Origin).Normalize(); } } } \ No newline at end of file From 8ef0f03518ab0e46011e9f47884a7b806976d218 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 27 Feb 2023 16:26:34 +0100 Subject: [PATCH 072/294] JSON Serialization for the rest of Math --- src/Math/Core/AABBd.cs | 4 ++++ src/Math/Core/AABBf.cs | 4 ++-- src/Math/Core/Curve.cs | 11 ++++++++++- src/Math/Core/Eigen.cs | 6 +++++- src/Math/Core/FrustumD.cs | 8 ++++++++ src/Math/Core/FrustumF.cs | 8 ++++++++ src/Math/Core/MinMaxRect.cs | 8 ++++++-- src/Math/Core/OBBd.cs | 24 ++++++++++++++++++------ src/Math/Core/OBBf.cs | 24 ++++++++++++++++++------ src/Math/Core/PlaneD.cs | 8 +++++++- src/Math/Core/PlaneF.cs | 8 +++++++- src/Math/Core/QuaternionD.cs | 5 +++++ src/Math/Core/QuaternionF.cs | 5 +++++ src/Math/Core/double2.cs | 4 ++++ src/Math/Core/double3.cs | 7 ++++++- src/Math/Core/double3x3.cs | 7 ++++++- src/Math/Core/double4.cs | 6 ++++++ src/Math/Core/double4x4.cs | 6 ++++++ src/Math/Core/float2.cs | 4 ++++ src/Math/Core/float3x3.cs | 7 ++++++- src/Math/Core/int2.cs | 6 +++++- src/Math/Core/int3.cs | 7 ++++++- src/Math/Core/int4.cs | 6 ++++++ 23 files changed, 158 insertions(+), 25 deletions(-) diff --git a/src/Math/Core/AABBd.cs b/src/Math/Core/AABBd.cs index bd351cdd8..b47c0659f 100644 --- a/src/Math/Core/AABBd.cs +++ b/src/Math/Core/AABBd.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents an axis aligned bounding box. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct AABBd @@ -14,11 +16,13 @@ public struct AABBd /// /// The minimum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty(PropertyName = "Min")] [ProtoMember(1)] public double3 min; /// /// The maximum values of the axis aligned bounding box in x, y and z direction /// + [JsonProperty(PropertyName = "Max")] [ProtoMember(2)] public double3 max; /// diff --git a/src/Math/Core/AABBf.cs b/src/Math/Core/AABBf.cs index 1e6915dd1..3748d7142 100644 --- a/src/Math/Core/AABBf.cs +++ b/src/Math/Core/AABBf.cs @@ -16,13 +16,13 @@ public struct AABBf /// /// The minimum values of the axis aligned bounding box in x, y and z direction /// - [JsonProperty] + [JsonProperty(PropertyName = "Min")] [ProtoMember(1)] public float3 min; /// /// The maximum values of the axis aligned bounding box in x, y and z direction /// - [JsonProperty] + [JsonProperty(PropertyName = "Max")] [ProtoMember(2)] public float3 max; /// diff --git a/src/Math/Core/Curve.cs b/src/Math/Core/Curve.cs index 3d251751c..5522f81fa 100644 --- a/src/Math/Core/Curve.cs +++ b/src/Math/Core/Curve.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Linq; @@ -7,11 +8,13 @@ namespace Fusee.Math.Core /// /// Represents a curve, using a list of CurveParts. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class Curve { /// /// The parts forming the curve. /// + [JsonProperty(PropertyName = "CurveParts")] public IList CurveParts = new List(); /// @@ -97,21 +100,25 @@ public IEnumerable CalcAdaptivePolyline(float acreage) /// /// Represents a open or closed part of a curve, using a list of CurveSegments and its starting point. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class CurvePart { /// /// A CurvePart can be closed or open. /// + [JsonProperty(PropertyName = "IsClosed")] public bool IsClosed; /// /// The starting point of the CurvePart. /// + [JsonProperty(PropertyName = "StartPoint")] public float3 StartPoint; /// /// The segments making up the CurvePart. /// + [JsonProperty(PropertyName = "CurveSegments")] public IList CurveSegments = new List(); /// @@ -225,11 +232,13 @@ public IEnumerable CalcAdaptivePolyline(float acreage) /// A CurveSgment does not know its own start point. For the first CurveSegment in a sequence the start point is saved in the CurvePart belonging to the segment. /// The start point for the CurveSegment with index i is the last vertex in the CurveSegent[i-1]'s list of vertices. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public abstract class CurveSegment { /// ///The vertices of a CurveSegment represented by float3s. /// + [JsonProperty(PropertyName = "Vertices")] public IList Vertices = new List(); /// diff --git a/src/Math/Core/Eigen.cs b/src/Math/Core/Eigen.cs index 7a8bc3fec..2ab12774c 100644 --- a/src/Math/Core/Eigen.cs +++ b/src/Math/Core/Eigen.cs @@ -1,20 +1,24 @@ -using System.Linq; +using Newtonsoft.Json; +using System.Linq; namespace Fusee.Math.Core { /// /// Eigen data structure with values and vectors in double precision. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct Eigen { /// /// Eigen values. /// + [JsonProperty(PropertyName = "Values")] public double[] Values; /// /// Eigen vectors. /// + [JsonProperty(PropertyName = "Vectors")] public double3[] Vectors; /// diff --git a/src/Math/Core/FrustumD.cs b/src/Math/Core/FrustumD.cs index 4608e93d4..7f180af55 100644 --- a/src/Math/Core/FrustumD.cs +++ b/src/Math/Core/FrustumD.cs @@ -1,4 +1,5 @@  +using Newtonsoft.Json; using System.Collections.Generic; namespace Fusee.Math.Core @@ -6,36 +7,43 @@ namespace Fusee.Math.Core /// /// Describes a frustum by using six s. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class FrustumD { /// /// The near plane of the frustum. /// + [JsonProperty(PropertyName = "Near")] public PlaneD Near { get; private set; } /// /// The far plane of the frustum. /// + [JsonProperty(PropertyName = "Far")] public PlaneD Far { get; private set; } /// /// The left plane of the frustum. /// + [JsonProperty(PropertyName = "Left")] public PlaneD Left { get; private set; } /// /// The right plane of the frustum. /// + [JsonProperty(PropertyName = "Right")] public PlaneD Right { get; private set; } /// /// The top plane of the frustum. /// + [JsonProperty(PropertyName = "Top")] public PlaneD Top { get; private set; } /// /// The bottom plane of the frustum. /// + [JsonProperty(PropertyName = "Bottom")] public PlaneD Bottom { get; private set; } /// diff --git a/src/Math/Core/FrustumF.cs b/src/Math/Core/FrustumF.cs index c1c500f4d..f0b1f289e 100644 --- a/src/Math/Core/FrustumF.cs +++ b/src/Math/Core/FrustumF.cs @@ -1,4 +1,5 @@  +using Newtonsoft.Json; using System.Collections.Generic; namespace Fusee.Math.Core @@ -6,36 +7,43 @@ namespace Fusee.Math.Core /// /// Describes a frustum by using six s. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class FrustumF { /// /// The near plane of the frustum. /// + [JsonProperty(PropertyName = "Near")] public PlaneF Near { get; private set; } /// /// The far plane of the frustum. /// + [JsonProperty(PropertyName = "Far")] public PlaneF Far { get; private set; } /// /// The left plane of the frustum. /// + [JsonProperty(PropertyName = "Left")] public PlaneF Left { get; private set; } /// /// The right plane of the frustum. /// + [JsonProperty(PropertyName = "Right")] public PlaneF Right { get; private set; } /// /// The top plane of the frustum. /// + [JsonProperty(PropertyName = "Top")] public PlaneF Top { get; private set; } /// /// The bottom plane of the frustum. /// + [JsonProperty(PropertyName = "Bottom")] public PlaneF Bottom { get; private set; } /// diff --git a/src/Math/Core/MinMaxRect.cs b/src/Math/Core/MinMaxRect.cs index ee1888955..b3e0ef3ba 100644 --- a/src/Math/Core/MinMaxRect.cs +++ b/src/Math/Core/MinMaxRect.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Globalization; namespace Fusee.Math.Core @@ -7,16 +8,19 @@ namespace Fusee.Math.Core /// Class containing an axis aligned (two-dimensional) rectangle specified by its minimum (lower-left) and maximum (upper-right) /// points in 2d space. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct MinMaxRect { /// /// Returns the minimum (lower-left corner) as a float2 vector. - /// + /// + [JsonProperty(PropertyName = "Min")] public float2 Min; /// /// Returns the maximum (upper-right corner) as a float2 vector. /// + [JsonProperty(PropertyName = "Max")] public float2 Max; /// diff --git a/src/Math/Core/OBBd.cs b/src/Math/Core/OBBd.cs index 453615149..d491fab30 100644 --- a/src/Math/Core/OBBd.cs +++ b/src/Math/Core/OBBd.cs @@ -1,4 +1,5 @@ -using ProtoBuf; +using Newtonsoft.Json; +using ProtoBuf; using System.Linq; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents an oriented bounding box. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct OBBd @@ -14,27 +16,37 @@ public struct OBBd /// /// The minimum values of the oriented bounding box in x, y and z direction /// - [ProtoMember(1)] public double3 Min; + [JsonProperty(PropertyName = "Min")] + [ProtoMember(1)] + public double3 Min; /// /// The maximum values of the oriented bounding box in x, y and z direction /// - [ProtoMember(2)] public double3 Max; + [JsonProperty(PropertyName = "Max")] + [ProtoMember(2)] + public double3 Max; /// /// The rotation of the oriented bounding box /// - [ProtoMember(3)] public double4x4 Rotation; + [JsonProperty(PropertyName = "Rotation")] + [ProtoMember(3)] + public double4x4 Rotation; /// /// The translation of the oriented bounding box /// - [ProtoMember(4)] public double3 Translation; + [JsonProperty(PropertyName = "Translation")] + [ProtoMember(4)] + public double3 Translation; /// /// Returns the with, height and depth of the box in x, y and z /// - [ProtoMember(5)] public double3 Size; + [JsonProperty(PropertyName = "Size")] + [ProtoMember(5)] + public double3 Size; /// /// Returns the center of the bounding box diff --git a/src/Math/Core/OBBf.cs b/src/Math/Core/OBBf.cs index af4ec4c16..bfa4b8aa1 100644 --- a/src/Math/Core/OBBf.cs +++ b/src/Math/Core/OBBf.cs @@ -1,4 +1,5 @@ -using ProtoBuf; +using Newtonsoft.Json; +using ProtoBuf; using System.Linq; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents an oriented bounding box. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct OBBf @@ -14,27 +16,37 @@ public struct OBBf /// /// The minimum values of the oriented bounding box in x, y and z direction /// - [ProtoMember(1)] public float3 Min; + [JsonProperty(PropertyName = "Min")] + [ProtoMember(1)] + public float3 Min; /// /// The maximum values of the oriented bounding box in x, y and z direction /// - [ProtoMember(2)] public float3 Max; + [JsonProperty(PropertyName = "Max")] + [ProtoMember(2)] + public float3 Max; /// /// The rotation of the oriented bounding box /// - [ProtoMember(3)] public float4x4 Rotation; + [JsonProperty(PropertyName = "Rotation")] + [ProtoMember(3)] + public float4x4 Rotation; /// /// The translation of the oriented bounding box /// - [ProtoMember(4)] public float3 Translation; + [JsonProperty(PropertyName = "Translation")] + [ProtoMember(4)] + public float3 Translation; /// /// Returns the with, height and depth of the box in x, y and z /// - [ProtoMember(5)] public float3 Size; + [JsonProperty(PropertyName = "Size")] + [ProtoMember(5)] + public float3 Size; /// /// Returns the center of the bounding box diff --git a/src/Math/Core/PlaneD.cs b/src/Math/Core/PlaneD.cs index 816ad89d3..38f63bf09 100644 --- a/src/Math/Core/PlaneD.cs +++ b/src/Math/Core/PlaneD.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; namespace Fusee.Math.Core { @@ -8,11 +9,13 @@ namespace Fusee.Math.Core /// The plane divides a space into two half-spaces.The direction plane's normal vector defines the "outer" or negative half-space. /// Points that lie in the positive half space of the plane do have a negative signed distance to the plane. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct PlaneD { /// /// The A plane coefficient. /// + [JsonProperty(PropertyName = "A")] public double A { get => _a; @@ -27,6 +30,7 @@ public double A /// /// The B plane coefficient. /// + [JsonProperty(PropertyName = "B")] public double B { get => _b; @@ -41,6 +45,7 @@ public double B /// /// The C plane coefficient. /// + [JsonProperty(PropertyName = "C")] public double C { get => _c; @@ -55,6 +60,7 @@ public double C /// /// The D plane coefficient. /// + [JsonProperty(PropertyName = "D")] public double D; /// diff --git a/src/Math/Core/PlaneF.cs b/src/Math/Core/PlaneF.cs index c69683499..599300e67 100644 --- a/src/Math/Core/PlaneF.cs +++ b/src/Math/Core/PlaneF.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; namespace Fusee.Math.Core { @@ -8,11 +9,13 @@ namespace Fusee.Math.Core /// The plane divides a space into two half-spaces.The direction plane's normal vector defines the "outer" or negative half-space. /// Points that lie in the positive half space of the plane do have a negative signed distance to the plane. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public struct PlaneF { /// /// The A plane coefficient. /// + [JsonProperty(PropertyName = "A")] public float A { get => _a; @@ -27,6 +30,7 @@ public float A /// /// The B plane coefficient. /// + [JsonProperty(PropertyName = "B")] public float B { get => _b; @@ -41,6 +45,7 @@ public float B /// /// The C plane coefficient. /// + [JsonProperty(PropertyName = "C")] public float C { get => _c; @@ -55,6 +60,7 @@ public float C /// /// The D plane coefficient. /// + [JsonProperty(PropertyName = "D")] public float D; /// diff --git a/src/Math/Core/QuaternionD.cs b/src/Math/Core/QuaternionD.cs index 7feaeb8d2..197f76e4c 100644 --- a/src/Math/Core/QuaternionD.cs +++ b/src/Math/Core/QuaternionD.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using System; using System.Globalization; using System.Runtime.InteropServices; @@ -7,12 +8,16 @@ namespace Fusee.Math.Core /// /// Represents a QuaternionD (single precision). /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] public struct QuaternionD : IEquatable { #region Fields + [JsonProperty(PropertyName = "XYZ")] private double3 _xyz; + + [JsonProperty(PropertyName = "W")] private double _w; #endregion Fields diff --git a/src/Math/Core/QuaternionF.cs b/src/Math/Core/QuaternionF.cs index 0d7965b36..bde2a0ac8 100644 --- a/src/Math/Core/QuaternionF.cs +++ b/src/Math/Core/QuaternionF.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using System; using System.Globalization; using System.Runtime.InteropServices; @@ -7,12 +8,16 @@ namespace Fusee.Math.Core /// /// Represents a Quaternion (single precision). /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] public struct QuaternionF : IEquatable { #region Fields + [JsonProperty(PropertyName = "XYZ")] private float3 _xyz; + + [JsonProperty(PropertyName = "W")] private float _w; #endregion Fields diff --git a/src/Math/Core/double2.cs b/src/Math/Core/double2.cs index d9c41162a..334bfef7c 100644 --- a/src/Math/Core/double2.cs +++ b/src/Math/Core/double2.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The double2 structure is suitable for inter-operation with unmanaged code requiring two consecutive doubles. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct double2 : IEquatable @@ -20,12 +22,14 @@ public struct double2 : IEquatable /// /// The x component of the double2. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public double x; /// /// The y component of the double2. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public double y; diff --git a/src/Math/Core/double3.cs b/src/Math/Core/double3.cs index c3431903d..2da6bbbc9 100644 --- a/src/Math/Core/double3.cs +++ b/src/Math/Core/double3.cs @@ -1,4 +1,5 @@ -using ProtoBuf; +using Newtonsoft.Json; +using ProtoBuf; using System; using System.Globalization; using System.Runtime.InteropServices; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The double3 structure is suitable for inter-operation with unmanaged code requiring three consecutive doubles. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct double3 : IEquatable @@ -20,18 +22,21 @@ public struct double3 : IEquatable /// /// The x component of the double3. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public double x; /// /// The y component of the double3. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public double y; /// /// The z component of the double3. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public double z; diff --git a/src/Math/Core/double3x3.cs b/src/Math/Core/double3x3.cs index aca0bd3a8..2cdf4ae2f 100644 --- a/src/Math/Core/double3x3.cs +++ b/src/Math/Core/double3x3.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Globalization; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents a 3x3 Matrix /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] public struct double3x3 : IEquatable { @@ -15,16 +17,19 @@ public struct double3x3 : IEquatable /// /// Top row of the matrix /// + [JsonProperty(PropertyName = "Row1")] public double3 Row1; /// /// 2nd row of the matrix /// + [JsonProperty(PropertyName = "Row2")] public double3 Row2; /// /// 3rd row of the matrix /// + [JsonProperty(PropertyName = "Row3")] public double3 Row3; /// diff --git a/src/Math/Core/double4.cs b/src/Math/Core/double4.cs index 2bf0ac80d..9d7b8364b 100644 --- a/src/Math/Core/double4.cs +++ b/src/Math/Core/double4.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -9,6 +10,7 @@ namespace Fusee.Math.Core /// /// The double4 structure is suitable for interoperation with unmanaged code requiring four consecutive doubles. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct double4 : IEquatable @@ -18,24 +20,28 @@ public struct double4 : IEquatable /// /// The x component of the double4. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public double x; /// /// The y component of the double4. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public double y; /// /// The z component of the double4. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public double z; /// /// The w component of the double4. /// + [JsonProperty(PropertyName = "W")] [ProtoMember(4)] public double w; diff --git a/src/Math/Core/double4x4.cs b/src/Math/Core/double4x4.cs index 0baa1c569..027db142f 100644 --- a/src/Math/Core/double4x4.cs +++ b/src/Math/Core/double4x4.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -44,6 +45,7 @@ namespace Fusee.Math.Core /// of methods are postfixed with "RH". /// /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct double4x4 : IEquatable @@ -53,24 +55,28 @@ public struct double4x4 : IEquatable /// /// Top row of the matrix /// + [JsonProperty(PropertyName = "Row1")] [ProtoMember(1)] public double4 Row1; /// /// 2nd row of the matrix /// + [JsonProperty(PropertyName = "Row2")] [ProtoMember(2)] public double4 Row2; /// /// 3rd row of the matrix /// + [JsonProperty(PropertyName = "Row3")] [ProtoMember(3)] public double4 Row3; /// /// Bottom row of the matrix /// + [JsonProperty(PropertyName = "Row4")] [ProtoMember(4)] public double4 Row4; diff --git a/src/Math/Core/float2.cs b/src/Math/Core/float2.cs index 0df58b5db..63d7ced78 100644 --- a/src/Math/Core/float2.cs +++ b/src/Math/Core/float2.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The float2 structure is suitable for interoperation with unmanaged code requiring two consecutive floats. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct float2 : IEquatable @@ -20,12 +22,14 @@ public struct float2 : IEquatable /// /// The x component of the float2. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public float x; /// /// The y component of the float2. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public float y; diff --git a/src/Math/Core/float3x3.cs b/src/Math/Core/float3x3.cs index cd759640a..b4121efd9 100644 --- a/src/Math/Core/float3x3.cs +++ b/src/Math/Core/float3x3.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Globalization; using System.Runtime.InteropServices; @@ -7,6 +8,7 @@ namespace Fusee.Math.Core /// /// Represents a 3x3 Matrix /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] public struct float3x3 : IEquatable { @@ -15,16 +17,19 @@ public struct float3x3 : IEquatable /// /// Top row of the matrix /// + [JsonProperty(PropertyName = "Row1")] public float3 Row1; /// /// 2nd row of the matrix /// + [JsonProperty(PropertyName = "Row2")] public float3 Row2; /// /// 3rd row of the matrix /// + [JsonProperty(PropertyName = "Row3")] public float3 Row3; /// diff --git a/src/Math/Core/int2.cs b/src/Math/Core/int2.cs index f1d2b1122..31c937d20 100644 --- a/src/Math/Core/int2.cs +++ b/src/Math/Core/int2.cs @@ -1,4 +1,5 @@ -using ProtoBuf; +using Newtonsoft.Json; +using ProtoBuf; using System; using System.Globalization; using System.Runtime.InteropServices; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The int2 structure is suitable for interoperation with unmanaged code requiring two consecutive ints. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct int2 : IEquatable @@ -20,12 +22,14 @@ public struct int2 : IEquatable /// /// The x component of the int2. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public int x; /// /// The y component of the int2. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public int y; diff --git a/src/Math/Core/int3.cs b/src/Math/Core/int3.cs index 54033be92..80d4ee64b 100644 --- a/src/Math/Core/int3.cs +++ b/src/Math/Core/int3.cs @@ -1,4 +1,5 @@ -using ProtoBuf; +using Newtonsoft.Json; +using ProtoBuf; using System; using System.Globalization; using System.Runtime.InteropServices; @@ -11,6 +12,7 @@ namespace Fusee.Math.Core /// /// The int3 structure is suitable for inter-operation with unmanaged code requiring three consecutive ints. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [ProtoContract] [StructLayout(LayoutKind.Sequential)] public struct int3 : IEquatable @@ -20,18 +22,21 @@ public struct int3 : IEquatable /// /// The x component of the int3. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public int x; /// /// The y component of the int3. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public int y; /// /// The z component of the int3. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public int z; diff --git a/src/Math/Core/int4.cs b/src/Math/Core/int4.cs index a3900f4a3..fe00755ac 100644 --- a/src/Math/Core/int4.cs +++ b/src/Math/Core/int4.cs @@ -1,3 +1,4 @@ +using Newtonsoft.Json; using ProtoBuf; using System; using System.Globalization; @@ -9,6 +10,7 @@ namespace Fusee.Math.Core /// /// The int4 structure is suitable for interoperation with unmanaged code requiring four consecutive ints. /// + [JsonObject(MemberSerialization = MemberSerialization.OptIn)] [StructLayout(LayoutKind.Sequential)] [ProtoContract] public struct int4 : IEquatable @@ -18,24 +20,28 @@ public struct int4 : IEquatable /// /// The x component of the int4. /// + [JsonProperty(PropertyName = "X")] [ProtoMember(1)] public int x; /// /// The y component of the int4. /// + [JsonProperty(PropertyName = "Y")] [ProtoMember(2)] public int y; /// /// The z component of the int4. /// + [JsonProperty(PropertyName = "Z")] [ProtoMember(3)] public int z; /// /// The w component of the int4. /// + [JsonProperty(PropertyName = "W")] [ProtoMember(4)] public int w; From d28c5bdfd92b0e2629a2a38dba8c85c18eaf6e29 Mon Sep 17 00:00:00 2001 From: ASPePeX Date: Mon, 27 Feb 2023 15:43:05 +0000 Subject: [PATCH 073/294] Linting --- src/Math/Core/PlaneD.cs | 2 +- src/Math/Core/PlaneF.cs | 2 +- src/Math/Core/QuaternionD.cs | 4 ++-- src/Math/Core/QuaternionF.cs | 6 +++--- src/Math/Core/Rayd.cs | 4 ++-- src/Math/Core/Rayf.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Math/Core/PlaneD.cs b/src/Math/Core/PlaneD.cs index 38f63bf09..4fc4a917e 100644 --- a/src/Math/Core/PlaneD.cs +++ b/src/Math/Core/PlaneD.cs @@ -60,7 +60,7 @@ public double C /// /// The D plane coefficient. /// - [JsonProperty(PropertyName = "D")] + [JsonProperty(PropertyName = "D")] public double D; /// diff --git a/src/Math/Core/PlaneF.cs b/src/Math/Core/PlaneF.cs index 599300e67..4b10fcd2f 100644 --- a/src/Math/Core/PlaneF.cs +++ b/src/Math/Core/PlaneF.cs @@ -60,7 +60,7 @@ public float C /// /// The D plane coefficient. /// - [JsonProperty(PropertyName = "D")] + [JsonProperty(PropertyName = "D")] public float D; /// diff --git a/src/Math/Core/QuaternionD.cs b/src/Math/Core/QuaternionD.cs index 197f76e4c..93bdf46bf 100644 --- a/src/Math/Core/QuaternionD.cs +++ b/src/Math/Core/QuaternionD.cs @@ -16,8 +16,8 @@ public struct QuaternionD : IEquatable [JsonProperty(PropertyName = "XYZ")] private double3 _xyz; - - [JsonProperty(PropertyName = "W")] + + [JsonProperty(PropertyName = "W")] private double _w; #endregion Fields diff --git a/src/Math/Core/QuaternionF.cs b/src/Math/Core/QuaternionF.cs index bde2a0ac8..df7f5b18f 100644 --- a/src/Math/Core/QuaternionF.cs +++ b/src/Math/Core/QuaternionF.cs @@ -14,10 +14,10 @@ public struct QuaternionF : IEquatable { #region Fields - [JsonProperty(PropertyName = "XYZ")] + [JsonProperty(PropertyName = "XYZ")] private float3 _xyz; - - [JsonProperty(PropertyName = "W")] + + [JsonProperty(PropertyName = "W")] private float _w; #endregion Fields diff --git a/src/Math/Core/Rayd.cs b/src/Math/Core/Rayd.cs index 945169d92..54ba044bb 100644 --- a/src/Math/Core/Rayd.cs +++ b/src/Math/Core/Rayd.cs @@ -11,7 +11,7 @@ public struct RayD /// /// The point in world coordinates from which the ray originates. /// - [JsonProperty(PropertyName = "Origin")] + [JsonProperty(PropertyName = "Origin")] public double3 Origin; private double3 _direction; @@ -19,7 +19,7 @@ public struct RayD /// /// The direction of the ray. /// - [JsonProperty(PropertyName = "Direction")] + [JsonProperty(PropertyName = "Direction")] public double3 Direction { get { return _direction; } diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index 20f4e56d7..a3707de78 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -11,7 +11,7 @@ public struct RayF /// /// The point in world coordinates from which the ray originates. /// - [JsonProperty(PropertyName = "Origin")] + [JsonProperty(PropertyName = "Origin")] public float3 Origin; private float3 _direction; @@ -19,7 +19,7 @@ public struct RayF /// /// The direction of the ray. /// - [JsonProperty(PropertyName = "Direction")] + [JsonProperty(PropertyName = "Direction")] public float3 Direction { get { return _direction; } From 7aab7c35b75cea7659aafd16dd888291dae8d611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Feb 2023 02:59:13 +0000 Subject: [PATCH 074/294] Bump protobuf-net from 3.1.25 to 3.2.8 Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) from 3.1.25 to 3.2.8. - [Release notes](https://github.com/protobuf-net/protobuf-net/releases) - [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md) - [Commits](https://github.com/protobuf-net/protobuf-net/commits) --- updated-dependencies: - dependency-name: protobuf-net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj | 2 +- .../Camera/Android/Fusee.Examples.Camera.Android.csproj | 2 +- .../Deferred/Android/Fusee.Examples.Deferred.Android.csproj | 2 +- .../Android/Fusee.Examples.GeometryEditing.Android.csproj | 2 +- .../Materials/Android/Fusee.Examples.Materials.Android.csproj | 2 +- .../Android/Fusee.Examples.MeshingAround.Android.csproj | 2 +- .../Picking/Android/Fusee.Examples.Picking.Android.csproj | 2 +- .../Android/Fusee.Examples.PickingRayCast.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderContextOnly.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderLayer.Android.csproj | 2 +- .../Simple/Android/Fusee.Examples.Simple.Android.csproj | 2 +- .../ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj | 2 +- Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj | 2 +- src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj | 2 +- src/Math/Core/Fusee.Math.Core.csproj | 2 +- src/Serialization/Fusee.Serialization.csproj | 2 +- src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj index 716bc73d3..d69915948 100644 --- a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj +++ b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj @@ -57,7 +57,7 @@ - + diff --git a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj index 0646d5f7f..0ab87abd6 100644 --- a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj +++ b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj @@ -59,7 +59,7 @@ - + diff --git a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj index 066bb39a5..420e8a68f 100644 --- a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj +++ b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj @@ -65,7 +65,7 @@ - + diff --git a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj index 24943d6ef..9e902a6c0 100644 --- a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj +++ b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj index a18574c18..7a0a736e2 100644 --- a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj +++ b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj @@ -64,7 +64,7 @@ - + diff --git a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj index 57533037d..786f6ecf7 100644 --- a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj +++ b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj index b42ecd28f..aa567235f 100644 --- a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj +++ b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj index 19d0bb5c9..38166cf1d 100644 --- a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj +++ b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj index 73972b9da..61fbe59b9 100644 --- a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj +++ b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj index 1fb54ee84..4ecd88b37 100644 --- a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj +++ b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj index da7bcfee9..244ff3c30 100644 --- a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj +++ b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj index 56dccb690..3e4791540 100644 --- a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj +++ b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj index 6756b5c85..66af1fb34 100644 --- a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj +++ b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj index a0836eaf8..2c28efe9d 100644 --- a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj +++ b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index 7dac979e0..b2b295658 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/src/Serialization/Fusee.Serialization.csproj b/src/Serialization/Fusee.Serialization.csproj index 8c44d05f9..541a3c1f1 100644 --- a/src/Serialization/Fusee.Serialization.csproj +++ b/src/Serialization/Fusee.Serialization.csproj @@ -19,7 +19,7 @@ analyzers - + \ No newline at end of file diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index 04c06af7f..fc1ce4c59 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -14,6 +14,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 8aeec3a8d4477582d10fc80e063d2b722919f984 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 1 Mar 2023 15:51:32 +0100 Subject: [PATCH 075/294] Fixed picking --- .../Complete/AdvancedUI/Core/AdvancedUI.cs | 7 +- Examples/Complete/Camera/Core/Camera.cs | 5 +- .../ComputeFractal/Core/ComputeFractal.cs | 3 +- .../GeometryEditing/Core/GeometryEditing.cs | 2 +- Examples/Complete/Integrations/Core/Main.cs | 6 +- Examples/Complete/Labyrinth/Core/Labyrinth.cs | 5 +- Examples/Complete/Materials/Core/Materials.cs | 6 +- Examples/Complete/Picking/Core/Picking.cs | 8 +- .../Core/PointCloudPotree2Core.cs | 4 +- Examples/Complete/Simple/Core/Simple.cs | 6 +- Examples/Complete/UI/Core/UI.cs | 12 +- src/Engine/Core/CameraResult.cs | 2 +- src/Engine/Core/PrePassVisitor.cs | 35 ++- src/Engine/Core/ScenePicker.cs | 206 +++++++++--------- src/Engine/Core/SceneRendererForward.cs | 4 +- src/Engine/GUI/SceneInteractionHandler.cs | 9 +- src/Engine/Player/Core/Player.cs | 5 +- src/Math/Core/AABBf.cs | 4 +- src/Math/Core/Rayf.cs | 7 +- .../Core/Scene/PointCloudPickerModule.cs | 34 ++- 20 files changed, 194 insertions(+), 176 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs index 9dbb41baa..05a1c4373 100644 --- a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs +++ b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs @@ -156,15 +156,16 @@ public override void Init() _gui = CreateGui(); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); //Create a scene picker for performing visibility tests - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override void Update() diff --git a/Examples/Complete/Camera/Core/Camera.cs b/Examples/Complete/Camera/Core/Camera.cs index 23570b9e9..65c491e8e 100644 --- a/Examples/Complete/Camera/Core/Camera.cs +++ b/Examples/Complete/Camera/Core/Camera.cs @@ -44,8 +44,6 @@ private async Task Load() { _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Camera Example"); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); _frustum = new WireframeCube(); SceneNode frustumNode = new() @@ -118,6 +116,9 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_rocketScene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override async Task InitAsync() diff --git a/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs b/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs index 3c56da381..366e5966f 100644 --- a/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs +++ b/Examples/Complete/ComputeFractal/Core/ComputeFractal.cs @@ -91,7 +91,8 @@ public override void Init() RC.SetEffect(_computeShader); _rect.SetData(_rectData); _colors.SetData(_colorData); - _sih = new SceneInteractionHandler(_gui); + + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } // RenderAFrame is called once a frame diff --git a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs index 863407d83..c20810e04 100644 --- a/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs +++ b/Examples/Complete/GeometryEditing/Core/GeometryEditing.cs @@ -120,7 +120,7 @@ public override void Init() _scene = new SceneContainer { Children = new List { _parentNode } }; _renderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, _renderer.PrePassVisitor.CameraPrepassResults); _activeGeometrys = new Dictionary(); } diff --git a/Examples/Complete/Integrations/Core/Main.cs b/Examples/Complete/Integrations/Core/Main.cs index 24b05449c..bad8773d4 100644 --- a/Examples/Complete/Integrations/Core/Main.cs +++ b/Examples/Complete/Integrations/Core/Main.cs @@ -45,9 +45,6 @@ private async Task Load() _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Simple Example"); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); - // Load the rocket model _rocketScene = await AssetStorage.GetAsync("RocketFus.fus"); rocketTransform = _rocketScene.Children[0].GetTransform(); @@ -80,6 +77,9 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_rocketScene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override async Task InitAsync() diff --git a/Examples/Complete/Labyrinth/Core/Labyrinth.cs b/Examples/Complete/Labyrinth/Core/Labyrinth.cs index c8d3a5046..912f596d2 100644 --- a/Examples/Complete/Labyrinth/Core/Labyrinth.cs +++ b/Examples/Complete/Labyrinth/Core/Labyrinth.cs @@ -284,7 +284,6 @@ private SceneContainer CreateScene() public override void Init() { _gui = CreateGui(); - _sih = new SceneInteractionHandler(_gui); // Find the ball and create AABB FindBall(); @@ -311,6 +310,8 @@ public override void Init() _bodyPivotTransform = _scene.Children.FindNodes(node => node.Name == "Bodytrans").FirstOrDefault()?.GetTransform(); _bodyNode = _scene.Children.FindNodes(node => node.Name == "Body")?.FirstOrDefault(); _bodyTransform = _bodyNode?.GetTransform(); + + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override void Update() @@ -843,7 +844,7 @@ private void OnWonGame() if (!_isGuiCreated) { _gui = CreateWinningGui(); - _sih = new SceneInteractionHandler(_gui); + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); _guiRenderer = new SceneRendererForward(_gui); _isGuiCreated = true; } diff --git a/Examples/Complete/Materials/Core/Materials.cs b/Examples/Complete/Materials/Core/Materials.cs index 93b736cf5..b3efceac3 100644 --- a/Examples/Complete/Materials/Core/Materials.cs +++ b/Examples/Complete/Materials/Core/Materials.cs @@ -63,8 +63,6 @@ public override void Init() } }); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); // Set the clear color for the backbuffer to white (100% intensity in all color channels R, G, B, A). _campComp.BackgroundColor = new float4(0.8f, 0.9f, 1, 1).LinearColorFromSRgb(); @@ -160,6 +158,10 @@ public override void Init() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererDeferred(_scene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); + } public override void Update() diff --git a/Examples/Complete/Picking/Core/Picking.cs b/Examples/Complete/Picking/Core/Picking.cs index 1ebf5ee80..1e130ecb0 100644 --- a/Examples/Complete/Picking/Core/Picking.cs +++ b/Examples/Complete/Picking/Core/Picking.cs @@ -48,12 +48,12 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _scenePicker = new ScenePicker(_scene); + _scenePicker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Picking Example"); // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); _guiRenderer = new SceneRendererForward(_gui); + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override async Task InitAsync() @@ -121,9 +121,9 @@ public override void RenderAFrame() //Picking if (_pick) { - float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); + //float2 pickPosClip = (_pickPos * new float2(2.0f / Width, -2.0f / Height)) + new float2(-1, 1); - PickResult newPick = _scenePicker.Pick(pickPosClip, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(pr => pr.ClipPos.z) + PickResult newPick = _scenePicker.Pick(Input.Mouse.Position, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(pr => pr.ClipPos.z) .FirstOrDefault(); Diagnostics.Debug(newPick); diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index db2c1a83f..0d57e8489 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -75,7 +75,7 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloudNode.Components[3] = _pointCloud; // re-generate picker and octree - _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); @@ -173,7 +173,7 @@ public void Init() // new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree, null) //}); - _picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() { new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) }); diff --git a/Examples/Complete/Simple/Core/Simple.cs b/Examples/Complete/Simple/Core/Simple.cs index a735fda81..0cd7f125b 100644 --- a/Examples/Complete/Simple/Core/Simple.cs +++ b/Examples/Complete/Simple/Core/Simple.cs @@ -41,9 +41,6 @@ private async Task Load() _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, CanvasRenderMode.Screen, "FUSEE Simple Example"); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); - // Load the rocket model _rocketScene = await AssetStorage.GetAsync("RocketFus.fus"); _camPivotTransform = new Transform(); @@ -72,6 +69,9 @@ private async Task Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_rocketScene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override async Task InitAsync() diff --git a/Examples/Complete/UI/Core/UI.cs b/Examples/Complete/UI/Core/UI.cs index d3699348f..89774297c 100644 --- a/Examples/Complete/UI/Core/UI.cs +++ b/Examples/Complete/UI/Core/UI.cs @@ -25,7 +25,7 @@ public class UI : RenderCanvas private const float Damping = 0.8f; private SceneContainer _scene; - private SceneRendererForward _sceneRenderer; + private SceneRendererForward _guiRenderer; private bool _keys; @@ -411,11 +411,11 @@ public override void Init() // Set the scene by creating a scene graph _scene = CreateNineSliceScene(); - // Create the interaction handler - _sih = new SceneInteractionHandler(_scene); - // Wrap a SceneRenderer around the model. - _sceneRenderer = new SceneRendererForward(_scene); + _guiRenderer = new SceneRendererForward(_scene); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_scene, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override void Update() @@ -465,7 +465,7 @@ public override void RenderAFrame() { _fpsText.Text = "FPS: " + Time.FramesPerSecond.ToString("0.00"); - _sceneRenderer.Render(RC); + _guiRenderer.Render(RC); // Constantly check for interactive objects. if (!Input.Mouse.Desc.Contains("Android")) diff --git a/src/Engine/Core/CameraResult.cs b/src/Engine/Core/CameraResult.cs index 50fd5c5a5..47655ade2 100644 --- a/src/Engine/Core/CameraResult.cs +++ b/src/Engine/Core/CameraResult.cs @@ -4,7 +4,7 @@ namespace Fusee.Engine.Core { - internal struct CameraResult : IEquatable + public struct CameraResult : IEquatable { public Camera Camera { get; private set; } diff --git a/src/Engine/Core/PrePassVisitor.cs b/src/Engine/Core/PrePassVisitor.cs index 2215a7c5e..af12afdc4 100644 --- a/src/Engine/Core/PrePassVisitor.cs +++ b/src/Engine/Core/PrePassVisitor.cs @@ -5,10 +5,21 @@ namespace Fusee.Engine.Core { - internal class PrePassVisitor : Visitor + /// + /// Visitor is inside before rendering the scene. + /// Collects s and s for rendering, picking, etc. + /// + public class PrePassVisitor : Visitor { - public List LightPrepassResuls; - public List CameraPrepassResults; + /// + /// Collection of s found while traversing the scene. + /// + public List LightPrepassResults { get; private set; } + + /// + /// Collection of s found while traversing the scene. + /// + public List CameraPrepassResults { get; private set; } /// /// Holds the status of the model matrices and other information we need while traversing up and down the scene graph. @@ -16,18 +27,26 @@ internal class PrePassVisitor : Visitor private readonly RendererState _state; private int _currentLight; + /// + /// which traverses the scene and yields and . + /// public PrePassVisitor() { _state = new RendererState(); IgnoreInactiveComponents = true; - LightPrepassResuls = new List(); + LightPrepassResults = new List(); CameraPrepassResults = new List(); } + /// + /// Call this method to initialize the traversal process. + /// + /// The to traverse. public void PrePassTraverse(SceneContainer sc) { _currentLight = 0; CameraPrepassResults.Clear(); + LightPrepassResults.Clear(); Traverse(sc.Children); } @@ -60,7 +79,7 @@ protected override void PopState() /// /// If a TransformComponent is visited the model matrix of the and is updated. /// It additionally updates the view matrix of the RenderContext. - /// + /// /// The TransformComponent. [VisitMethod] public void RenderTransform(Transform transform) @@ -71,7 +90,7 @@ public void RenderTransform(Transform transform) [VisitMethod] public void OnLight(Light lightComponent) { - if (LightPrepassResuls.Count - 1 < _currentLight) + if (LightPrepassResults.Count - 1 < _currentLight) { var lightResult = new LightResult(lightComponent) { @@ -79,11 +98,11 @@ public void OnLight(Light lightComponent) WorldSpacePos = new float3(_state.Model.M14, _state.Model.M24, _state.Model.M34) }; - LightPrepassResuls.Add(lightResult); + LightPrepassResults.Add(lightResult); } else { - var currentRes = LightPrepassResuls[_currentLight]; + var currentRes = LightPrepassResults[_currentLight]; currentRes.Rotation = _state.Model.RotationComponent(); currentRes.WorldSpacePos = new float3(_state.Model.M14, _state.Model.M24, _state.Model.M34); } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index cd5657fd0..381c727be 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -70,7 +70,6 @@ public class ScenePicker : Viserator /// The picker state upon scene traversal. @@ -141,16 +140,6 @@ public PickerState() RegisterState(_cullMode); RegisterState(_shaderFX); } - - /// - /// The current view matrix. - /// - public float4x4 View { get; set; } - - /// - /// The current projection matrix. - /// - public float4x4 Projection { get; set; } } /// @@ -158,15 +147,33 @@ public PickerState() /// public float2 PickPosClip { get; set; } - public float4x4 InvView => State.View.Invert(); + private float4x4 _view; + private float4x4 _invView; + private float4x4 _projection; + private float4x4 _invProj; + + private CameraResult _currentCameraResult; - public float4x4 InvProjection => State.Projection.Invert(); + internal CameraResult CurrentCameraResult + { + get => _currentCameraResult; + private set + { + Guard.IsGreaterThan(_canvasWidth, 0); + Guard.IsGreaterThan(_canvasHeight, 0); + + _currentCameraResult = value; + _projection = _currentCameraResult.Camera.GetProjectionMat(_canvasWidth, _canvasHeight, out _); + _view = _currentCameraResult.View; + _invView = _view.Invert(); + _invProj = _projection.Invert(); + } + } - public Camera? CurrentCamera { get; private set; } + private readonly IEnumerable _prePassResults; #endregion - private SceneContainer sc; /// /// The constructor to initialize a new ScenePicker. @@ -174,12 +181,13 @@ public PickerState() /// /// /// The to pick from. - public ScenePicker(SceneContainer scene, Cull cullMode = Cull.None, IEnumerable? customPickModule = null) + public ScenePicker(SceneContainer scene, IEnumerable prePassCameraResults, Cull cullMode = Cull.None, IEnumerable? customPickModule = null) : base(scene.Children, customPickModule) { IgnoreInactiveComponents = true; State.CullMode = cullMode; - sc = scene; + _prePassResults = prePassCameraResults; + } /// @@ -189,32 +197,67 @@ protected override void InitState() { base.InitState(); State.Model = float4x4.Identity; - State.View = float4x4.Identity; - State.Projection = float4x4.Identity; State.CanvasXForm = float4x4.Identity; - } /// /// Returns a collection of objects that fall in the area of the pick position and that can be iterated over. /// - /// The pick position. + /// The pick position in canvas coordinates (e.g. [1270x720]), usually .Position. /// The width of the current canvas, gets overwrite if a is bound /// The height of the current canvas, gets overwrite if a is bound /// public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) { - _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; - PickPosClip = pickPos; - PickerState.PickPosClip = pickPos; + float2 pickPosClip; + if (_prePassResults.Count() == 0) + { + pickPosClip = (pickPos * new float2(2.0f / _canvasWidth, -2.0f / _canvasHeight)) + new float2(-1, 1); + + PickPosClip = pickPosClip; + PickerState.PickPosClip = pickPosClip; + + SetState(); + var resNoCam = Viserate().ToList(); + resNoCam.AddRange(CheckVisitorModuleResults()); + return resNoCam; + } + + CameraResult pickCam = default; + Rectangle pickCamRect = new(); + + foreach (var camRes in _prePassResults) + { + Rectangle camRect = new() + { + Left = (int)(camRes.Camera.Viewport.x * _canvasWidth / 100), + Top = (int)(camRes.Camera.Viewport.y * _canvasHeight / 100) + }; + camRect.Right = ((int)(camRes.Camera.Viewport.z * _canvasWidth) / 100) + camRect.Left; + camRect.Bottom = ((int)(camRes.Camera.Viewport.w * _canvasHeight) / 100) + camRect.Top; + + if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) + continue; + + if (pickCam == default || camRes.Camera.Layer > pickCam.Camera.Layer) + { + pickCam = camRes; + pickCamRect = camRect; + } + } + + CurrentCameraResult = pickCam; + + pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); + PickPosClip = pickPosClip; + PickerState.PickPosClip = pickPosClip; SetState(); var res = Viserate().ToList(); res.AddRange(CheckVisitorModuleResults()); - return res; } @@ -242,54 +285,6 @@ private void SetState() #region Visitors - /// - /// Set the current camera, update View and Projection matrices - /// - /// - [VisitMethod] - public void UpdateCamera(Camera cam) - { - if (!cam.Active) return; - - CurrentCamera = cam; - - var view = State.Model; - var scale = float4x4.GetScale(State.View); - - if (scale.x != 1) - { - view.M11 /= scale.x; - view.M21 /= scale.x; - view.M31 /= scale.x; - } - - if (scale.y != 1) - { - view.M12 /= scale.y; - view.M22 /= scale.y; - view.M32 /= scale.y; - } - - if (scale.z != 1) - { - view.M13 /= scale.z; - view.M23 /= scale.z; - view.M33 /= scale.z; - } - - State.View = view.Invert(); - - var sizeInPx = CurrentCamera.RenderTexture == null ? - CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight) - : CurrentCamera.GetViewportInPx(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height); - - // TODO(mr): TEST Renderlayer - State.Projection = CurrentCamera.RenderTexture != null - ? CurrentCamera.GetProjectionMat(CurrentCamera.RenderTexture.Width, CurrentCamera.RenderTexture.Height, out var _) - : CurrentCamera.GetProjectionMat((int)sizeInPx.z, (int)sizeInPx.w, out var _); - - } - /// /// Sets the state of the model matrices and UiRects. /// @@ -315,14 +310,12 @@ public void RenderCanvasTransform(CanvasTransform ctc) } else if (ctc.CanvasRenderMode == CanvasRenderMode.Screen) { - var invProj = float4x4.Invert(State.Projection); - var frustumCorners = new float4[4]; - frustumCorners[0] = invProj * new float4(-1, -1, -1, 1); //nbl - frustumCorners[1] = invProj * new float4(1, -1, -1, 1); //nbr - frustumCorners[2] = invProj * new float4(-1, 1, -1, 1); //ntl - frustumCorners[3] = invProj * new float4(1, 1, -1, 1); //ntr + frustumCorners[0] = _invProj * new float4(-1, -1, -1, 1); //nbl + frustumCorners[1] = _invProj * new float4(1, -1, -1, 1); //nbr + frustumCorners[2] = _invProj * new float4(-1, 1, -1, 1); //ntl + frustumCorners[3] = _invProj * new float4(1, 1, -1, 1); //ntr for (var i = 0; i < frustumCorners.Length; i++) { @@ -335,7 +328,7 @@ public void RenderCanvasTransform(CanvasTransform ctc) var height = (frustumCorners[0] - frustumCorners[2]).Length; var zNear = frustumCorners[0].z; - var canvasPos = new float3(InvView.M14, InvView.M24, InvView.M34 + zNear); + var canvasPos = new float3(_invView.M14, _invView.M24, _invView.M34 + zNear); ctc.ScreenSpaceSize = new MinMaxRect { @@ -358,7 +351,7 @@ public void RenderCanvasTransform(CanvasTransform ctc) isCtcInitialized = true; } - State.CanvasXForm *= State.Model.Invert() * InvView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); + State.CanvasXForm *= State.Model.Invert() * _invView * float4x4.CreateTranslation(0, 0, zNear + (zNear * 0.01f)); State.Model *= State.CanvasXForm; _parentRect = newRect; @@ -435,7 +428,7 @@ public void RenderXForm(XForm xfc) [VisitMethod] public void RenderXFormText(XFormText xfc) { - var zNear = (InvProjection * new float4(-1, -1, -1, 1)).z; + var zNear = (_invProj * new float4(-1, -1, -1, 1)).z; var scaleFactor = zNear / 100; var invScaleFactor = 1 / scaleFactor; @@ -546,7 +539,7 @@ public void HandleMesh(Mesh mesh) { if (State?.CurrentPickComp?.CustomPickMethod != null) { - var res = State?.CurrentPickComp?.CustomPickMethod(mesh, CurrentNode, State.Model, State.View, State.Projection, PickPosClip); + var res = State?.CurrentPickComp?.CustomPickMethod(mesh, CurrentNode, State.Model, _view, _projection, PickPosClip); if (res != null) { YieldItem(res); @@ -562,7 +555,7 @@ public void HandleMesh(Mesh mesh) case PrimitiveType.TriangleFan: case PrimitiveType.TriangleStrip: if (State != null) - PickTriangleGeometry(mesh, State.Projection, State.View); + PickTriangleGeometry(mesh); break; case PrimitiveType.Lines: PickLineGeometry(mesh); @@ -582,7 +575,8 @@ public void HandleMesh(Mesh mesh) private void PickLineAdjacencyGeometry(Mesh mesh) { - var mvp = State.Projection * State.View * State.Model; + + var mvp = _projection * _view * State.Model; var matOfNode = CurrentNode.GetComponent(); if (matOfNode == null) { @@ -593,13 +587,13 @@ private void PickLineAdjacencyGeometry(Mesh mesh) if (mesh.Triangles == null) return; if (mesh.Vertices == null) return; - if (CurrentCamera == null) + if (CurrentCameraResult == null) { Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); return; } - var size = CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight); + var size = CurrentCameraResult.Camera.GetViewportInPx(_canvasWidth, _canvasHeight); var viewportHeight = size.w; var viewportWidth = size.z; var aspect = viewportHeight / viewportWidth; @@ -668,9 +662,9 @@ private void PickLineAdjacencyGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), - View = State.View, - Projection = State.Projection + ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + View = _view, + Projection = _projection }); } } @@ -678,7 +672,9 @@ private void PickLineAdjacencyGeometry(Mesh mesh) private void PickLineGeometry(Mesh mesh) { - var mvp = State.Projection * State.View * State.Model; + + var mvp = _projection * _view * State.Model; + var matOfNode = CurrentNode.GetComponent(); if (matOfNode == null) { @@ -689,12 +685,12 @@ private void PickLineGeometry(Mesh mesh) if (mesh.Triangles == null) return; if (mesh.Vertices == null) return; - if (CurrentCamera == null) + if (CurrentCameraResult == null) { Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); return; } - var size = CurrentCamera.GetViewportInPx(_canvasWidth, _canvasHeight); + var size = CurrentCameraResult.Camera.GetViewportInPx(_canvasWidth, _canvasHeight); var viewportHeight = size.w; var viewportWidth = size.z; var aspect = viewportHeight / viewportWidth; @@ -710,8 +706,6 @@ private void PickLineGeometry(Mesh mesh) var viewport_line_vector = lineVector * new float2(viewportWidth, viewportHeight); var dir = new float2(lineVector.x, lineVector.y * aspect).Normalize(); - var lineLength = viewport_line_vector.Length; - var normal = new float2(-dir.y, dir.x); var normal_a = new float2(line_width / viewportWidth, line_width / viewportHeight) * normal; var normal_b = new float2(line_width / viewportWidth, line_width / viewportHeight) * normal; @@ -729,9 +723,9 @@ private void PickLineGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), - View = State.View, - Projection = State.Projection + ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + View = _view, + Projection = _projection }); } @@ -766,9 +760,7 @@ private void PickLineGeometry(Mesh mesh) /// Pick triangle geometry via ray cast. /// /// - /// - /// - private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 viewMatrix) + private void PickTriangleGeometry(Mesh mesh) { if (mesh == null) return; if (mesh.Triangles == null) return; @@ -783,9 +775,9 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 if (mesh.BoundingBox.Size.x <= 0 || mesh.BoundingBox.Size.y <= 0 || mesh.BoundingBox.Size.z <= 0) { Diagnostics.Warn($"Current bounding box of {mesh} is smaller or equal to zero. Forcing a thickness in zero direction of >= float.Epsilon"); - var maxX = mesh.BoundingBox.Size.x <= 0 ? float.Epsilon : mesh.BoundingBox.max.x; - var maxY = mesh.BoundingBox.Size.y <= 0 ? float.Epsilon : mesh.BoundingBox.max.y; - var maxZ = mesh.BoundingBox.Size.z <= 0 ? float.Epsilon : mesh.BoundingBox.max.z; + var maxX = mesh.BoundingBox.Size.x <= 0 ? 0.1f : mesh.BoundingBox.max.x; + var maxY = mesh.BoundingBox.Size.y <= 0 ? 0.1f : mesh.BoundingBox.max.y; + var maxZ = mesh.BoundingBox.Size.z <= 0 ? 0.1f : mesh.BoundingBox.max.z; var minX = mesh.BoundingBox.Size.x <= 0 ? 0 : mesh.BoundingBox.min.x; var minY = mesh.BoundingBox.Size.y <= 0 ? 0 : mesh.BoundingBox.min.y; @@ -794,9 +786,9 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 mesh.BoundingBox = new AABBf(new float3(minX, minY, minZ), new float3(maxX, maxY, maxZ)); } - var ray = new RayF(PickPosClip, viewMatrix, projectionMatrix); + var ray = new RayF(PickPosClip, _view, _projection); + var box = State.Model * mesh.BoundingBox; - // does not work for Planes or Ortographic Cameras! if (!box.IntersectRay(ray)) return; @@ -815,11 +807,9 @@ private void PickTriangleGeometry(Mesh mesh, float4x4 projectionMatrix, float4x4 // Normal of the plane defined by a, b, and c. var n = float3.Normalize(float3.Cross(a - c, b - c)); - // Distance between "Origin" and the plane abc when following the Direction. var distance = -float3.Dot(ray.Origin - a, n) / float3.Dot(ray.Direction, n); - // does not work for Planes or Ortographic Cameras! if (distance < 0) continue; diff --git a/src/Engine/Core/SceneRendererForward.cs b/src/Engine/Core/SceneRendererForward.cs index 02643e706..1430421fa 100644 --- a/src/Engine/Core/SceneRendererForward.cs +++ b/src/Engine/Core/SceneRendererForward.cs @@ -71,7 +71,7 @@ private set private MinMaxRect _parentRect; private int _numberOfLights; - internal PrePassVisitor PrePassVisitor { get; private set; } + public PrePassVisitor PrePassVisitor { get; private set; } /// /// Caches SceneNodes and their model matrices. Used when visiting a . @@ -399,7 +399,7 @@ private void PerCamRender(CameraResult cam) /// protected void AccumulateLight() { - LightViseratorResults = PrePassVisitor.LightPrepassResuls; + LightViseratorResults = PrePassVisitor.LightPrepassResults; if (LightViseratorResults.Count == 0) SetDefaultLight(); diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index e7a150d96..41ab33500 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -25,10 +25,11 @@ public class SceneInteractionHandler : Visitor /// Initializes a new instance of the class. /// /// The scene the interaction handler belongs to. - public SceneInteractionHandler(SceneContainer scene) + /// The of the operation. + public SceneInteractionHandler(SceneContainer scene, IEnumerable prePassCameraResults) { IgnoreInactiveComponents = true; - _scenePicker = new ScenePicker(scene); + _scenePicker = new ScenePicker(scene, prePassCameraResults); } private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IList pickResults) @@ -64,9 +65,9 @@ private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IListCanvas height - needed to determine the mouse position in clip space. public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int canvasHeight) { - var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); + //var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); - var pickResults = _scenePicker.Pick(pickPosClip, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); + var pickResults = _scenePicker.Pick(mousePos, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); var pickResNodes = pickResults.ConvertAll(x => x.Node); var firstPickRes = pickResults.FirstOrDefault(); diff --git a/src/Engine/Player/Core/Player.cs b/src/Engine/Player/Core/Player.cs index b68165b93..5ab67f2a1 100644 --- a/src/Engine/Player/Core/Player.cs +++ b/src/Engine/Player/Core/Player.cs @@ -52,8 +52,6 @@ public async Task LoadAssets() _scene = await AssetStorage.GetAsync(ModelFile); _gui = await FuseeGuiHelper.CreateDefaultGuiAsync(this, _canvasRenderMode, "FUSEE Player"); - // Create the interaction handler - _sih = new SceneInteractionHandler(_gui); AABBCalculator aabbc = new(_scene); var bbox = aabbc.GetBox(); @@ -111,6 +109,9 @@ public async Task LoadAssets() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); _guiRenderer = new SceneRendererForward(_gui); + + // Create the interaction handler + _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } public override async Task InitAsync() diff --git a/src/Math/Core/AABBf.cs b/src/Math/Core/AABBf.cs index 764a1ac45..db2771102 100644 --- a/src/Math/Core/AABBf.cs +++ b/src/Math/Core/AABBf.cs @@ -229,8 +229,8 @@ public bool IntersectRay(RayF ray) if (this.Intersects(ray.Origin)) return true; - float t1 = (min[0] - ray.Origin[0]) * ray.Inverse[0]; - float t2 = (max[0] - ray.Origin[0]) * ray.Inverse[0]; + float t1 = (min.x - ray.Origin.x) * ray.Inverse.x; + float t2 = (max.x - ray.Origin.x) * ray.Inverse.x; float tmin = M.Min(t1, t2); float tmax = M.Max(t1, t2); diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index 68f7f5ec9..e762259f9 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -42,14 +42,17 @@ public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) { float4x4 invViewProjection = float4x4.Invert(projection * view); + var noTP = invViewProjection * new float3(pickPosClip.x, pickPosClip.y, 1); + var noTP2 = invViewProjection * new float3(pickPosClip.x, pickPosClip.y, 0); + var pickPosFarWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); - var pickPosNearWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 0)); + var pickPosNearWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, -1)); Direction = (pickPosFarWorld - pickPosNearWorld).Normalize(); Origin = pickPosNearWorld; Inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); - Inverse = new float3(float.IsInfinity(Inverse.x) ? 0 : Inverse.x, float.IsInfinity(Inverse.y) ? 0 : Inverse.y, float.IsInfinity(Inverse.z) ? 0 : Inverse.z); + //Inverse = new float3(float.IsInfinity(Inverse.x) ? 0 : Inverse.x, float.IsInfinity(Inverse.y) ? 0 : Inverse.y, float.IsInfinity(Inverse.z) ? 0 : Inverse.z); } } } \ No newline at end of file diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 8790dcf01..5598aa12e 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -18,7 +18,6 @@ namespace Fusee.PointCloud.Core.Scene public class PointCloudPickResult : PickResult { public OctantId OctandID; - } public class PointCloudPickerModule : IPickerModule @@ -27,7 +26,6 @@ public class PointCloudPickerModule : IPickerModule private PointCloudOctree _octree; private IPointCloudImp _pcImp; - public PickResult PickResult { get; set; } /// @@ -39,22 +37,22 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (!pointCloud.Active) return; - var ray = new RayD(new double2(PickerState.PickPosClip.x, PickerState.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); - var tmpList = new List(); - var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); - if (allHitBoxes != null && allHitBoxes.Any()) - { - var mvp = State.Projection * State.View * State.Model; - PickResult = new PointCloudPickResult - { - Node = null, - Projection = State.Projection, - View = State.View, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), - OctandID = allHitBoxes.ElementAt(0).OctId, - }; - } + //var ray = new RayD(new double2(PickerState.PickPosClip.x, PickerState.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); + //var tmpList = new List(); + //var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); + //if (allHitBoxes != null && allHitBoxes.Any()) + //{ + // var mvp = State.Projection * State.View * State.Model; + // PickResult = new PointCloudPickResult + // { + // Node = null, + // Projection = State.Projection, + // View = State.View, + // Model = State.Model, + // ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), + // OctandID = allHitBoxes.ElementAt(0).OctId, + // }; + //} } private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) From 8dc80e1efb73174842d0c7883e0b515130811f89 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 1 Mar 2023 17:09:15 +0100 Subject: [PATCH 076/294] Refactoring, comments, overhauled SceneRayCaster --- Examples/Complete/Picking/Desktop/Main.cs | 2 + .../PickingRayCast/Core/PickingRayCast.cs | 4 +- src/Engine/Core/Scene/PickComponent.cs | 2 +- src/Engine/Core/ScenePicker.cs | 83 +++++++----- src/Engine/Core/SceneRayCaster.cs | 127 ++++-------------- 5 files changed, 79 insertions(+), 139 deletions(-) diff --git a/Examples/Complete/Picking/Desktop/Main.cs b/Examples/Complete/Picking/Desktop/Main.cs index 3cea3f408..472fad234 100644 --- a/Examples/Complete/Picking/Desktop/Main.cs +++ b/Examples/Complete/Picking/Desktop/Main.cs @@ -4,6 +4,8 @@ using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Serialization; +using System.Collections.Generic; +using System; using System.IO; using System.Threading.Tasks; diff --git a/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs b/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs index 52e77bf0f..baa71c9c8 100644 --- a/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs +++ b/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs @@ -83,7 +83,7 @@ private async void Load() // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); - _sceneRayCaster = new SceneRayCaster(_scene, Cull.Clockwise); + _sceneRayCaster = new SceneRayCaster(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Cull.Clockwise); // Create the interaction handler _guiRenderer = new SceneRendererForward(_gui); @@ -147,7 +147,7 @@ public override void Update() // Check for hits if (_pick) { - var castHit = _sceneRayCaster.RayPick(RC, _pickPos).ToList().OrderBy(rr => rr.DistanceFromOrigin).FirstOrDefault(); + var castHit = _sceneRayCaster.RayPick(_pickPos, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(rr => rr.DistanceFromOrigin).FirstOrDefault(); if (castHit != null) castHit.Node.GetComponent().SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs index 3678e04a9..7e7936fbe 100644 --- a/src/Engine/Core/Scene/PickComponent.cs +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -9,7 +9,7 @@ namespace Fusee.Engine.Core.Scene public class PickComponent : SceneComponent { /// - /// Pick layer, on picking the result with the higher layer will be prefered + /// Pick layer, on picking the result with the higher layer will be preferred /// public int PickLayer { get; set; } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 381c727be..c14b373b8 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -1,5 +1,4 @@ using CommunityToolkit.Diagnostics; -using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core.Effects; @@ -23,11 +22,6 @@ public class PickResult /// public SceneNode? Node; - /// - /// The mesh. - /// - public Mesh? Mesh; - /// /// The model matrix. /// @@ -49,11 +43,43 @@ public class PickResult public float3 ClipPos; } + /// + /// A possible line . + /// Contains specific information for a line geometry. + /// + public class LinePickResult : PickResult + { + /// + /// The mesh. + /// + public Mesh? Mesh; + } + + /// + /// A possible mesh . + /// Contains specific information for a triangle geometry. + /// public class MeshPickResult : PickResult { + /// + /// The mesh. + /// + public Mesh? Mesh; + /// + /// The hit triangle. + /// public int Triangle; + /// + /// U coordinate. + /// public float U; + /// + /// V coordinate. + /// public float V; + /// + /// The distance from the (mouse position) to the . + /// public float DistanceFromOrigin; } @@ -82,6 +108,9 @@ public class PickerState : VisitorState private readonly CollapsingStateStack _cullMode = new(); private readonly CollapsingStateStack _shaderFX = new(); + /// + /// The current pick position in clip coordinate space. + /// public static float2 PickPosClip { get; set; } /// @@ -111,12 +140,6 @@ public float4x4 CanvasXForm set => _canvasXForm.Tos = value; } - public ShaderEffect ShaderEffect - { - get => _shaderFX.Tos; - set => _shaderFX.Tos = value; - } - /// /// The registered cull mode. /// @@ -126,6 +149,9 @@ public Cull CullMode set => _cullMode.Tos = value; } + /// + /// The currently bound optional which can be used for storing custom pick methods as well as a . + /// public PickComponent? CurrentPickComp; /// @@ -178,9 +204,10 @@ private set /// /// The constructor to initialize a new ScenePicker. /// - /// - /// /// The to pick from. + /// The collected from the functionality. + /// The 's mode. + /// Any custom s, e. g. a point cloud picker module. Has to be registered from "external" like Desktop.Core. public ScenePicker(SceneContainer scene, IEnumerable prePassCameraResults, Cull cullMode = Cull.None, IEnumerable? customPickModule = null) : base(scene.Children, customPickModule) { @@ -207,7 +234,7 @@ protected override void InitState() /// The width of the current canvas, gets overwrite if a is bound /// The height of the current canvas, gets overwrite if a is bound /// - public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasHeight) + public IEnumerable? Pick(float2 pickPos, int canvasWidth, int canvasHeight) { _canvasWidth = canvasWidth; _canvasHeight = canvasHeight; @@ -215,15 +242,8 @@ public IEnumerable Pick(float2 pickPos, int canvasWidth, int canvasH float2 pickPosClip; if (_prePassResults.Count() == 0) { - pickPosClip = (pickPos * new float2(2.0f / _canvasWidth, -2.0f / _canvasHeight)) + new float2(-1, 1); - - PickPosClip = pickPosClip; - PickerState.PickPosClip = pickPosClip; - - SetState(); - var resNoCam = Viserate().ToList(); - resNoCam.AddRange(CheckVisitorModuleResults()); - return resNoCam; + Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); + return null; } CameraResult pickCam = default; @@ -501,17 +521,6 @@ public void RenderTransform(Transform transform) State.Model *= transform.Matrix; } - /// - /// Save the current shader effect - /// Later we can check if we have a geometry shader source and use FBO picking - /// - /// - [VisitMethod] - public void RenderShaderEffect(ShaderEffect effect) - { - State.ShaderEffect = effect; - } - /// /// Handles custom pick component with pick layer and custom picking methods. /// If is not active, the picking is being skipped @@ -657,7 +666,7 @@ private void PickLineAdjacencyGeometry(Mesh mesh) if (float2.PointInTriangle(vert0.xy, vert1.xy, vert2.xy, PickPosClip, out _, out _) || float2.PointInTriangle(vert2.xy, vert1.xy, vert3.xy, PickPosClip, out _, out _)) { - YieldItem(new PickResult + YieldItem(new LinePickResult { Mesh = mesh, Node = CurrentNode, @@ -718,7 +727,7 @@ private void PickLineGeometry(Mesh mesh) if (float2.PointInTriangle(vert0.xy, vert1.xy, vert2.xy, PickPosClip, out _, out _) || float2.PointInTriangle(vert2.xy, vert1.xy, vert3.xy, PickPosClip, out _, out _)) { - YieldItem(new PickResult + YieldItem(new LinePickResult { Mesh = mesh, Node = CurrentNode, diff --git a/src/Engine/Core/SceneRayCaster.cs b/src/Engine/Core/SceneRayCaster.cs index 2e10a5650..d158c793c 100644 --- a/src/Engine/Core/SceneRayCaster.cs +++ b/src/Engine/Core/SceneRayCaster.cs @@ -1,91 +1,22 @@ -using Fusee.Engine.Common; +using Fusee.Base.Core; +using Fusee.Engine.Common; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.Xene; using System.Collections.Generic; +using System.Linq; namespace Fusee.Engine.Core { /// /// This class contains information about the scene of the picked point. /// - public class RayCastResult + public class RayCastResult : PickResult { /// - /// The scene node container of the result. + /// The mesh. /// - public SceneNode Node; - - /// - /// The picked mesh. - /// - public Mesh Mesh; - - /// - /// The index of the triangle in which the intersection of ray and mesh happened. - /// - public int Triangle; - - /// - /// The barycentric u, v coordinates within the picked triangle. - /// - public float U, V; - - /// - /// The (texture-) UV coordinates of the picked point. - /// - public float2 UV - { - get - { - float2 uva = Mesh.UVs[(int)Mesh.Triangles[Triangle]]; - float2 uvb = Mesh.UVs[(int)Mesh.Triangles[Triangle + 1]]; - float2 uvc = Mesh.UVs[(int)Mesh.Triangles[Triangle + 2]]; - - return float2.Barycentric(uva, uvb, uvc, U, V); - } - } - - /// - /// The model matrix. - /// - public float4x4 Model; - - /// - /// Gets the triangles of the picked mesh. - /// - /// - /// - /// - public void GetTriangle(out float3 a, out float3 b, out float3 c) - { - a = Mesh.Vertices[(int)Mesh.Triangles[Triangle + 0]]; - b = Mesh.Vertices[(int)Mesh.Triangles[Triangle + 1]]; - c = Mesh.Vertices[(int)Mesh.Triangles[Triangle + 2]]; - } - - /// - /// Returns the barycentric triangle coordinates. - /// - public float3 TriangleBarycentric - { - get - { - GetTriangle(out var a, out var b, out var c); - return float3.Barycentric(a, b, c, U, V); - } - } - - /// - /// Returns the model position. - /// - public float3 ModelPos => TriangleBarycentric; - - /// - /// Returns the world position of the intersection. - /// - public float3 WorldPos => float4x4.TransformPerspective(Model, ModelPos); - + public Mesh? Mesh; /// /// Returns the distance between ray origin and the intersection. /// @@ -112,7 +43,8 @@ public class SceneRayCaster : Viserator protected SceneContainer _sc; - internal PrePassVisitor PrePassVisitor { get; private set; } + + private readonly IEnumerable _prePassResults; #region State /// @@ -145,14 +77,14 @@ public RayCasterState() /// The constructor to initialize a new SceneRayCaster. /// /// The to use. + /// The collected from the functionality. /// The mode to use. - public SceneRayCaster(SceneContainer scene, Cull cullMode = Cull.None) + public SceneRayCaster(SceneContainer scene, IEnumerable prePassCameraResults, Cull cullMode = Cull.None) : base(scene.Children) { CullMode = cullMode; - - PrePassVisitor = new PrePassVisitor(); - _sc = scene; + _prePassResults = prePassCameraResults; + _sc = scene; } /// @@ -178,35 +110,32 @@ public IEnumerable RayCast(RayF ray) /// /// Returns a collection of objects that are hit by the ray and that can be iterated over. /// - /// - /// + /// The pick position in canvas coordinates (e.g. [1270x720]), usually .Position. + /// The width of the current canvas, gets overwrite if a is bound + /// The height of the current canvas, gets overwrite if a is bound /// - public IEnumerable RayPick(RenderContext rc, float2 pickPos) + public IEnumerable? RayPick(float2 pickPos, int canvasWidth, int canvasHeight) { - PrePassVisitor.PrePassTraverse(_sc); - var cams = PrePassVisitor.CameraPrepassResults; - float2 pickPosClip; - if (cams.Count == 0) + if (_prePassResults.Count() == 0) { - pickPosClip = (pickPos * new float2(2.0f / rc.ViewportWidth, -2.0f / rc.ViewportHeight)) + new float2(-1, 1); - Ray = new RayF(pickPosClip, rc.View, rc.Projection); - return Viserate(); + Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); + return null; } CameraResult pickCam = default; Rectangle pickCamRect = new(); - foreach (var camRes in cams) + foreach (var camRes in _prePassResults) { Rectangle camRect = new() { - Left = (int)(camRes.Camera.Viewport.x * rc.ViewportWidth / 100), - Top = (int)(camRes.Camera.Viewport.y * rc.ViewportHeight / 100) + Left = (int)(camRes.Camera.Viewport.x * canvasWidth / 100), + Top = (int)(camRes.Camera.Viewport.y * canvasHeight / 100) }; - camRect.Right = ((int)(camRes.Camera.Viewport.z * rc.ViewportWidth) / 100) + camRect.Left; - camRect.Bottom = ((int)(camRes.Camera.Viewport.w * rc.ViewportHeight) / 100) + camRect.Top; + camRect.Right = ((int)(camRes.Camera.Viewport.z * canvasWidth) / 100) + camRect.Left; + camRect.Bottom = ((int)(camRes.Camera.Viewport.w * canvasHeight) / 100) + camRect.Top; if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) continue; @@ -219,7 +148,7 @@ public IEnumerable RayPick(RenderContext rc, float2 pickPos) // Calculate pickPosClip pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); - Ray = new RayF(pickPosClip, pickCam.View, pickCam.Camera.GetProjectionMat(rc.ViewportWidth, rc.ViewportHeight, out _)); + Ray = new RayF(pickPosClip, pickCam.View, pickCam.Camera.GetProjectionMat(canvasWidth, canvasHeight, out _)); return Viserate(); } @@ -243,7 +172,10 @@ public void RenderTransform(Transform transform) [VisitMethod] public void HitMesh(Mesh mesh) { + if (mesh == null) return; if (!mesh.Active) return; + if (mesh.Vertices == null) return; + if (mesh.Triangles == null) return; AABBf box = State.Model * mesh.BoundingBox; if (!box.IntersectRay(Ray)) return; @@ -280,10 +212,7 @@ public void HitMesh(Mesh mesh) { Mesh = mesh, Node = CurrentNode, - Triangle = i, Model = State.Model, - U = u, - V = v, DistanceFromOrigin = distance }); } From 87a9792eb47b3edffe63020914e0341e9ccefe89 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 1 Mar 2023 17:21:05 +0100 Subject: [PATCH 077/294] Remove picking functionality from SceneRayCaster --- .../PickingRayCast/Core/PickingRayCast.cs | 33 ++++++++++++++- src/Engine/Core/SceneRayCaster.cs | 42 ++----------------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs b/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs index baa71c9c8..2b1d54b0c 100644 --- a/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs +++ b/Examples/Complete/PickingRayCast/Core/PickingRayCast.cs @@ -147,7 +147,38 @@ public override void Update() // Check for hits if (_pick) { - var castHit = _sceneRayCaster.RayPick(_pickPos, RC.ViewportWidth, RC.ViewportHeight).ToList().OrderBy(rr => rr.DistanceFromOrigin).FirstOrDefault(); + // prepare mouse coordinates; for each camera calculate clip space mouse position and create a new ray which travels through the scene and collects meshes + CameraResult pickCam = default; + Rectangle pickCamRect = new(); + + // check in which camera our mouse is currently positioned (left or right) + foreach (var camRes in _sceneRenderer.PrePassVisitor.CameraPrepassResults) + { + Rectangle camRect = new() + { + Left = (int)(camRes.Camera.Viewport.x * RC.ViewportWidth / 100), + Top = (int)(camRes.Camera.Viewport.y * RC.ViewportHeight / 100) + }; + camRect.Right = ((int)(camRes.Camera.Viewport.z * RC.ViewportWidth) / 100) + camRect.Left; + camRect.Bottom = ((int)(camRes.Camera.Viewport.w * RC.ViewportHeight) / 100) + camRect.Top; + + if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), Input.Mouse.Position)) + continue; + + if (pickCam == default || camRes.Camera.Layer > pickCam.Camera.Layer) + { + pickCam = camRes; + pickCamRect = camRect; + } + } + + // generate fitting clip position with the currently used camera rectangle + var pickPosClip = ((Input.Mouse.Position - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); + + // generate ray at mouse position and... + var ray = new RayF(pickPosClip, pickCam.View, pickCam.Camera.GetProjectionMat(RC.ViewportWidth, RC.ViewportHeight, out var _)); + // ... send it through the scene + var castHit = _sceneRayCaster.Traverse(ray).ToList().OrderBy(rr => rr.DistanceFromOrigin).FirstOrDefault(); if (castHit != null) castHit.Node.GetComponent().SurfaceInput.Albedo = (float4)ColorUint.LawnGreen; diff --git a/src/Engine/Core/SceneRayCaster.cs b/src/Engine/Core/SceneRayCaster.cs index d158c793c..bfd82a48b 100644 --- a/src/Engine/Core/SceneRayCaster.cs +++ b/src/Engine/Core/SceneRayCaster.cs @@ -43,7 +43,6 @@ public class SceneRayCaster : Viserator protected SceneContainer _sc; - private readonly IEnumerable _prePassResults; #region State @@ -110,46 +109,11 @@ public IEnumerable RayCast(RayF ray) /// /// Returns a collection of objects that are hit by the ray and that can be iterated over. /// - /// The pick position in canvas coordinates (e.g. [1270x720]), usually .Position. - /// The width of the current canvas, gets overwrite if a is bound - /// The height of the current canvas, gets overwrite if a is bound + /// The which should travel through the scene /// - public IEnumerable? RayPick(float2 pickPos, int canvasWidth, int canvasHeight) + public IEnumerable? Traverse(RayF ray) { - float2 pickPosClip; - - if (_prePassResults.Count() == 0) - { - Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); - return null; - } - - CameraResult pickCam = default; - Rectangle pickCamRect = new(); - - foreach (var camRes in _prePassResults) - { - Rectangle camRect = new() - { - Left = (int)(camRes.Camera.Viewport.x * canvasWidth / 100), - Top = (int)(camRes.Camera.Viewport.y * canvasHeight / 100) - }; - camRect.Right = ((int)(camRes.Camera.Viewport.z * canvasWidth) / 100) + camRect.Left; - camRect.Bottom = ((int)(camRes.Camera.Viewport.w * canvasHeight) / 100) + camRect.Top; - - if (!float2.PointInRectangle(new float2(camRect.Left, camRect.Top), new float2(camRect.Right, camRect.Bottom), pickPos)) continue; - - if (pickCam == default || camRes.Camera.Layer > pickCam.Camera.Layer) - { - pickCam = camRes; - pickCamRect = camRect; - } - } - - // Calculate pickPosClip - pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); - Ray = new RayF(pickPosClip, pickCam.View, pickCam.Camera.GetProjectionMat(canvasWidth, canvasHeight, out _)); - + Ray = ray; return Viserate(); } From 04836d721552c645f3c7a2ae76aed4fd7a79b66f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 1 Mar 2023 17:45:38 +0100 Subject: [PATCH 078/294] Null checks for default cameras and transform --- src/Engine/Core/ScenePicker.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index c14b373b8..428057d0d 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -189,7 +189,7 @@ private set Guard.IsGreaterThan(_canvasHeight, 0); _currentCameraResult = value; - _projection = _currentCameraResult.Camera.GetProjectionMat(_canvasWidth, _canvasHeight, out _); + _projection = _currentCameraResult.Camera == null ? float4x4.Identity : _currentCameraResult.Camera.GetProjectionMat(_canvasWidth, _canvasHeight, out _); _view = _currentCameraResult.View; _invView = _view.Invert(); _invProj = _projection.Invert(); @@ -242,7 +242,7 @@ protected override void InitState() float2 pickPosClip; if (_prePassResults.Count() == 0) { - Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); + //Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); return null; } @@ -602,6 +602,9 @@ private void PickLineAdjacencyGeometry(Mesh mesh) return; } + if (CurrentCameraResult.Camera == default) + return; + var size = CurrentCameraResult.Camera.GetViewportInPx(_canvasWidth, _canvasHeight); var viewportHeight = size.w; var viewportWidth = size.z; @@ -699,6 +702,10 @@ private void PickLineGeometry(Mesh mesh) Diagnostics.Warn("No camera found in SceneGraph, no picking possible!"); return; } + + if (CurrentCameraResult.Camera == default) + return; + var size = CurrentCameraResult.Camera.GetViewportInPx(_canvasWidth, _canvasHeight); var viewportHeight = size.w; var viewportWidth = size.z; @@ -732,7 +739,7 @@ private void PickLineGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + ClipPos = float4x4.TransformPerspective(_projection * _view, State.Model.Translation()), View = _view, Projection = _projection }); From 0765820e2d44775a07114dedfcd526535469bf14 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 1 Mar 2023 18:12:27 +0100 Subject: [PATCH 079/294] Houskeeping --- src/Engine/Core/ScenePicker.cs | 2 +- src/Engine/GUI/SceneInteractionHandler.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 428057d0d..cbcfe71ae 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -242,7 +242,7 @@ protected override void InitState() float2 pickPosClip; if (_prePassResults.Count() == 0) { - //Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); + Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); return null; } diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index 41ab33500..ec7da26e4 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -65,8 +65,6 @@ private static SceneNode FindLeafNodeInPickRes(SceneNode firstPickRes, IListCanvas height - needed to determine the mouse position in clip space. public void CheckForInteractiveObjects(float2 mousePos, int canvasWidth, int canvasHeight) { - //var pickPosClip = (mousePos * new float2(2.0f / canvasWidth, -2.0f / canvasHeight)) + new float2(-1, 1); - var pickResults = _scenePicker.Pick(mousePos, canvasWidth, canvasHeight).ToList().OrderBy(pr => pr.ClipPos.z).ToList(); var pickResNodes = pickResults.ConvertAll(x => x.Node); var firstPickRes = pickResults.FirstOrDefault(); From cc9b4f5418559f18cf5e158b5f7937d142ad9945 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 2 Mar 2023 11:34:05 +0100 Subject: [PATCH 080/294] Remove commented code, added missing attributes to MeshPickResult, housekeeping --- src/Engine/Core/ScenePicker.cs | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 428057d0d..f1b72a53c 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -242,7 +242,7 @@ protected override void InitState() float2 pickPosClip; if (_prePassResults.Count() == 0) { - //Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); + Diagnostics.Error("No camera from a PrePassVisitor found. Picking not possible!"); return null; } @@ -716,10 +716,8 @@ private void PickLineGeometry(Mesh mesh) { var pt0 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 0]]).xy; var pt1 = float4x4.TransformPerspective(mvp, mesh.Vertices[(int)mesh.Triangles[i + 1]]).xy; - var pt = PickerState.PickPosClip; var lineVector = pt1 - pt0; - var viewport_line_vector = lineVector * new float2(viewportWidth, viewportHeight); var dir = new float2(lineVector.x, lineVector.y * aspect).Normalize(); var normal = new float2(-dir.y, dir.x); @@ -744,31 +742,6 @@ private void PickLineGeometry(Mesh mesh) Projection = _projection }); } - - //// see: https://math.stackexchange.com/a/3633025 - //// for calculation, does not work :( - //var nom = (pt.x - pt0.x) * (pt1.x - pt0.x) + (pt.y - pt0.y) * (pt1.y - pt0.y); - //var denom = MathF.Pow((pt1.x - pt.x), 2) + MathF.Pow((pt1.y - pt0.y), 2); - //var t = nom / denom; - - //// point is somewhere on the line - //if(t >= 0 && t <= 1) - //{ - // var dSqrd = MathF.Pow(pt.x - pt0.x - t * (pt1.x - pt0.x), 2) + MathF.Pow(pt.y - pt0.y - t * (pt1.y - pt0.y), 2); - - // if(dSqrd < 0.25 * (w * w)) { - - // YieldItem(new PickResult - // { - // Mesh = mesh, - // Node = CurrentNode, - // Model = State.Model, - // ClipPos = float4x4.TransformPerspective(State.Projection * State.View, CurrentNode.GetTransform().Translation), - // View = State.View, - // Projection = State.Projection - // }); - // } - //} } } @@ -844,6 +817,9 @@ private void PickTriangleGeometry(Mesh mesh) Model = State.Model, U = u, V = v, + ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + Projection = _projection, + View = _view, DistanceFromOrigin = distance }); } From 3d547a58e8d99490cf8af414084ab8aad05e9b8b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 2 Mar 2023 11:37:11 +0100 Subject: [PATCH 081/294] PickerState.PickPos changed to internal set --- src/Engine/Core/ScenePicker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index f1b72a53c..52fdca6b3 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -111,7 +111,7 @@ public class PickerState : VisitorState /// /// The current pick position in clip coordinate space. /// - public static float2 PickPosClip { get; set; } + public static float2 PickPosClip { get; internal set; } /// /// The registered model. From 1c31056b93893ec9cb31aca0779aaa0a0fce95ee Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 2 Mar 2023 11:57:22 +0100 Subject: [PATCH 082/294] Fixed merge errors (again) --- src/Engine/Core/ScenePicker.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 52fdca6b3..8b67f9974 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -157,7 +157,6 @@ public Cull CullMode /// /// The default constructor for the class, which registers state stacks for model, UI rectangle, and canvas transform, as well as cull mode. /// - // TODO: Ask @CMl why this is being called after every Iteration/Viseration public PickerState() { RegisterState(_model); @@ -674,7 +673,7 @@ private void PickLineAdjacencyGeometry(Mesh mesh) Mesh = mesh, Node = CurrentNode, Model = State.Model, - ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + ClipPos = float4x4.TransformPerspective(_projection * _view, State.Model.Translation()), View = _view, Projection = _projection }); @@ -817,7 +816,7 @@ private void PickTriangleGeometry(Mesh mesh) Model = State.Model, U = u, V = v, - ClipPos = float4x4.TransformPerspective(_projection * _view, CurrentNode.GetTransform().Translation), + ClipPos = float4x4.TransformPerspective(_projection * _view, State.Model.Translation()), Projection = _projection, View = _view, DistanceFromOrigin = distance From ef4e374770ca4a148490d983e8bad99c5aab17a5 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 2 Mar 2023 16:10:20 +0100 Subject: [PATCH 083/294] First rudimentary point cloud picking --- .../Core/PointCloudPotree2Core.cs | 27 +++++++-- src/Engine/Core/ScenePicker.cs | 18 +++++- .../Core/Scene/PointCloudPickerModule.cs | 56 +++++++++++++------ 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 0d57e8489..d039eb3a0 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -1,6 +1,7 @@ using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Core; +using Fusee.Engine.Core.Primitives; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; @@ -83,8 +84,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeData = new PotreeData(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); _potreeReader = new Potree2Reader(); + _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile), out _potreeData); _rc = rc; } @@ -159,6 +160,15 @@ public void Init() Children = new List() { _camNode, + new SceneNode + { + Components = new List() + { + _debugTransform, + MakeEffect.FromDiffuse(new float4(1,0,0,1)), + new Cube() + } + }, _pointCloudNode } }; @@ -206,6 +216,8 @@ public void RenderAFrame() _sceneRenderer.Render(_rc); } + private Transform _debugTransform = new(); + public void Update(bool allowInput) { if (!allowInput) return; @@ -251,11 +263,18 @@ public void Update(bool allowInput) if (!_keys && Input.Mouse.LeftButton) { + _debugTransform.Translation = float3.Zero; + _debugTransform.Scale = float3.One * 0.001f; + var width = _rc.ViewportWidth; var height = _rc.ViewportHeight; - var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); - var result = _picker?.Pick(pickPosClip, width, height).ToList(); - if (result != null) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); + var result = _picker?.Pick(Input.Mouse.Position, width, height).ToList(); + if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) + { + _debugTransform.Translation = (float3)ppr.Octant.Center; + _debugTransform.Scale = new float3((float)ppr.Octant.Size); + } + } } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 8b67f9974..9e72df61e 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -111,7 +111,7 @@ public class PickerState : VisitorState /// /// The current pick position in clip coordinate space. /// - public static float2 PickPosClip { get; internal set; } + public float2 PickPosClip { get; internal set; } /// /// The registered model. @@ -122,6 +122,16 @@ public float4x4 Model get => _model.Tos; } + /// + /// The currently used camera result + /// + public CameraResult CurrentCameraResult { get; internal set; } + + /// + /// The current screen size + /// + public int2 ScreenSize { get; internal set; } + /// /// The registered UI rectangle. /// @@ -272,7 +282,11 @@ protected override void InitState() pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); PickPosClip = pickPosClip; - PickerState.PickPosClip = pickPosClip; + + // Update state values for VisitorModules + State.PickPosClip = pickPosClip; + State.CurrentCameraResult = pickCam; + State.ScreenSize = new int2(pickCamRect.Width, pickCamRect.Height); SetState(); var res = Viserate().ToList(); diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 5598aa12e..98848c67d 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -12,12 +12,14 @@ using Microsoft.Extensions.Options; using Fusee.PointCloud.Common; using Fusee.Base.Core; +using Fusee.Structures; namespace Fusee.PointCloud.Core.Scene { public class PointCloudPickResult : PickResult { public OctantId OctandID; + public PointCloudOctant Octant; } public class PointCloudPickerModule : IPickerModule @@ -37,27 +39,47 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (!pointCloud.Active) return; - //var ray = new RayD(new double2(PickerState.PickPosClip.x, PickerState.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); - //var tmpList = new List(); - //var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); - //if (allHitBoxes != null && allHitBoxes.Any()) - //{ - // var mvp = State.Projection * State.View * State.Model; - // PickResult = new PointCloudPickResult - // { - // Node = null, - // Projection = State.Projection, - // View = State.View, - // Model = State.Model, - // ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), - // OctandID = allHitBoxes.ElementAt(0).OctId, - // }; - //} + var proj = (double4x4)State.CurrentCameraResult.Camera.GetProjectionMat(State.ScreenSize.x, State.ScreenSize.y, out _); + var view = (double4x4)State.CurrentCameraResult.View; + var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), view, proj); + + var tmpList = new List(); + var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).ToList(); + + if (allHitBoxes == null || allHitBoxes.Count == 0) return; + foreach (var box in allHitBoxes) + box.ComputeScreenProjectedSize(view.Translation(), State.ScreenSize.y, State.CurrentCameraResult.Camera.Fov, (float3)box.Center, new float3((float)box.Size)); + var octant = allHitBoxes.OrderBy(x => x.ProjectedScreenSize).ToList()[0]; + + //foreach (var box in allHitBoxes) + // if ((float)box.Center.x - State.View.Translation().x < (float)octant.Center.x - State.View.Translation().x + // && (float)box.Center.y - State.View.Translation().y < (float)octant.Center.y - State.View.Translation().y + // && (float)box.Center.z - State.View.Translation().z < (float)octant.Center.z - State.View.Translation().z) + // octant = box; + + if (allHitBoxes != null && allHitBoxes.Any()) + { + var mvp = (float4x4)proj * (float4x4)view * State.Model; + PickResult = new PointCloudPickResult + { + Node = null, + Projection = (float4x4)proj, + View = (float4x4)view, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), + OctandID = octant.OctId, + Octant = octant + }; + } } private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) { - list.Add(node); + if (node?.IsVisible == true && node.IntersectRay(ray)) + { + list.Add(node); + } + if (node.Children[0] != null) { foreach (var child in node.Children.Cast()) From 26750eb81e1113741ec5e76dec311119087c3035 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 3 Mar 2023 16:54:29 +0100 Subject: [PATCH 084/294] Bump NUnit.Analyzers from 3.5.0 to 3.6.0 --- src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj index 14bfac019..d359c7267 100644 --- a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj +++ b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj @@ -29,7 +29,10 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 10b52a707012e4e082e4dc6ac754b6e7e5b82418 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 3 Mar 2023 16:56:03 +0100 Subject: [PATCH 085/294] Bump OpenTK from 4.7.5 to 4.7.7 --- .../Graphics/Desktop/Fusee.Engine.Imp.Graphics.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Engine/Imp/Graphics/Desktop/Fusee.Engine.Imp.Graphics.Desktop.csproj b/src/Engine/Imp/Graphics/Desktop/Fusee.Engine.Imp.Graphics.Desktop.csproj index b2701eb43..cbf83a344 100644 --- a/src/Engine/Imp/Graphics/Desktop/Fusee.Engine.Imp.Graphics.Desktop.csproj +++ b/src/Engine/Imp/Graphics/Desktop/Fusee.Engine.Imp.Graphics.Desktop.csproj @@ -27,7 +27,7 @@ analyzers - + From 45e13ffcf77662b91a8c86ab6cd0a99387c21828 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 3 Mar 2023 17:31:44 +0100 Subject: [PATCH 086/294] Bump Google.Protobuf.Tools from 3.21.9 to 3.22.0 --- .../Python/google/protobuf/__init__.py | 2 +- .../Python/google/protobuf/any_pb2.py | 9 +- .../Python/google/protobuf/api_pb2.py | 17 +- .../google/protobuf/compiler/plugin_pb2.py | 31 +- .../Python/google/protobuf/descriptor.py | 72 +- .../Python/google/protobuf/descriptor_pb2.py | 330 +- .../Python/google/protobuf/descriptor_pool.py | 35 +- .../Python/google/protobuf/duration_pb2.py | 9 +- .../Python/google/protobuf/empty_pb2.py | 9 +- .../Python/google/protobuf/field_mask_pb2.py | 9 +- .../google/protobuf/internal/__init__.py | 30 + .../protobuf/internal/_parameterized.py | 26 +- .../protobuf/internal/api_implementation.py | 7 +- .../google/protobuf/internal/decoder.py | 3 +- .../internal/descriptor_database_test.py | 127 - .../protobuf/internal/descriptor_pool_test.py | 1149 ------ .../protobuf/internal/descriptor_test.py | 1076 ------ .../protobuf/internal/extension_dict.py | 5 +- .../google/protobuf/internal/field_mask.py | 333 ++ .../protobuf/internal/generator_test.py | 354 -- .../google/protobuf/internal/import_test.py | 49 - .../internal/import_test_package/__init__.py | 33 - .../protobuf/internal/json_format_test.py | 1285 ------- .../google/protobuf/internal/keywords_test.py | 103 - .../protobuf/internal/message_factory_test.py | 299 -- .../google/protobuf/internal/message_test.py | 2572 ------------- .../protobuf/internal/proto_builder_test.py | 106 - .../protobuf/internal/python_message.py | 75 +- .../protobuf/internal/reflection_test.py | 3381 ----------------- .../internal/service_reflection_test.py | 139 - .../protobuf/internal/symbol_database_test.py | 133 - .../google/protobuf/internal/test_util.py | 878 ----- .../protobuf/internal/text_encoding_test.py | 67 - .../protobuf/internal/text_format_test.py | 2447 ------------ .../google/protobuf/internal/type_checkers.py | 10 +- .../protobuf/internal/unknown_fields_test.py | 461 --- .../protobuf/internal/well_known_types.py | 304 +- .../internal/well_known_types_test.py | 1013 ----- .../google/protobuf/internal/wire_format.py | 2 +- .../protobuf/internal/wire_format_test.py | 252 -- .../Python/google/protobuf/json_format.py | 73 +- .../Python/google/protobuf/message.py | 15 +- .../Python/google/protobuf/message_factory.py | 184 +- .../Python/google/protobuf/proto_builder.py | 20 +- .../Python/google/protobuf/reflection.py | 2 +- .../google/protobuf/source_context_pb2.py | 9 +- .../Python/google/protobuf/struct_pb2.py | 25 +- .../Python/google/protobuf/symbol_database.py | 28 +- .../Python/google/protobuf/text_encoding.py | 6 +- .../Python/google/protobuf/text_format.py | 254 +- .../Python/google/protobuf/timestamp_pb2.py | 9 +- .../Python/google/protobuf/type_pb2.py | 37 +- .../Python/google/protobuf/wrappers_pb2.py | 41 +- ext/protobuf/Python/six.py | 18 +- .../addons/io_export_fus/requirements.txt | 2 +- src/Tools/CmdLine/Fusee.Tools.CmdLine.csproj | 2 +- 56 files changed, 1214 insertions(+), 16753 deletions(-) delete mode 100644 ext/protobuf/Python/google/protobuf/internal/descriptor_database_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/descriptor_pool_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/descriptor_test.py create mode 100644 ext/protobuf/Python/google/protobuf/internal/field_mask.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/generator_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/import_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/import_test_package/__init__.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/json_format_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/keywords_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/message_factory_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/message_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/proto_builder_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/reflection_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/service_reflection_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/symbol_database_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/test_util.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/text_encoding_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/text_format_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/unknown_fields_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/well_known_types_test.py delete mode 100644 ext/protobuf/Python/google/protobuf/internal/wire_format_test.py diff --git a/ext/protobuf/Python/google/protobuf/__init__.py b/ext/protobuf/Python/google/protobuf/__init__.py index e7b197e5d..a301349e3 100644 --- a/ext/protobuf/Python/google/protobuf/__init__.py +++ b/ext/protobuf/Python/google/protobuf/__init__.py @@ -30,4 +30,4 @@ # Copyright 2007 Google Inc. All Rights Reserved. -__version__ = '4.21.9' +__version__ = '4.22.0' diff --git a/ext/protobuf/Python/google/protobuf/any_pb2.py b/ext/protobuf/Python/google/protobuf/any_pb2.py index a3aea5eae..c43ebb48f 100644 --- a/ext/protobuf/Python/google/protobuf/any_pb2.py +++ b/ext/protobuf/Python/google/protobuf/any_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19google/protobuf/any.proto\x12\x0fgoogle.protobuf\"6\n\x03\x41ny\x12\x19\n\x08type_url\x18\x01 \x01(\tR\x07typeUrl\x12\x14\n\x05value\x18\x02 \x01(\x0cR\x05valueBv\n\x13\x63om.google.protobufB\x08\x41nyProtoP\x01Z,google.golang.org/protobuf/types/known/anypb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.any_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.any_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\010AnyProtoP\001Z,google.golang.org/protobuf/types/known/anypb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _ANY._serialized_start=46 - _ANY._serialized_end=100 + _globals['_ANY']._serialized_start=46 + _globals['_ANY']._serialized_end=100 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/api_pb2.py b/ext/protobuf/Python/google/protobuf/api_pb2.py index 8c3cec53a..70233ef40 100644 --- a/ext/protobuf/Python/google/protobuf/api_pb2.py +++ b/ext/protobuf/Python/google/protobuf/api_pb2.py @@ -17,16 +17,17 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19google/protobuf/api.proto\x12\x0fgoogle.protobuf\x1a$google/protobuf/source_context.proto\x1a\x1agoogle/protobuf/type.proto\"\xc1\x02\n\x03\x41pi\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x31\n\x07methods\x18\x02 \x03(\x0b\x32\x17.google.protobuf.MethodR\x07methods\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x18\n\x07version\x18\x04 \x01(\tR\x07version\x12\x45\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12.\n\x06mixins\x18\x06 \x03(\x0b\x32\x16.google.protobuf.MixinR\x06mixins\x12/\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"\xb2\x02\n\x06Method\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12(\n\x10request_type_url\x18\x02 \x01(\tR\x0erequestTypeUrl\x12+\n\x11request_streaming\x18\x03 \x01(\x08R\x10requestStreaming\x12*\n\x11response_type_url\x18\x04 \x01(\tR\x0fresponseTypeUrl\x12-\n\x12response_streaming\x18\x05 \x01(\x08R\x11responseStreaming\x12\x31\n\x07options\x18\x06 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12/\n\x06syntax\x18\x07 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"/\n\x05Mixin\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04root\x18\x02 \x01(\tR\x04rootBv\n\x13\x63om.google.protobufB\x08\x41piProtoP\x01Z,google.golang.org/protobuf/types/known/apipb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.api_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.api_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\010ApiProtoP\001Z,google.golang.org/protobuf/types/known/apipb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _API._serialized_start=113 - _API._serialized_end=434 - _METHOD._serialized_start=437 - _METHOD._serialized_end=743 - _MIXIN._serialized_start=745 - _MIXIN._serialized_end=792 + _globals['_API']._serialized_start=113 + _globals['_API']._serialized_end=434 + _globals['_METHOD']._serialized_start=437 + _globals['_METHOD']._serialized_end=743 + _globals['_MIXIN']._serialized_start=745 + _globals['_MIXIN']._serialized_end=792 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/compiler/plugin_pb2.py b/ext/protobuf/Python/google/protobuf/compiler/plugin_pb2.py index 6684ec269..7dc1e3861 100644 --- a/ext/protobuf/Python/google/protobuf/compiler/plugin_pb2.py +++ b/ext/protobuf/Python/google/protobuf/compiler/plugin_pb2.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: google/protobuf/compiler/plugin.proto +# source: src/google/protobuf/compiler/plugin.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor @@ -14,22 +14,23 @@ from google.protobuf import descriptor_pb2 as google_dot_protobuf_dot_descriptor__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%google/protobuf/compiler/plugin.proto\x12\x18google.protobuf.compiler\x1a google/protobuf/descriptor.proto\"c\n\x07Version\x12\x14\n\x05major\x18\x01 \x01(\x05R\x05major\x12\x14\n\x05minor\x18\x02 \x01(\x05R\x05minor\x12\x14\n\x05patch\x18\x03 \x01(\x05R\x05patch\x12\x16\n\x06suffix\x18\x04 \x01(\tR\x06suffix\"\xf1\x01\n\x14\x43odeGeneratorRequest\x12(\n\x10\x66ile_to_generate\x18\x01 \x03(\tR\x0e\x66ileToGenerate\x12\x1c\n\tparameter\x18\x02 \x01(\tR\tparameter\x12\x43\n\nproto_file\x18\x0f \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\tprotoFile\x12L\n\x10\x63ompiler_version\x18\x03 \x01(\x0b\x32!.google.protobuf.compiler.VersionR\x0f\x63ompilerVersion\"\x94\x03\n\x15\x43odeGeneratorResponse\x12\x14\n\x05\x65rror\x18\x01 \x01(\tR\x05\x65rror\x12-\n\x12supported_features\x18\x02 \x01(\x04R\x11supportedFeatures\x12H\n\x04\x66ile\x18\x0f \x03(\x0b\x32\x34.google.protobuf.compiler.CodeGeneratorResponse.FileR\x04\x66ile\x1a\xb1\x01\n\x04\x46ile\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\'\n\x0finsertion_point\x18\x02 \x01(\tR\x0einsertionPoint\x12\x18\n\x07\x63ontent\x18\x0f \x01(\tR\x07\x63ontent\x12R\n\x13generated_code_info\x18\x10 \x01(\x0b\x32\".google.protobuf.GeneratedCodeInfoR\x11generatedCodeInfo\"8\n\x07\x46\x65\x61ture\x12\x10\n\x0c\x46\x45\x41TURE_NONE\x10\x00\x12\x1b\n\x17\x46\x45\x41TURE_PROTO3_OPTIONAL\x10\x01\x42W\n\x1c\x63om.google.protobuf.compilerB\x0cPluginProtosZ)google.golang.org/protobuf/types/pluginpb') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n)src/google/protobuf/compiler/plugin.proto\x12\x18google.protobuf.compiler\x1a google/protobuf/descriptor.proto\"c\n\x07Version\x12\x14\n\x05major\x18\x01 \x01(\x05R\x05major\x12\x14\n\x05minor\x18\x02 \x01(\x05R\x05minor\x12\x14\n\x05patch\x18\x03 \x01(\x05R\x05patch\x12\x16\n\x06suffix\x18\x04 \x01(\tR\x06suffix\"\xf1\x01\n\x14\x43odeGeneratorRequest\x12(\n\x10\x66ile_to_generate\x18\x01 \x03(\tR\x0e\x66ileToGenerate\x12\x1c\n\tparameter\x18\x02 \x01(\tR\tparameter\x12\x43\n\nproto_file\x18\x0f \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\tprotoFile\x12L\n\x10\x63ompiler_version\x18\x03 \x01(\x0b\x32!.google.protobuf.compiler.VersionR\x0f\x63ompilerVersion\"\x94\x03\n\x15\x43odeGeneratorResponse\x12\x14\n\x05\x65rror\x18\x01 \x01(\tR\x05\x65rror\x12-\n\x12supported_features\x18\x02 \x01(\x04R\x11supportedFeatures\x12H\n\x04\x66ile\x18\x0f \x03(\x0b\x32\x34.google.protobuf.compiler.CodeGeneratorResponse.FileR\x04\x66ile\x1a\xb1\x01\n\x04\x46ile\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\'\n\x0finsertion_point\x18\x02 \x01(\tR\x0einsertionPoint\x12\x18\n\x07\x63ontent\x18\x0f \x01(\tR\x07\x63ontent\x12R\n\x13generated_code_info\x18\x10 \x01(\x0b\x32\".google.protobuf.GeneratedCodeInfoR\x11generatedCodeInfo\"8\n\x07\x46\x65\x61ture\x12\x10\n\x0c\x46\x45\x41TURE_NONE\x10\x00\x12\x1b\n\x17\x46\x45\x41TURE_PROTO3_OPTIONAL\x10\x01\x42r\n\x1c\x63om.google.protobuf.compilerB\x0cPluginProtosZ)google.golang.org/protobuf/types/pluginpb\xaa\x02\x18Google.Protobuf.Compiler') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.compiler.plugin_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'src.google.protobuf.compiler.plugin_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\034com.google.protobuf.compilerB\014PluginProtosZ)google.golang.org/protobuf/types/pluginpb' - _VERSION._serialized_start=101 - _VERSION._serialized_end=200 - _CODEGENERATORREQUEST._serialized_start=203 - _CODEGENERATORREQUEST._serialized_end=444 - _CODEGENERATORRESPONSE._serialized_start=447 - _CODEGENERATORRESPONSE._serialized_end=851 - _CODEGENERATORRESPONSE_FILE._serialized_start=616 - _CODEGENERATORRESPONSE_FILE._serialized_end=793 - _CODEGENERATORRESPONSE_FEATURE._serialized_start=795 - _CODEGENERATORRESPONSE_FEATURE._serialized_end=851 + DESCRIPTOR._serialized_options = b'\n\034com.google.protobuf.compilerB\014PluginProtosZ)google.golang.org/protobuf/types/pluginpb\252\002\030Google.Protobuf.Compiler' + _globals['_VERSION']._serialized_start=105 + _globals['_VERSION']._serialized_end=204 + _globals['_CODEGENERATORREQUEST']._serialized_start=207 + _globals['_CODEGENERATORREQUEST']._serialized_end=448 + _globals['_CODEGENERATORRESPONSE']._serialized_start=451 + _globals['_CODEGENERATORRESPONSE']._serialized_end=855 + _globals['_CODEGENERATORRESPONSE_FILE']._serialized_start=620 + _globals['_CODEGENERATORRESPONSE_FILE']._serialized_end=797 + _globals['_CODEGENERATORRESPONSE_FEATURE']._serialized_start=799 + _globals['_CODEGENERATORRESPONSE_FEATURE']._serialized_end=855 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/descriptor.py b/ext/protobuf/Python/google/protobuf/descriptor.py index f5a0caa6b..fcb87cab5 100644 --- a/ext/protobuf/Python/google/protobuf/descriptor.py +++ b/ext/protobuf/Python/google/protobuf/descriptor.py @@ -66,6 +66,7 @@ class TypeTransformationError(Error): # and make it return True when the descriptor is an instance of the extension # type written in C++. class DescriptorMetaclass(type): + def __instancecheck__(cls, obj): if super(DescriptorMetaclass, cls).__instancecheck__(obj): return True @@ -633,13 +634,29 @@ def has_presence(self): if (self.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE or self.containing_oneof): return True - if hasattr(self.file, 'syntax'): - return self.file.syntax == 'proto2' - if hasattr(self.message_type, 'syntax'): - return self.message_type.syntax == 'proto2' - raise RuntimeError( - 'has_presence is not ready to use because field %s is not' - ' linked with message type nor file' % self.full_name) + # self.containing_type is used here instead of self.file for legacy + # compatibility. FieldDescriptor.file was added in cl/153110619 + # Some old/generated code didn't link file to FieldDescriptor. + # TODO(jieluo): remove syntax usage b/240619313 + return self.containing_type.syntax == 'proto2' + + @property + def is_packed(self): + """Returns if the field is packed.""" + if self.label != FieldDescriptor.LABEL_REPEATED: + return False + field_type = self.type + if (field_type == FieldDescriptor.TYPE_STRING or + field_type == FieldDescriptor.TYPE_GROUP or + field_type == FieldDescriptor.TYPE_MESSAGE or + field_type == FieldDescriptor.TYPE_BYTES): + return False + if self.containing_type.syntax == 'proto2': + return self.has_options and self.GetOptions().packed + else: + return (not self.has_options or + not self.GetOptions().HasField('packed') or + self.GetOptions().packed) @staticmethod def ProtoTypeToCppProtoType(proto_type): @@ -720,6 +737,30 @@ def __init__(self, name, full_name, filename, values, # Values are reversed to ensure that the first alias is retained. self.values_by_number = dict((v.number, v) for v in reversed(values)) + @property + def is_closed(self): + """Returns true whether this is a "closed" enum. + + This means that it: + - Has a fixed set of values, rather than being equivalent to an int32. + - Encountering values not in this set causes them to be treated as unknown + fields. + - The first value (i.e., the default) may be nonzero. + + WARNING: Some runtimes currently have a quirk where non-closed enums are + treated as closed when used as the type of fields defined in a + `syntax = proto2;` file. This quirk is not present in all runtimes; as of + writing, we know that: + + - C++, Java, and C++-based Python share this quirk. + - UPB and UPB-based Python do not. + - PHP and Ruby treat all enums as open regardless of declaration. + + Care should be taken when using this function to respect the target + runtime's enum handling quirks. + """ + return self.file.syntax == 'proto2' + def CopyToProto(self, proto): """Copies this to a descriptor_pb2.EnumDescriptorProto. @@ -873,11 +914,14 @@ def FindMethodByName(self, name): Args: name (str): Name of the method. + Returns: - MethodDescriptor or None: the descriptor for the requested method, if - found. + MethodDescriptor: The descriptor for the requested method. + + Raises: + KeyError: if the method cannot be found in the service. """ - return self.methods_by_name.get(name, None) + return self.methods_by_name[name] def CopyToProto(self, proto): """Copies this to a descriptor_pb2.ServiceDescriptorProto. @@ -1018,13 +1062,7 @@ def __new__(cls, name, package, options=None, # FileDescriptor() is called from various places, not only from generated # files, to register dynamic proto files and messages. # pylint: disable=g-explicit-bool-comparison - if serialized_pb == b'': - # Cpp generated code must be linked in if serialized_pb is '' - try: - return _message.default_pool.FindFileByName(name) - except KeyError: - raise RuntimeError('Please link in cpp generated lib for %s' % (name)) - elif serialized_pb: + if serialized_pb: return _message.default_pool.AddSerializedFile(serialized_pb) else: return super(FileDescriptor, cls).__new__(cls) diff --git a/ext/protobuf/Python/google/protobuf/descriptor_pb2.py b/ext/protobuf/Python/google/protobuf/descriptor_pb2.py index 2eaeb7d13..84781d251 100644 --- a/ext/protobuf/Python/google/protobuf/descriptor_pb2.py +++ b/ext/protobuf/Python/google/protobuf/descriptor_pb2.py @@ -20,11 +20,12 @@ syntax='proto2', serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_pb=b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\xe4\x04\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"|\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\x91\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12\x37\n\x14php_generic_services\x18* \x01(\x08:\x05\x66\x61lseR\x12phpGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08&\x10\'\"\xe3\x02\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\x92\x04\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05\"s\n\x0cOneofOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc0\x01\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\x9e\x01\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9c\x01\n\x0eServiceOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xe0\x02\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd1\x01\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1am\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65ndB~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection' + serialized_pb=b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\xfe\x04\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\x12\x18\n\x07\x65\x64ition\x18\r \x01(\tR\x07\x65\x64ition\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"|\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\x91\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12\x37\n\x14php_generic_services\x18* \x01(\x08:\x05\x66\x61lseR\x12phpGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08&\x10\'\"\xbb\x03\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12V\n&deprecated_legacy_json_field_conflicts\x18\x0b \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\xb7\x08\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12(\n\x0c\x64\x65\x62ug_redact\x18\x10 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12K\n\tretention\x18\x11 \x01(\x0e\x32-.google.protobuf.FieldOptions.OptionRetentionR\tretention\x12\x46\n\x06target\x18\x12 \x01(\x0e\x32..google.protobuf.FieldOptions.OptionTargetTypeR\x06target\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02\"U\n\x0fOptionRetention\x12\x15\n\x11RETENTION_UNKNOWN\x10\x00\x12\x15\n\x11RETENTION_RUNTIME\x10\x01\x12\x14\n\x10RETENTION_SOURCE\x10\x02\"\x8c\x02\n\x10OptionTargetType\x12\x17\n\x13TARGET_TYPE_UNKNOWN\x10\x00\x12\x14\n\x10TARGET_TYPE_FILE\x10\x01\x12\x1f\n\x1bTARGET_TYPE_EXTENSION_RANGE\x10\x02\x12\x17\n\x13TARGET_TYPE_MESSAGE\x10\x03\x12\x15\n\x11TARGET_TYPE_FIELD\x10\x04\x12\x15\n\x11TARGET_TYPE_ONEOF\x10\x05\x12\x14\n\x10TARGET_TYPE_ENUM\x10\x06\x12\x1a\n\x16TARGET_TYPE_ENUM_ENTRY\x10\x07\x12\x17\n\x13TARGET_TYPE_SERVICE\x10\x08\x12\x16\n\x12TARGET_TYPE_METHOD\x10\t*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05\"s\n\x0cOneofOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x98\x02\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12V\n&deprecated_legacy_json_field_conflicts\x18\x06 \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\x9e\x01\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9c\x01\n\x0eServiceOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xe0\x02\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd0\x02\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1a\xeb\x01\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65nd\x12R\n\x08semantic\x18\x05 \x01(\x0e\x32\x36.google.protobuf.GeneratedCodeInfo.Annotation.SemanticR\x08semantic\"(\n\x08Semantic\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03SET\x10\x01\x12\t\n\x05\x41LIAS\x10\x02\x42~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection' ) else: - DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\xe4\x04\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"|\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\x91\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12\x37\n\x14php_generic_services\x18* \x01(\x08:\x05\x66\x61lseR\x12phpGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08&\x10\'\"\xe3\x02\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\x92\x04\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05\"s\n\x0cOneofOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc0\x01\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\x9e\x01\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9c\x01\n\x0eServiceOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xe0\x02\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd1\x01\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1am\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65ndB~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection') + DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n google/protobuf/descriptor.proto\x12\x0fgoogle.protobuf\"M\n\x11\x46ileDescriptorSet\x12\x38\n\x04\x66ile\x18\x01 \x03(\x0b\x32$.google.protobuf.FileDescriptorProtoR\x04\x66ile\"\xfe\x04\n\x13\x46ileDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n\x07package\x18\x02 \x01(\tR\x07package\x12\x1e\n\ndependency\x18\x03 \x03(\tR\ndependency\x12+\n\x11public_dependency\x18\n \x03(\x05R\x10publicDependency\x12\'\n\x0fweak_dependency\x18\x0b \x03(\x05R\x0eweakDependency\x12\x43\n\x0cmessage_type\x18\x04 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\x0bmessageType\x12\x41\n\tenum_type\x18\x05 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12\x41\n\x07service\x18\x06 \x03(\x0b\x32\'.google.protobuf.ServiceDescriptorProtoR\x07service\x12\x43\n\textension\x18\x07 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x36\n\x07options\x18\x08 \x01(\x0b\x32\x1c.google.protobuf.FileOptionsR\x07options\x12I\n\x10source_code_info\x18\t \x01(\x0b\x32\x1f.google.protobuf.SourceCodeInfoR\x0esourceCodeInfo\x12\x16\n\x06syntax\x18\x0c \x01(\tR\x06syntax\x12\x18\n\x07\x65\x64ition\x18\r \x01(\tR\x07\x65\x64ition\"\xb9\x06\n\x0f\x44\x65scriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12;\n\x05\x66ield\x18\x02 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\x05\x66ield\x12\x43\n\textension\x18\x06 \x03(\x0b\x32%.google.protobuf.FieldDescriptorProtoR\textension\x12\x41\n\x0bnested_type\x18\x03 \x03(\x0b\x32 .google.protobuf.DescriptorProtoR\nnestedType\x12\x41\n\tenum_type\x18\x04 \x03(\x0b\x32$.google.protobuf.EnumDescriptorProtoR\x08\x65numType\x12X\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32/.google.protobuf.DescriptorProto.ExtensionRangeR\x0e\x65xtensionRange\x12\x44\n\noneof_decl\x18\x08 \x03(\x0b\x32%.google.protobuf.OneofDescriptorProtoR\toneofDecl\x12\x39\n\x07options\x18\x07 \x01(\x0b\x32\x1f.google.protobuf.MessageOptionsR\x07options\x12U\n\x0ereserved_range\x18\t \x03(\x0b\x32..google.protobuf.DescriptorProto.ReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\n \x03(\tR\x0creservedName\x1az\n\x0e\x45xtensionRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\x12@\n\x07options\x18\x03 \x01(\x0b\x32&.google.protobuf.ExtensionRangeOptionsR\x07options\x1a\x37\n\rReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"|\n\x15\x45xtensionRangeOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xc1\x06\n\x14\x46ieldDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x41\n\x05label\x18\x04 \x01(\x0e\x32+.google.protobuf.FieldDescriptorProto.LabelR\x05label\x12>\n\x04type\x18\x05 \x01(\x0e\x32*.google.protobuf.FieldDescriptorProto.TypeR\x04type\x12\x1b\n\ttype_name\x18\x06 \x01(\tR\x08typeName\x12\x1a\n\x08\x65xtendee\x18\x02 \x01(\tR\x08\x65xtendee\x12#\n\rdefault_value\x18\x07 \x01(\tR\x0c\x64\x65\x66\x61ultValue\x12\x1f\n\x0boneof_index\x18\t \x01(\x05R\noneofIndex\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12\x37\n\x07options\x18\x08 \x01(\x0b\x32\x1d.google.protobuf.FieldOptionsR\x07options\x12\'\n\x0fproto3_optional\x18\x11 \x01(\x08R\x0eproto3Optional\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"c\n\x14OneofDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x37\n\x07options\x18\x02 \x01(\x0b\x32\x1d.google.protobuf.OneofOptionsR\x07options\"\xe3\x02\n\x13\x45numDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12?\n\x05value\x18\x02 \x03(\x0b\x32).google.protobuf.EnumValueDescriptorProtoR\x05value\x12\x36\n\x07options\x18\x03 \x01(\x0b\x32\x1c.google.protobuf.EnumOptionsR\x07options\x12]\n\x0ereserved_range\x18\x04 \x03(\x0b\x32\x36.google.protobuf.EnumDescriptorProto.EnumReservedRangeR\rreservedRange\x12#\n\rreserved_name\x18\x05 \x03(\tR\x0creservedName\x1a;\n\x11\x45numReservedRange\x12\x14\n\x05start\x18\x01 \x01(\x05R\x05start\x12\x10\n\x03\x65nd\x18\x02 \x01(\x05R\x03\x65nd\"\x83\x01\n\x18\x45numValueDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12;\n\x07options\x18\x03 \x01(\x0b\x32!.google.protobuf.EnumValueOptionsR\x07options\"\xa7\x01\n\x16ServiceDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12>\n\x06method\x18\x02 \x03(\x0b\x32&.google.protobuf.MethodDescriptorProtoR\x06method\x12\x39\n\x07options\x18\x03 \x01(\x0b\x32\x1f.google.protobuf.ServiceOptionsR\x07options\"\x89\x02\n\x15MethodDescriptorProto\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\ninput_type\x18\x02 \x01(\tR\tinputType\x12\x1f\n\x0boutput_type\x18\x03 \x01(\tR\noutputType\x12\x38\n\x07options\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.MethodOptionsR\x07options\x12\x30\n\x10\x63lient_streaming\x18\x05 \x01(\x08:\x05\x66\x61lseR\x0f\x63lientStreaming\x12\x30\n\x10server_streaming\x18\x06 \x01(\x08:\x05\x66\x61lseR\x0fserverStreaming\"\x91\t\n\x0b\x46ileOptions\x12!\n\x0cjava_package\x18\x01 \x01(\tR\x0bjavaPackage\x12\x30\n\x14java_outer_classname\x18\x08 \x01(\tR\x12javaOuterClassname\x12\x35\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lseR\x11javaMultipleFiles\x12\x44\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08\x42\x02\x18\x01R\x19javaGenerateEqualsAndHash\x12:\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lseR\x13javaStringCheckUtf8\x12S\n\x0coptimize_for\x18\t \x01(\x0e\x32).google.protobuf.FileOptions.OptimizeMode:\x05SPEEDR\x0boptimizeFor\x12\x1d\n\ngo_package\x18\x0b \x01(\tR\tgoPackage\x12\x35\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lseR\x11\x63\x63GenericServices\x12\x39\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lseR\x13javaGenericServices\x12\x35\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lseR\x11pyGenericServices\x12\x37\n\x14php_generic_services\x18* \x01(\x08:\x05\x66\x61lseR\x12phpGenericServices\x12%\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12.\n\x10\x63\x63_enable_arenas\x18\x1f \x01(\x08:\x04trueR\x0e\x63\x63\x45nableArenas\x12*\n\x11objc_class_prefix\x18$ \x01(\tR\x0fobjcClassPrefix\x12)\n\x10\x63sharp_namespace\x18% \x01(\tR\x0f\x63sharpNamespace\x12!\n\x0cswift_prefix\x18\' \x01(\tR\x0bswiftPrefix\x12(\n\x10php_class_prefix\x18( \x01(\tR\x0ephpClassPrefix\x12#\n\rphp_namespace\x18) \x01(\tR\x0cphpNamespace\x12\x34\n\x16php_metadata_namespace\x18, \x01(\tR\x14phpMetadataNamespace\x12!\n\x0cruby_package\x18- \x01(\tR\x0brubyPackage\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08&\x10\'\"\xbb\x03\n\x0eMessageOptions\x12<\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lseR\x14messageSetWireFormat\x12L\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lseR\x1cnoStandardDescriptorAccessor\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x1b\n\tmap_entry\x18\x07 \x01(\x08R\x08mapEntry\x12V\n&deprecated_legacy_json_field_conflicts\x18\x0b \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07J\x04\x08\x08\x10\tJ\x04\x08\t\x10\n\"\xb7\x08\n\x0c\x46ieldOptions\x12\x41\n\x05\x63type\x18\x01 \x01(\x0e\x32#.google.protobuf.FieldOptions.CType:\x06STRINGR\x05\x63type\x12\x16\n\x06packed\x18\x02 \x01(\x08R\x06packed\x12G\n\x06jstype\x18\x06 \x01(\x0e\x32$.google.protobuf.FieldOptions.JSType:\tJS_NORMALR\x06jstype\x12\x19\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lseR\x04lazy\x12.\n\x0funverified_lazy\x18\x0f \x01(\x08:\x05\x66\x61lseR\x0eunverifiedLazy\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12\x19\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lseR\x04weak\x12(\n\x0c\x64\x65\x62ug_redact\x18\x10 \x01(\x08:\x05\x66\x61lseR\x0b\x64\x65\x62ugRedact\x12K\n\tretention\x18\x11 \x01(\x0e\x32-.google.protobuf.FieldOptions.OptionRetentionR\tretention\x12\x46\n\x06target\x18\x12 \x01(\x0e\x32..google.protobuf.FieldOptions.OptionTargetTypeR\x06target\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02\"U\n\x0fOptionRetention\x12\x15\n\x11RETENTION_UNKNOWN\x10\x00\x12\x15\n\x11RETENTION_RUNTIME\x10\x01\x12\x14\n\x10RETENTION_SOURCE\x10\x02\"\x8c\x02\n\x10OptionTargetType\x12\x17\n\x13TARGET_TYPE_UNKNOWN\x10\x00\x12\x14\n\x10TARGET_TYPE_FILE\x10\x01\x12\x1f\n\x1bTARGET_TYPE_EXTENSION_RANGE\x10\x02\x12\x17\n\x13TARGET_TYPE_MESSAGE\x10\x03\x12\x15\n\x11TARGET_TYPE_FIELD\x10\x04\x12\x15\n\x11TARGET_TYPE_ONEOF\x10\x05\x12\x14\n\x10TARGET_TYPE_ENUM\x10\x06\x12\x1a\n\x16TARGET_TYPE_ENUM_ENTRY\x10\x07\x12\x17\n\x13TARGET_TYPE_SERVICE\x10\x08\x12\x16\n\x12TARGET_TYPE_METHOD\x10\t*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x04\x10\x05\"s\n\x0cOneofOptions\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x98\x02\n\x0b\x45numOptions\x12\x1f\n\x0b\x61llow_alias\x18\x02 \x01(\x08R\nallowAlias\x12%\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12V\n&deprecated_legacy_json_field_conflicts\x18\x06 \x01(\x08\x42\x02\x18\x01R\"deprecatedLegacyJsonFieldConflicts\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02J\x04\x08\x05\x10\x06\"\x9e\x01\n\x10\x45numValueOptions\x12%\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9c\x01\n\x0eServiceOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xe0\x02\n\rMethodOptions\x12%\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lseR\ndeprecated\x12q\n\x11idempotency_level\x18\" \x01(\x0e\x32/.google.protobuf.MethodOptions.IdempotencyLevel:\x13IDEMPOTENCY_UNKNOWNR\x10idempotencyLevel\x12X\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32$.google.protobuf.UninterpretedOptionR\x13uninterpretedOption\"P\n\x10IdempotencyLevel\x12\x17\n\x13IDEMPOTENCY_UNKNOWN\x10\x00\x12\x13\n\x0fNO_SIDE_EFFECTS\x10\x01\x12\x0e\n\nIDEMPOTENT\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x9a\x03\n\x13UninterpretedOption\x12\x41\n\x04name\x18\x02 \x03(\x0b\x32-.google.protobuf.UninterpretedOption.NamePartR\x04name\x12)\n\x10identifier_value\x18\x03 \x01(\tR\x0fidentifierValue\x12,\n\x12positive_int_value\x18\x04 \x01(\x04R\x10positiveIntValue\x12,\n\x12negative_int_value\x18\x05 \x01(\x03R\x10negativeIntValue\x12!\n\x0c\x64ouble_value\x18\x06 \x01(\x01R\x0b\x64oubleValue\x12!\n\x0cstring_value\x18\x07 \x01(\x0cR\x0bstringValue\x12\'\n\x0f\x61ggregate_value\x18\x08 \x01(\tR\x0e\x61ggregateValue\x1aJ\n\x08NamePart\x12\x1b\n\tname_part\x18\x01 \x02(\tR\x08namePart\x12!\n\x0cis_extension\x18\x02 \x02(\x08R\x0bisExtension\"\xa7\x02\n\x0eSourceCodeInfo\x12\x44\n\x08location\x18\x01 \x03(\x0b\x32(.google.protobuf.SourceCodeInfo.LocationR\x08location\x1a\xce\x01\n\x08Location\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x16\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01R\x04span\x12)\n\x10leading_comments\x18\x03 \x01(\tR\x0fleadingComments\x12+\n\x11trailing_comments\x18\x04 \x01(\tR\x10trailingComments\x12:\n\x19leading_detached_comments\x18\x06 \x03(\tR\x17leadingDetachedComments\"\xd0\x02\n\x11GeneratedCodeInfo\x12M\n\nannotation\x18\x01 \x03(\x0b\x32-.google.protobuf.GeneratedCodeInfo.AnnotationR\nannotation\x1a\xeb\x01\n\nAnnotation\x12\x16\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01R\x04path\x12\x1f\n\x0bsource_file\x18\x02 \x01(\tR\nsourceFile\x12\x14\n\x05\x62\x65gin\x18\x03 \x01(\x05R\x05\x62\x65gin\x12\x10\n\x03\x65nd\x18\x04 \x01(\x05R\x03\x65nd\x12R\n\x08semantic\x18\x05 \x01(\x0e\x32\x36.google.protobuf.GeneratedCodeInfo.Annotation.SemanticR\x08semantic\"(\n\x08Semantic\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03SET\x10\x01\x12\t\n\x05\x41LIAS\x10\x02\x42~\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01Z-google.golang.org/protobuf/types/descriptorpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1aGoogle.Protobuf.Reflection') +_globals = globals() if _descriptor._USE_C_DESCRIPTORS == False: _FIELDDESCRIPTORPROTO_TYPE = _descriptor.EnumDescriptor( name='Type', @@ -241,6 +242,97 @@ ) _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_JSTYPE) + _FIELDOPTIONS_OPTIONRETENTION = _descriptor.EnumDescriptor( + name='OptionRetention', + full_name='google.protobuf.FieldOptions.OptionRetention', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='RETENTION_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RETENTION_RUNTIME', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='RETENTION_SOURCE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_OPTIONRETENTION) + + _FIELDOPTIONS_OPTIONTARGETTYPE = _descriptor.EnumDescriptor( + name='OptionTargetType', + full_name='google.protobuf.FieldOptions.OptionTargetType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_UNKNOWN', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_FILE', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_EXTENSION_RANGE', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_MESSAGE', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_FIELD', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ONEOF', index=5, number=5, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ENUM', index=6, number=6, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_ENUM_ENTRY', index=7, number=7, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_SERVICE', index=8, number=8, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='TARGET_TYPE_METHOD', index=9, number=9, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_FIELDOPTIONS_OPTIONTARGETTYPE) + _METHODOPTIONS_IDEMPOTENCYLEVEL = _descriptor.EnumDescriptor( name='IdempotencyLevel', full_name='google.protobuf.MethodOptions.IdempotencyLevel', @@ -269,6 +361,34 @@ ) _sym_db.RegisterEnumDescriptor(_METHODOPTIONS_IDEMPOTENCYLEVEL) + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC = _descriptor.EnumDescriptor( + name='Semantic', + full_name='google.protobuf.GeneratedCodeInfo.Annotation.Semantic', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SET', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ALIAS', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + ) + _sym_db.RegisterEnumDescriptor(_GENERATEDCODEINFO_ANNOTATION_SEMANTIC) + _FILEDESCRIPTORSET = _descriptor.Descriptor( name='FileDescriptorSet', @@ -392,6 +512,13 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, json_name='syntax', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='edition', full_name='google.protobuf.FileDescriptorProto.edition', index=12, + number=13, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='edition', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -1203,7 +1330,14 @@ is_extension=False, extension_scope=None, serialized_options=None, json_name='mapEntry', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( - name='uninterpreted_option', full_name='google.protobuf.MessageOptions.uninterpreted_option', index=4, + name='deprecated_legacy_json_field_conflicts', full_name='google.protobuf.MessageOptions.deprecated_legacy_json_field_conflicts', index=4, + number=11, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecatedLegacyJsonFieldConflicts', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.MessageOptions.uninterpreted_option', index=5, number=999, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -1282,7 +1416,28 @@ is_extension=False, extension_scope=None, serialized_options=None, json_name='weak', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( - name='uninterpreted_option', full_name='google.protobuf.FieldOptions.uninterpreted_option', index=7, + name='debug_redact', full_name='google.protobuf.FieldOptions.debug_redact', index=7, + number=16, type=8, cpp_type=7, label=1, + has_default_value=True, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='debugRedact', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='retention', full_name='google.protobuf.FieldOptions.retention', index=8, + number=17, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='retention', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='target', full_name='google.protobuf.FieldOptions.target', index=9, + number=18, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='target', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.FieldOptions.uninterpreted_option', index=10, number=999, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -1295,6 +1450,8 @@ enum_types=[ _FIELDOPTIONS_CTYPE, _FIELDOPTIONS_JSTYPE, + _FIELDOPTIONS_OPTIONRETENTION, + _FIELDOPTIONS_OPTIONTARGETTYPE, ], serialized_options=None, is_extendable=True, @@ -1358,7 +1515,14 @@ is_extension=False, extension_scope=None, serialized_options=None, json_name='deprecated', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( - name='uninterpreted_option', full_name='google.protobuf.EnumOptions.uninterpreted_option', index=2, + name='deprecated_legacy_json_field_conflicts', full_name='google.protobuf.EnumOptions.deprecated_legacy_json_field_conflicts', index=2, + number=6, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='deprecatedLegacyJsonFieldConflicts', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='uninterpreted_option', full_name='google.protobuf.EnumOptions.uninterpreted_option', index=3, number=999, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, @@ -1729,11 +1893,19 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, serialized_options=None, json_name='end', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='semantic', full_name='google.protobuf.GeneratedCodeInfo.Annotation.semantic', index=4, + number=5, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, json_name='semantic', file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], nested_types=[], enum_types=[ + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC, ], serialized_options=None, is_extendable=False, @@ -1811,9 +1983,13 @@ _MESSAGEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION _FIELDOPTIONS.fields_by_name['ctype'].enum_type = _FIELDOPTIONS_CTYPE _FIELDOPTIONS.fields_by_name['jstype'].enum_type = _FIELDOPTIONS_JSTYPE + _FIELDOPTIONS.fields_by_name['retention'].enum_type = _FIELDOPTIONS_OPTIONRETENTION + _FIELDOPTIONS.fields_by_name['target'].enum_type = _FIELDOPTIONS_OPTIONTARGETTYPE _FIELDOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION _FIELDOPTIONS_CTYPE.containing_type = _FIELDOPTIONS _FIELDOPTIONS_JSTYPE.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_OPTIONRETENTION.containing_type = _FIELDOPTIONS + _FIELDOPTIONS_OPTIONTARGETTYPE.containing_type = _FIELDOPTIONS _ONEOFOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION _ENUMOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION _ENUMVALUEOPTIONS.fields_by_name['uninterpreted_option'].message_type = _UNINTERPRETEDOPTION @@ -1825,7 +2001,9 @@ _UNINTERPRETEDOPTION.fields_by_name['name'].message_type = _UNINTERPRETEDOPTION_NAMEPART _SOURCECODEINFO_LOCATION.containing_type = _SOURCECODEINFO _SOURCECODEINFO.fields_by_name['location'].message_type = _SOURCECODEINFO_LOCATION + _GENERATEDCODEINFO_ANNOTATION.fields_by_name['semantic'].enum_type = _GENERATEDCODEINFO_ANNOTATION_SEMANTIC _GENERATEDCODEINFO_ANNOTATION.containing_type = _GENERATEDCODEINFO + _GENERATEDCODEINFO_ANNOTATION_SEMANTIC.containing_type = _GENERATEDCODEINFO_ANNOTATION _GENERATEDCODEINFO.fields_by_name['annotation'].message_type = _GENERATEDCODEINFO_ANNOTATION DESCRIPTOR.message_types_by_name['FileDescriptorSet'] = _FILEDESCRIPTORSET DESCRIPTOR.message_types_by_name['FileDescriptorProto'] = _FILEDESCRIPTORPROTO @@ -1851,75 +2029,81 @@ _sym_db.RegisterFileDescriptor(DESCRIPTOR) else: - _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.descriptor_pb2', globals()) + _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.descriptor_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _FILEDESCRIPTORSET._serialized_start=53 - _FILEDESCRIPTORSET._serialized_end=130 - _FILEDESCRIPTORPROTO._serialized_start=133 - _FILEDESCRIPTORPROTO._serialized_end=745 - _DESCRIPTORPROTO._serialized_start=748 - _DESCRIPTORPROTO._serialized_end=1573 - _DESCRIPTORPROTO_EXTENSIONRANGE._serialized_start=1394 - _DESCRIPTORPROTO_EXTENSIONRANGE._serialized_end=1516 - _DESCRIPTORPROTO_RESERVEDRANGE._serialized_start=1518 - _DESCRIPTORPROTO_RESERVEDRANGE._serialized_end=1573 - _EXTENSIONRANGEOPTIONS._serialized_start=1575 - _EXTENSIONRANGEOPTIONS._serialized_end=1699 - _FIELDDESCRIPTORPROTO._serialized_start=1702 - _FIELDDESCRIPTORPROTO._serialized_end=2535 - _FIELDDESCRIPTORPROTO_TYPE._serialized_start=2156 - _FIELDDESCRIPTORPROTO_TYPE._serialized_end=2466 - _FIELDDESCRIPTORPROTO_LABEL._serialized_start=2468 - _FIELDDESCRIPTORPROTO_LABEL._serialized_end=2535 - _ONEOFDESCRIPTORPROTO._serialized_start=2537 - _ONEOFDESCRIPTORPROTO._serialized_end=2636 - _ENUMDESCRIPTORPROTO._serialized_start=2639 - _ENUMDESCRIPTORPROTO._serialized_end=2994 - _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE._serialized_start=2935 - _ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE._serialized_end=2994 - _ENUMVALUEDESCRIPTORPROTO._serialized_start=2997 - _ENUMVALUEDESCRIPTORPROTO._serialized_end=3128 - _SERVICEDESCRIPTORPROTO._serialized_start=3131 - _SERVICEDESCRIPTORPROTO._serialized_end=3298 - _METHODDESCRIPTORPROTO._serialized_start=3301 - _METHODDESCRIPTORPROTO._serialized_end=3566 - _FILEOPTIONS._serialized_start=3569 - _FILEOPTIONS._serialized_end=4738 - _FILEOPTIONS_OPTIMIZEMODE._serialized_start=4663 - _FILEOPTIONS_OPTIMIZEMODE._serialized_end=4721 - _MESSAGEOPTIONS._serialized_start=4741 - _MESSAGEOPTIONS._serialized_end=5096 - _FIELDOPTIONS._serialized_start=5099 - _FIELDOPTIONS._serialized_end=5629 - _FIELDOPTIONS_CTYPE._serialized_start=5510 - _FIELDOPTIONS_CTYPE._serialized_end=5557 - _FIELDOPTIONS_JSTYPE._serialized_start=5559 - _FIELDOPTIONS_JSTYPE._serialized_end=5612 - _ONEOFOPTIONS._serialized_start=5631 - _ONEOFOPTIONS._serialized_end=5746 - _ENUMOPTIONS._serialized_start=5749 - _ENUMOPTIONS._serialized_end=5941 - _ENUMVALUEOPTIONS._serialized_start=5944 - _ENUMVALUEOPTIONS._serialized_end=6102 - _SERVICEOPTIONS._serialized_start=6105 - _SERVICEOPTIONS._serialized_end=6261 - _METHODOPTIONS._serialized_start=6264 - _METHODOPTIONS._serialized_end=6616 - _METHODOPTIONS_IDEMPOTENCYLEVEL._serialized_start=6525 - _METHODOPTIONS_IDEMPOTENCYLEVEL._serialized_end=6605 - _UNINTERPRETEDOPTION._serialized_start=6619 - _UNINTERPRETEDOPTION._serialized_end=7029 - _UNINTERPRETEDOPTION_NAMEPART._serialized_start=6955 - _UNINTERPRETEDOPTION_NAMEPART._serialized_end=7029 - _SOURCECODEINFO._serialized_start=7032 - _SOURCECODEINFO._serialized_end=7327 - _SOURCECODEINFO_LOCATION._serialized_start=7121 - _SOURCECODEINFO_LOCATION._serialized_end=7327 - _GENERATEDCODEINFO._serialized_start=7330 - _GENERATEDCODEINFO._serialized_end=7539 - _GENERATEDCODEINFO_ANNOTATION._serialized_start=7430 - _GENERATEDCODEINFO_ANNOTATION._serialized_end=7539 + _globals['_FILEDESCRIPTORSET']._serialized_start=53 + _globals['_FILEDESCRIPTORSET']._serialized_end=130 + _globals['_FILEDESCRIPTORPROTO']._serialized_start=133 + _globals['_FILEDESCRIPTORPROTO']._serialized_end=771 + _globals['_DESCRIPTORPROTO']._serialized_start=774 + _globals['_DESCRIPTORPROTO']._serialized_end=1599 + _globals['_DESCRIPTORPROTO_EXTENSIONRANGE']._serialized_start=1420 + _globals['_DESCRIPTORPROTO_EXTENSIONRANGE']._serialized_end=1542 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_start=1544 + _globals['_DESCRIPTORPROTO_RESERVEDRANGE']._serialized_end=1599 + _globals['_EXTENSIONRANGEOPTIONS']._serialized_start=1601 + _globals['_EXTENSIONRANGEOPTIONS']._serialized_end=1725 + _globals['_FIELDDESCRIPTORPROTO']._serialized_start=1728 + _globals['_FIELDDESCRIPTORPROTO']._serialized_end=2561 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_start=2182 + _globals['_FIELDDESCRIPTORPROTO_TYPE']._serialized_end=2492 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_start=2494 + _globals['_FIELDDESCRIPTORPROTO_LABEL']._serialized_end=2561 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_start=2563 + _globals['_ONEOFDESCRIPTORPROTO']._serialized_end=2662 + _globals['_ENUMDESCRIPTORPROTO']._serialized_start=2665 + _globals['_ENUMDESCRIPTORPROTO']._serialized_end=3020 + _globals['_ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE']._serialized_start=2961 + _globals['_ENUMDESCRIPTORPROTO_ENUMRESERVEDRANGE']._serialized_end=3020 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_start=3023 + _globals['_ENUMVALUEDESCRIPTORPROTO']._serialized_end=3154 + _globals['_SERVICEDESCRIPTORPROTO']._serialized_start=3157 + _globals['_SERVICEDESCRIPTORPROTO']._serialized_end=3324 + _globals['_METHODDESCRIPTORPROTO']._serialized_start=3327 + _globals['_METHODDESCRIPTORPROTO']._serialized_end=3592 + _globals['_FILEOPTIONS']._serialized_start=3595 + _globals['_FILEOPTIONS']._serialized_end=4764 + _globals['_FILEOPTIONS_OPTIMIZEMODE']._serialized_start=4689 + _globals['_FILEOPTIONS_OPTIMIZEMODE']._serialized_end=4747 + _globals['_MESSAGEOPTIONS']._serialized_start=4767 + _globals['_MESSAGEOPTIONS']._serialized_end=5210 + _globals['_FIELDOPTIONS']._serialized_start=5213 + _globals['_FIELDOPTIONS']._serialized_end=6292 + _globals['_FIELDOPTIONS_CTYPE']._serialized_start=5815 + _globals['_FIELDOPTIONS_CTYPE']._serialized_end=5862 + _globals['_FIELDOPTIONS_JSTYPE']._serialized_start=5864 + _globals['_FIELDOPTIONS_JSTYPE']._serialized_end=5917 + _globals['_FIELDOPTIONS_OPTIONRETENTION']._serialized_start=5919 + _globals['_FIELDOPTIONS_OPTIONRETENTION']._serialized_end=6004 + _globals['_FIELDOPTIONS_OPTIONTARGETTYPE']._serialized_start=6007 + _globals['_FIELDOPTIONS_OPTIONTARGETTYPE']._serialized_end=6275 + _globals['_ONEOFOPTIONS']._serialized_start=6294 + _globals['_ONEOFOPTIONS']._serialized_end=6409 + _globals['_ENUMOPTIONS']._serialized_start=6412 + _globals['_ENUMOPTIONS']._serialized_end=6692 + _globals['_ENUMVALUEOPTIONS']._serialized_start=6695 + _globals['_ENUMVALUEOPTIONS']._serialized_end=6853 + _globals['_SERVICEOPTIONS']._serialized_start=6856 + _globals['_SERVICEOPTIONS']._serialized_end=7012 + _globals['_METHODOPTIONS']._serialized_start=7015 + _globals['_METHODOPTIONS']._serialized_end=7367 + _globals['_METHODOPTIONS_IDEMPOTENCYLEVEL']._serialized_start=7276 + _globals['_METHODOPTIONS_IDEMPOTENCYLEVEL']._serialized_end=7356 + _globals['_UNINTERPRETEDOPTION']._serialized_start=7370 + _globals['_UNINTERPRETEDOPTION']._serialized_end=7780 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_start=7706 + _globals['_UNINTERPRETEDOPTION_NAMEPART']._serialized_end=7780 + _globals['_SOURCECODEINFO']._serialized_start=7783 + _globals['_SOURCECODEINFO']._serialized_end=8078 + _globals['_SOURCECODEINFO_LOCATION']._serialized_start=7872 + _globals['_SOURCECODEINFO_LOCATION']._serialized_end=8078 + _globals['_GENERATEDCODEINFO']._serialized_start=8081 + _globals['_GENERATEDCODEINFO']._serialized_end=8417 + _globals['_GENERATEDCODEINFO_ANNOTATION']._serialized_start=8182 + _globals['_GENERATEDCODEINFO_ANNOTATION']._serialized_end=8417 + _globals['_GENERATEDCODEINFO_ANNOTATION_SEMANTIC']._serialized_start=8377 + _globals['_GENERATEDCODEINFO_ANNOTATION_SEMANTIC']._serialized_end=8417 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/descriptor_pool.py b/ext/protobuf/Python/google/protobuf/descriptor_pool.py index 911372a8b..1ebf11834 100644 --- a/ext/protobuf/Python/google/protobuf/descriptor_pool.py +++ b/ext/protobuf/Python/google/protobuf/descriptor_pool.py @@ -120,11 +120,13 @@ class DescriptorPool(object): if _USE_C_DESCRIPTORS: - def __new__(cls, descriptor_db=None): - # pylint: disable=protected-access - return descriptor._message.DescriptorPool(descriptor_db) + def __new__(cls, descriptor_db=None): + # pylint: disable=protected-access + return descriptor._message.DescriptorPool(descriptor_db) - def __init__(self, descriptor_db=None): + def __init__( + self, descriptor_db=None, use_deprecated_legacy_json_field_conflicts=False + ): """Initializes a Pool of proto buffs. The descriptor_db argument to the constructor is provided to allow @@ -135,6 +137,8 @@ def __init__(self, descriptor_db=None): Args: descriptor_db: A secondary source of file descriptors. + use_deprecated_legacy_json_field_conflicts: Unused, for compatibility with + C++. """ self._internal_db = descriptor_database.DescriptorDatabase() @@ -144,9 +148,6 @@ def __init__(self, descriptor_db=None): self._service_descriptors = {} self._file_descriptors = {} self._toplevel_extensions = {} - # TODO(jieluo): Remove _file_desc_by_toplevel_extension after - # maybe year 2020 for compatibility issue (with 3.4.1 only). - self._file_desc_by_toplevel_extension = {} self._top_enum_values = {} # We store extensions in two two-level mappings: The first key is the # descriptor of the message being extended, the second key is the extension @@ -220,7 +221,7 @@ def AddSerializedFile(self, serialized_file_desc_proto): file_desc.serialized_pb = serialized_file_desc_proto return file_desc - # Add Descriptor to descriptor pool is dreprecated. Please use Add() + # Add Descriptor to descriptor pool is deprecated. Please use Add() # or AddSerializedFile() to add a FileDescriptorProto instead. @_Deprecated def AddDescriptor(self, desc): @@ -245,7 +246,7 @@ def _AddDescriptor(self, desc): self._descriptors[desc.full_name] = desc self._AddFileDescriptor(desc.file) - # Add EnumDescriptor to descriptor pool is dreprecated. Please use Add() + # Add EnumDescriptor to descriptor pool is deprecated. Please use Add() # or AddSerializedFile() to add a FileDescriptorProto instead. @_Deprecated def AddEnumDescriptor(self, enum_desc): @@ -286,7 +287,7 @@ def _AddEnumDescriptor(self, enum_desc): self._top_enum_values[full_name] = enum_value self._AddFileDescriptor(enum_desc.file) - # Add ServiceDescriptor to descriptor pool is dreprecated. Please use Add() + # Add ServiceDescriptor to descriptor pool is deprecated. Please use Add() # or AddSerializedFile() to add a FileDescriptorProto instead. @_Deprecated def AddServiceDescriptor(self, service_desc): @@ -307,7 +308,7 @@ def _AddServiceDescriptor(self, service_desc): service_desc.file.name) self._service_descriptors[service_desc.full_name] = service_desc - # Add ExtensionDescriptor to descriptor pool is dreprecated. Please use Add() + # Add ExtensionDescriptor to descriptor pool is deprecated. Please use Add() # or AddSerializedFile() to add a FileDescriptorProto instead. @_Deprecated def AddExtensionDescriptor(self, extension): @@ -331,6 +332,8 @@ def _AddExtensionDescriptor(self, extension): raise TypeError('Expected an extension descriptor.') if extension.extension_scope is None: + self._CheckConflictRegister( + extension, extension.full_name, extension.file.name) self._toplevel_extensions[extension.full_name] = extension try: @@ -372,12 +375,6 @@ def _InternalAddFileDescriptor(self, file_desc): """ self._AddFileDescriptor(file_desc) - # TODO(jieluo): This is a temporary solution for FieldDescriptor.file. - # FieldDescriptor.file is added in code gen. Remove this solution after - # maybe 2020 for compatibility reason (with 3.4.1 only). - for extension in file_desc.extensions_by_name.values(): - self._file_desc_by_toplevel_extension[ - extension.full_name] = file_desc def _AddFileDescriptor(self, file_desc): """Adds a FileDescriptor to the pool, non-recursively. @@ -483,7 +480,7 @@ def _InternalFindFileContainingSymbol(self, symbol): pass try: - return self._file_desc_by_toplevel_extension[symbol] + return self._toplevel_extensions[symbol].file except KeyError: pass @@ -792,8 +789,6 @@ def _ConvertFileProtoToFileDescriptor(self, file_proto): file_descriptor.package, scope) file_descriptor.extensions_by_name[extension_desc.name] = ( extension_desc) - self._file_desc_by_toplevel_extension[extension_desc.full_name] = ( - file_descriptor) for desc_proto in file_proto.message_type: self._SetAllFieldTypes(file_proto.package, desc_proto, scope) diff --git a/ext/protobuf/Python/google/protobuf/duration_pb2.py b/ext/protobuf/Python/google/protobuf/duration_pb2.py index d11409e5d..f5309c1c4 100644 --- a/ext/protobuf/Python/google/protobuf/duration_pb2.py +++ b/ext/protobuf/Python/google/protobuf/duration_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1egoogle/protobuf/duration.proto\x12\x0fgoogle.protobuf\":\n\x08\x44uration\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanosB\x83\x01\n\x13\x63om.google.protobufB\rDurationProtoP\x01Z1google.golang.org/protobuf/types/known/durationpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.duration_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.duration_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\rDurationProtoP\001Z1google.golang.org/protobuf/types/known/durationpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _DURATION._serialized_start=51 - _DURATION._serialized_end=109 + _globals['_DURATION']._serialized_start=51 + _globals['_DURATION']._serialized_end=109 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/empty_pb2.py b/ext/protobuf/Python/google/protobuf/empty_pb2.py index 0b4d554db..d4d36580e 100644 --- a/ext/protobuf/Python/google/protobuf/empty_pb2.py +++ b/ext/protobuf/Python/google/protobuf/empty_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bgoogle/protobuf/empty.proto\x12\x0fgoogle.protobuf\"\x07\n\x05\x45mptyB}\n\x13\x63om.google.protobufB\nEmptyProtoP\x01Z.google.golang.org/protobuf/types/known/emptypb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.empty_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.empty_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\nEmptyProtoP\001Z.google.golang.org/protobuf/types/known/emptypb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _EMPTY._serialized_start=48 - _EMPTY._serialized_end=55 + _globals['_EMPTY']._serialized_start=48 + _globals['_EMPTY']._serialized_end=55 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/field_mask_pb2.py b/ext/protobuf/Python/google/protobuf/field_mask_pb2.py index a4f60fd02..645aae888 100644 --- a/ext/protobuf/Python/google/protobuf/field_mask_pb2.py +++ b/ext/protobuf/Python/google/protobuf/field_mask_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n google/protobuf/field_mask.proto\x12\x0fgoogle.protobuf\"!\n\tFieldMask\x12\x14\n\x05paths\x18\x01 \x03(\tR\x05pathsB\x85\x01\n\x13\x63om.google.protobufB\x0e\x46ieldMaskProtoP\x01Z2google.golang.org/protobuf/types/known/fieldmaskpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.field_mask_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.field_mask_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\016FieldMaskProtoP\001Z2google.golang.org/protobuf/types/known/fieldmaskpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _FIELDMASK._serialized_start=53 - _FIELDMASK._serialized_end=86 + _globals['_FIELDMASK']._serialized_start=53 + _globals['_FIELDMASK']._serialized_end=86 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/internal/__init__.py b/ext/protobuf/Python/google/protobuf/internal/__init__.py index e69de29bb..7d2e571a1 100644 --- a/ext/protobuf/Python/google/protobuf/internal/__init__.py +++ b/ext/protobuf/Python/google/protobuf/internal/__init__.py @@ -0,0 +1,30 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# https://developers.google.com/protocol-buffers/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/ext/protobuf/Python/google/protobuf/internal/_parameterized.py b/ext/protobuf/Python/google/protobuf/internal/_parameterized.py index afdbb78c3..2f4a3b6b7 100644 --- a/ext/protobuf/Python/google/protobuf/internal/_parameterized.py +++ b/ext/protobuf/Python/google/protobuf/internal/_parameterized.py @@ -37,8 +37,8 @@ A simple example: - class AdditionExample(parameterized.TestCase): - @parameterized.parameters( + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( (1, 2, 3), (4, 5, 9), (1, 1, 3)) @@ -54,8 +54,8 @@ def testAddition(self, op1, op2, result): Parameters for individual test cases can be tuples (with positional parameters) or dictionaries (with named parameters): - class AdditionExample(parameterized.TestCase): - @parameterized.parameters( + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( {'op1': 1, 'op2': 2, 'result': 3}, {'op1': 4, 'op2': 5, 'result': 9}, ) @@ -82,8 +82,8 @@ def testAddition(self, op1, op2, result): be a string (or an object that returns an apt name when converted via str()): - class NamedExample(parameterized.TestCase): - @parameterized.named_parameters( + class NamedExample(_parameterized.TestCase): + @_parameterized.named_parameters( ('Normal', 'aa', 'aaa', True), ('EmptyPrefix', '', 'abc', True), ('BothEmpty', '', '', True)) @@ -106,10 +106,10 @@ def testStartsWith(self, prefix, string, result): TestCase class, instead of decorating all test methods individually, the class itself can be decorated: - @parameterized.parameters( + @_parameterized.parameters( (1, 2, 3) (4, 5, 9)) - class ArithmeticTest(parameterized.TestCase): + class ArithmeticTest(_parameterized.TestCase): def testAdd(self, arg1, arg2, result): self.assertEqual(arg1 + arg2, result) @@ -122,8 +122,8 @@ def testSubtract(self, arg2, arg2, result): created from other sources, a single non-tuple iterable can be passed into the decorator. This iterable will be used to obtain the test cases: - class AdditionExample(parameterized.TestCase): - @parameterized.parameters( + class AdditionExample(_parameterized.TestCase): + @_parameterized.parameters( c.op1, c.op2, c.result for c in testcases ) def testAddition(self, op1, op2, result): @@ -135,8 +135,8 @@ def testAddition(self, op1, op2, result): If a test method takes only one argument, the single argument does not need to be wrapped into a tuple: - class NegativeNumberExample(parameterized.TestCase): - @parameterized.parameters( + class NegativeNumberExample(_parameterized.TestCase): + @_parameterized.parameters( -1, -3, -4, -5 ) def testIsNegative(self, arg): @@ -423,7 +423,7 @@ def CoopTestCase(other_base_class): import google3 import mox - from google3.testing.pybase import parameterized + from google.protobuf.internal import _parameterized class ExampleTest(parameterized.CoopTestCase(mox.MoxTestBase)): ... diff --git a/ext/protobuf/Python/google/protobuf/internal/api_implementation.py b/ext/protobuf/Python/google/protobuf/internal/api_implementation.py index 74586487a..7d20bd221 100644 --- a/ext/protobuf/Python/google/protobuf/internal/api_implementation.py +++ b/ext/protobuf/Python/google/protobuf/internal/api_implementation.py @@ -102,6 +102,7 @@ def _CanImport(mod_name): try: # pylint: disable=g-import-not-at-top from google.protobuf.pyext import _message + sys.modules['google3.net.proto2.python.internal.cpp._message'] = _message _c_module = _message del _message except ImportError: @@ -151,12 +152,6 @@ def Type(): return _implementation_type -def _SetType(implementation_type): - """Never use! Only for protobuf benchmark.""" - global _implementation_type - _implementation_type = implementation_type - - # See comment on 'Type' above. # TODO(jieluo): Remove the API, it returns a constant. b/228102101 def Version(): diff --git a/ext/protobuf/Python/google/protobuf/internal/decoder.py b/ext/protobuf/Python/google/protobuf/internal/decoder.py index a91627631..8ff549381 100644 --- a/ext/protobuf/Python/google/protobuf/internal/decoder.py +++ b/ext/protobuf/Python/google/protobuf/internal/decoder.py @@ -806,8 +806,7 @@ def DecodeItem(buffer, pos, end, message, field_dict): if value is None: message_type = extension.message_type if not hasattr(message_type, '_concrete_class'): - # pylint: disable=protected-access - message._FACTORY.GetPrototype(message_type) + message_factory.GetMessageClass(message_type) value = field_dict.setdefault( extension, message_type._concrete_class()) if value._InternalParse(buffer, message_start,message_end) != message_end: diff --git a/ext/protobuf/Python/google/protobuf/internal/descriptor_database_test.py b/ext/protobuf/Python/google/protobuf/internal/descriptor_database_test.py deleted file mode 100644 index 3c086b924..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/descriptor_database_test.py +++ /dev/null @@ -1,127 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.descriptor_database.""" - -__author__ = 'matthewtoia@google.com (Matt Toia)' - -import unittest -import warnings - -from google.protobuf import unittest_pb2 -from google.protobuf import descriptor_pb2 -from google.protobuf.internal import factory_test2_pb2 -from google.protobuf.internal import no_package_pb2 -from google.protobuf.internal import testing_refleaks -from google.protobuf import descriptor_database - - -@testing_refleaks.TestCase -class DescriptorDatabaseTest(unittest.TestCase): - - def testAdd(self): - db = descriptor_database.DescriptorDatabase() - file_desc_proto = descriptor_pb2.FileDescriptorProto.FromString( - factory_test2_pb2.DESCRIPTOR.serialized_pb) - file_desc_proto2 = descriptor_pb2.FileDescriptorProto.FromString( - no_package_pb2.DESCRIPTOR.serialized_pb) - db.Add(file_desc_proto) - db.Add(file_desc_proto2) - - self.assertEqual(file_desc_proto, db.FindFileByName( - 'google/protobuf/internal/factory_test2.proto')) - # Can find message type. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message')) - # Can find nested message type. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.NestedFactory2Message')) - # Can find enum type. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Enum')) - # Can find nested enum type. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.NestedFactory2Enum')) - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.MessageWithNestedEnumOnly.NestedEnum')) - # Can find field. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.list_field')) - # Can find enum value. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Enum.FACTORY_2_VALUE_0')) - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.FACTORY_2_VALUE_0')) - self.assertEqual(file_desc_proto2, db.FindFileContainingSymbol( - '.NO_PACKAGE_VALUE_0')) - # Can find top level extension. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.another_field')) - # Can find nested extension inside a message. - self.assertEqual(file_desc_proto, db.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.one_more_field')) - - # Can find service. - file_desc_proto2 = descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb) - db.Add(file_desc_proto2) - self.assertEqual(file_desc_proto2, db.FindFileContainingSymbol( - 'protobuf_unittest.TestService')) - - # Non-existent field under a valid top level symbol can also be - # found. The behavior is the same with protobuf C++. - self.assertEqual(file_desc_proto2, db.FindFileContainingSymbol( - 'protobuf_unittest.TestAllTypes.none_field')) - - with self.assertRaisesRegex(KeyError, r'\'protobuf_unittest\.NoneMessage\''): - db.FindFileContainingSymbol('protobuf_unittest.NoneMessage') - - def testConflictRegister(self): - db = descriptor_database.DescriptorDatabase() - unittest_fd = descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb) - db.Add(unittest_fd) - conflict_fd = descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb) - conflict_fd.name = 'other_file2' - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter('always') - db.Add(conflict_fd) - self.assertTrue(len(w)) - self.assertIs(w[0].category, RuntimeWarning) - self.assertIn('Conflict register for file "other_file2": ', - str(w[0].message)) - self.assertIn('already defined in file ' - '"google/protobuf/unittest.proto"', - str(w[0].message)) - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/descriptor_pool_test.py b/ext/protobuf/Python/google/protobuf/internal/descriptor_pool_test.py deleted file mode 100644 index 9e451b45b..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/descriptor_pool_test.py +++ /dev/null @@ -1,1149 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.descriptor_pool.""" - -__author__ = 'matthewtoia@google.com (Matt Toia)' - -import copy -import os -import unittest -import warnings - -from google.protobuf import unittest_import_pb2 -from google.protobuf import unittest_import_public_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import descriptor_pb2 -from google.protobuf.internal import api_implementation -from google.protobuf.internal import descriptor_pool_test1_pb2 -from google.protobuf.internal import descriptor_pool_test2_pb2 -from google.protobuf.internal import factory_test1_pb2 -from google.protobuf.internal import factory_test2_pb2 -from google.protobuf.internal import file_options_test_pb2 -from google.protobuf.internal import more_messages_pb2 -from google.protobuf.internal import no_package_pb2 -from google.protobuf.internal import testing_refleaks -from google.protobuf import descriptor -from google.protobuf import descriptor_database -from google.protobuf import descriptor_pool -from google.protobuf import message_factory -from google.protobuf import symbol_database - - - -warnings.simplefilter('error', DeprecationWarning) - - -class DescriptorPoolTestBase(object): - - def testFindFileByName(self): - name1 = 'google/protobuf/internal/factory_test1.proto' - file_desc1 = self.pool.FindFileByName(name1) - self.assertIsInstance(file_desc1, descriptor.FileDescriptor) - self.assertEqual(name1, file_desc1.name) - self.assertEqual('google.protobuf.python.internal', file_desc1.package) - self.assertIn('Factory1Message', file_desc1.message_types_by_name) - - name2 = 'google/protobuf/internal/factory_test2.proto' - file_desc2 = self.pool.FindFileByName(name2) - self.assertIsInstance(file_desc2, descriptor.FileDescriptor) - self.assertEqual(name2, file_desc2.name) - self.assertEqual('google.protobuf.python.internal', file_desc2.package) - self.assertIn('Factory2Message', file_desc2.message_types_by_name) - - def testFindFileByNameFailure(self): - with self.assertRaises(KeyError): - self.pool.FindFileByName('Does not exist') - - def testFindFileContainingSymbol(self): - file_desc1 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory1Message') - self.assertIsInstance(file_desc1, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test1.proto', - file_desc1.name) - self.assertEqual('google.protobuf.python.internal', file_desc1.package) - self.assertIn('Factory1Message', file_desc1.message_types_by_name) - - file_desc2 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message') - self.assertIsInstance(file_desc2, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test2.proto', - file_desc2.name) - self.assertEqual('google.protobuf.python.internal', file_desc2.package) - self.assertIn('Factory2Message', file_desc2.message_types_by_name) - - # Tests top level extension. - file_desc3 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.another_field') - self.assertIsInstance(file_desc3, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test2.proto', - file_desc3.name) - - # Tests nested extension inside a message. - file_desc4 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.one_more_field') - self.assertIsInstance(file_desc4, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test2.proto', - file_desc4.name) - - file_desc5 = self.pool.FindFileContainingSymbol( - 'protobuf_unittest.TestService') - self.assertIsInstance(file_desc5, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/unittest.proto', - file_desc5.name) - # Tests the generated pool. - assert descriptor_pool.Default().FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory2Message.one_more_field') - assert descriptor_pool.Default().FindFileContainingSymbol( - 'google.protobuf.python.internal.another_field') - assert descriptor_pool.Default().FindFileContainingSymbol( - 'protobuf_unittest.TestService') - - # Can find field. - file_desc6 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.Factory1Message.list_value') - self.assertIsInstance(file_desc6, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test1.proto', - file_desc6.name) - - # Can find top level Enum value. - file_desc7 = self.pool.FindFileContainingSymbol( - 'google.protobuf.python.internal.FACTORY_1_VALUE_0') - self.assertIsInstance(file_desc7, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/internal/factory_test1.proto', - file_desc7.name) - - # Can find nested Enum value. - file_desc8 = self.pool.FindFileContainingSymbol( - 'protobuf_unittest.TestAllTypes.FOO') - self.assertIsInstance(file_desc8, descriptor.FileDescriptor) - self.assertEqual('google/protobuf/unittest.proto', - file_desc8.name) - - # TODO(jieluo): Add tests for no package when b/13860351 is fixed. - - self.assertRaises(KeyError, self.pool.FindFileContainingSymbol, - 'google.protobuf.python.internal.Factory1Message.none_field') - - def testFindFileContainingSymbolFailure(self): - with self.assertRaises(KeyError): - self.pool.FindFileContainingSymbol('Does not exist') - - def testFindMessageTypeByName(self): - msg1 = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory1Message') - self.assertIsInstance(msg1, descriptor.Descriptor) - self.assertEqual('Factory1Message', msg1.name) - self.assertEqual('google.protobuf.python.internal.Factory1Message', - msg1.full_name) - self.assertEqual(None, msg1.containing_type) - self.assertFalse(msg1.has_options) - - nested_msg1 = msg1.nested_types[0] - self.assertEqual('NestedFactory1Message', nested_msg1.name) - self.assertEqual(msg1, nested_msg1.containing_type) - - nested_enum1 = msg1.enum_types[0] - self.assertEqual('NestedFactory1Enum', nested_enum1.name) - self.assertEqual(msg1, nested_enum1.containing_type) - - self.assertEqual(nested_msg1, msg1.fields_by_name[ - 'nested_factory_1_message'].message_type) - self.assertEqual(nested_enum1, msg1.fields_by_name[ - 'nested_factory_1_enum'].enum_type) - - msg2 = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message') - self.assertIsInstance(msg2, descriptor.Descriptor) - self.assertEqual('Factory2Message', msg2.name) - self.assertEqual('google.protobuf.python.internal.Factory2Message', - msg2.full_name) - self.assertIsNone(msg2.containing_type) - - nested_msg2 = msg2.nested_types[0] - self.assertEqual('NestedFactory2Message', nested_msg2.name) - self.assertEqual(msg2, nested_msg2.containing_type) - - nested_enum2 = msg2.enum_types[0] - self.assertEqual('NestedFactory2Enum', nested_enum2.name) - self.assertEqual(msg2, nested_enum2.containing_type) - - self.assertEqual(nested_msg2, msg2.fields_by_name[ - 'nested_factory_2_message'].message_type) - self.assertEqual(nested_enum2, msg2.fields_by_name[ - 'nested_factory_2_enum'].enum_type) - - self.assertTrue(msg2.fields_by_name['int_with_default'].has_default_value) - self.assertEqual( - 1776, msg2.fields_by_name['int_with_default'].default_value) - - self.assertTrue( - msg2.fields_by_name['double_with_default'].has_default_value) - self.assertEqual( - 9.99, msg2.fields_by_name['double_with_default'].default_value) - - self.assertTrue( - msg2.fields_by_name['string_with_default'].has_default_value) - self.assertEqual( - 'hello world', msg2.fields_by_name['string_with_default'].default_value) - - self.assertTrue(msg2.fields_by_name['bool_with_default'].has_default_value) - self.assertFalse(msg2.fields_by_name['bool_with_default'].default_value) - - self.assertTrue(msg2.fields_by_name['enum_with_default'].has_default_value) - self.assertEqual( - 1, msg2.fields_by_name['enum_with_default'].default_value) - - msg3 = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message.NestedFactory2Message') - self.assertEqual(nested_msg2, msg3) - - self.assertTrue(msg2.fields_by_name['bytes_with_default'].has_default_value) - self.assertEqual( - b'a\xfb\x00c', - msg2.fields_by_name['bytes_with_default'].default_value) - - self.assertEqual(1, len(msg2.oneofs)) - self.assertEqual(1, len(msg2.oneofs_by_name)) - self.assertEqual(2, len(msg2.oneofs[0].fields)) - for name in ['oneof_int', 'oneof_string']: - self.assertEqual(msg2.oneofs[0], - msg2.fields_by_name[name].containing_oneof) - self.assertIn(msg2.fields_by_name[name], msg2.oneofs[0].fields) - - def testFindTypeErrors(self): - self.assertRaises(TypeError, self.pool.FindExtensionByNumber, '') - self.assertRaises(KeyError, self.pool.FindMethodByName, '') - - # TODO(jieluo): Fix python to raise correct errors. - if api_implementation.Type() == 'python': - error_type = AttributeError - else: - error_type = TypeError - self.assertRaises(error_type, self.pool.FindMessageTypeByName, 0) - self.assertRaises(error_type, self.pool.FindFieldByName, 0) - self.assertRaises(error_type, self.pool.FindExtensionByName, 0) - self.assertRaises(error_type, self.pool.FindEnumTypeByName, 0) - self.assertRaises(error_type, self.pool.FindOneofByName, 0) - self.assertRaises(error_type, self.pool.FindServiceByName, 0) - self.assertRaises(error_type, self.pool.FindMethodByName, 0) - self.assertRaises(error_type, self.pool.FindFileContainingSymbol, 0) - if api_implementation.Type() == 'python': - error_type = KeyError - self.assertRaises(error_type, self.pool.FindFileByName, 0) - - def testFindMessageTypeByNameFailure(self): - with self.assertRaises(KeyError): - self.pool.FindMessageTypeByName('Does not exist') - - def testFindEnumTypeByName(self): - enum1 = self.pool.FindEnumTypeByName( - 'google.protobuf.python.internal.Factory1Enum') - self.assertIsInstance(enum1, descriptor.EnumDescriptor) - self.assertEqual(0, enum1.values_by_name['FACTORY_1_VALUE_0'].number) - self.assertEqual(1, enum1.values_by_name['FACTORY_1_VALUE_1'].number) - self.assertFalse(enum1.has_options) - - nested_enum1 = self.pool.FindEnumTypeByName( - 'google.protobuf.python.internal.Factory1Message.NestedFactory1Enum') - self.assertIsInstance(nested_enum1, descriptor.EnumDescriptor) - self.assertEqual( - 0, nested_enum1.values_by_name['NESTED_FACTORY_1_VALUE_0'].number) - self.assertEqual( - 1, nested_enum1.values_by_name['NESTED_FACTORY_1_VALUE_1'].number) - - enum2 = self.pool.FindEnumTypeByName( - 'google.protobuf.python.internal.Factory2Enum') - self.assertIsInstance(enum2, descriptor.EnumDescriptor) - self.assertEqual(0, enum2.values_by_name['FACTORY_2_VALUE_0'].number) - self.assertEqual(1, enum2.values_by_name['FACTORY_2_VALUE_1'].number) - - nested_enum2 = self.pool.FindEnumTypeByName( - 'google.protobuf.python.internal.Factory2Message.NestedFactory2Enum') - self.assertIsInstance(nested_enum2, descriptor.EnumDescriptor) - self.assertEqual( - 0, nested_enum2.values_by_name['NESTED_FACTORY_2_VALUE_0'].number) - self.assertEqual( - 1, nested_enum2.values_by_name['NESTED_FACTORY_2_VALUE_1'].number) - - def testFindEnumTypeByNameFailure(self): - with self.assertRaises(KeyError): - self.pool.FindEnumTypeByName('Does not exist') - - def testFindFieldByName(self): - field = self.pool.FindFieldByName( - 'google.protobuf.python.internal.Factory1Message.list_value') - self.assertEqual(field.name, 'list_value') - self.assertEqual(field.label, field.LABEL_REPEATED) - self.assertFalse(field.has_options) - - with self.assertRaises(KeyError): - self.pool.FindFieldByName('Does not exist') - - def testFindOneofByName(self): - oneof = self.pool.FindOneofByName( - 'google.protobuf.python.internal.Factory2Message.oneof_field') - self.assertEqual(oneof.name, 'oneof_field') - with self.assertRaises(KeyError): - self.pool.FindOneofByName('Does not exist') - - def testFindExtensionByName(self): - # An extension defined in a message. - extension = self.pool.FindExtensionByName( - 'google.protobuf.python.internal.Factory2Message.one_more_field') - self.assertEqual(extension.name, 'one_more_field') - # An extension defined at file scope. - extension = self.pool.FindExtensionByName( - 'google.protobuf.python.internal.another_field') - self.assertEqual(extension.name, 'another_field') - self.assertEqual(extension.number, 1002) - with self.assertRaises(KeyError): - self.pool.FindFieldByName('Does not exist') - - def testFindAllExtensions(self): - factory1_message = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory1Message') - factory2_message = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message') - # An extension defined in a message. - one_more_field = factory2_message.extensions_by_name['one_more_field'] - # An extension defined at file scope. - factory_test2 = self.pool.FindFileByName( - 'google/protobuf/internal/factory_test2.proto') - another_field = factory_test2.extensions_by_name['another_field'] - - extensions = self.pool.FindAllExtensions(factory1_message) - expected_extension_numbers = set([one_more_field, another_field]) - self.assertEqual(expected_extension_numbers, set(extensions)) - # Verify that mutating the returned list does not affect the pool. - extensions.append('unexpected_element') - # Get the extensions again, the returned value does not contain the - # 'unexpected_element'. - extensions = self.pool.FindAllExtensions(factory1_message) - self.assertEqual(expected_extension_numbers, set(extensions)) - - def testFindExtensionByNumber(self): - factory1_message = self.pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory1Message') - # Build factory_test2.proto which will put extensions to the pool - self.pool.FindFileByName( - 'google/protobuf/internal/factory_test2.proto') - - # An extension defined in a message. - extension = self.pool.FindExtensionByNumber(factory1_message, 1001) - self.assertEqual(extension.name, 'one_more_field') - # An extension defined at file scope. - extension = self.pool.FindExtensionByNumber(factory1_message, 1002) - self.assertEqual(extension.name, 'another_field') - with self.assertRaises(KeyError): - extension = self.pool.FindExtensionByNumber(factory1_message, 1234567) - - def testExtensionsAreNotFields(self): - with self.assertRaises(KeyError): - self.pool.FindFieldByName('google.protobuf.python.internal.another_field') - with self.assertRaises(KeyError): - self.pool.FindFieldByName( - 'google.protobuf.python.internal.Factory2Message.one_more_field') - with self.assertRaises(KeyError): - self.pool.FindExtensionByName( - 'google.protobuf.python.internal.Factory1Message.list_value') - - def testFindService(self): - service = self.pool.FindServiceByName('protobuf_unittest.TestService') - self.assertEqual(service.full_name, 'protobuf_unittest.TestService') - with self.assertRaises(KeyError): - self.pool.FindServiceByName('Does not exist') - - method = self.pool.FindMethodByName('protobuf_unittest.TestService.Foo') - self.assertIs(method.containing_service, service) - with self.assertRaises(KeyError): - self.pool.FindMethodByName('protobuf_unittest.TestService.Doesnotexist') - - def testUserDefinedDB(self): - db = descriptor_database.DescriptorDatabase() - self.pool = descriptor_pool.DescriptorPool(db) - db.Add(self.factory_test1_fd) - db.Add(self.factory_test2_fd) - self.testFindMessageTypeByName() - - def testAddSerializedFile(self): - if isinstance(self, SecondaryDescriptorFromDescriptorDB): - if api_implementation.Type() != 'python': - # Cpp extension cannot call Add on a DescriptorPool - # that uses a DescriptorDatabase. - # TODO(jieluo): Fix python and cpp extension diff. - return - self.pool = descriptor_pool.DescriptorPool() - file1 = self.pool.AddSerializedFile( - self.factory_test1_fd.SerializeToString()) - file2 = self.pool.AddSerializedFile( - self.factory_test2_fd.SerializeToString()) - self.assertEqual(file1.name, - 'google/protobuf/internal/factory_test1.proto') - self.assertEqual(file2.name, - 'google/protobuf/internal/factory_test2.proto') - self.testFindMessageTypeByName() - file_json = self.pool.AddSerializedFile( - more_messages_pb2.DESCRIPTOR.serialized_pb) - field = file_json.message_types_by_name['class'].fields_by_name['int_field'] - self.assertEqual(field.json_name, 'json_int') - - - def testEnumDefaultValue(self): - """Test the default value of enums which don't start at zero.""" - def _CheckDefaultValue(file_descriptor): - default_value = (file_descriptor - .message_types_by_name['DescriptorPoolTest1'] - .fields_by_name['nested_enum'] - .default_value) - self.assertEqual(default_value, - descriptor_pool_test1_pb2.DescriptorPoolTest1.BETA) - # First check what the generated descriptor contains. - _CheckDefaultValue(descriptor_pool_test1_pb2.DESCRIPTOR) - # Then check the generated pool. Normally this is the same descriptor. - file_descriptor = symbol_database.Default().pool.FindFileByName( - 'google/protobuf/internal/descriptor_pool_test1.proto') - self.assertIs(file_descriptor, descriptor_pool_test1_pb2.DESCRIPTOR) - _CheckDefaultValue(file_descriptor) - - if isinstance(self, SecondaryDescriptorFromDescriptorDB): - if api_implementation.Type() != 'python': - # Cpp extension cannot call Add on a DescriptorPool - # that uses a DescriptorDatabase. - # TODO(jieluo): Fix python and cpp extension diff. - return - # Then check the dynamic pool and its internal DescriptorDatabase. - descriptor_proto = descriptor_pb2.FileDescriptorProto.FromString( - descriptor_pool_test1_pb2.DESCRIPTOR.serialized_pb) - self.pool.Add(descriptor_proto) - # And do the same check as above - file_descriptor = self.pool.FindFileByName( - 'google/protobuf/internal/descriptor_pool_test1.proto') - _CheckDefaultValue(file_descriptor) - - def testDefaultValueForCustomMessages(self): - """Check the value returned by non-existent fields.""" - def _CheckValueAndType(value, expected_value, expected_type): - self.assertEqual(value, expected_value) - self.assertIsInstance(value, expected_type) - - def _CheckDefaultValues(msg): - try: - int64 = long - except NameError: # Python3 - int64 = int - try: - unicode_type = unicode - except NameError: # Python3 - unicode_type = str - _CheckValueAndType(msg.optional_int32, 0, int) - _CheckValueAndType(msg.optional_uint64, 0, (int64, int)) - _CheckValueAndType(msg.optional_float, 0, (float, int)) - _CheckValueAndType(msg.optional_double, 0, (float, int)) - _CheckValueAndType(msg.optional_bool, False, bool) - _CheckValueAndType(msg.optional_string, u'', unicode_type) - _CheckValueAndType(msg.optional_bytes, b'', bytes) - _CheckValueAndType(msg.optional_nested_enum, msg.FOO, int) - # First for the generated message - _CheckDefaultValues(unittest_pb2.TestAllTypes()) - # Then for a message built with from the DescriptorPool. - pool = descriptor_pool.DescriptorPool() - pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_public_pb2.DESCRIPTOR.serialized_pb)) - pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_pb2.DESCRIPTOR.serialized_pb)) - pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb)) - message_class = message_factory.MessageFactory(pool).GetPrototype( - pool.FindMessageTypeByName( - unittest_pb2.TestAllTypes.DESCRIPTOR.full_name)) - _CheckDefaultValues(message_class()) - - def testAddFileDescriptor(self): - if isinstance(self, SecondaryDescriptorFromDescriptorDB): - if api_implementation.Type() != 'python': - # Cpp extension cannot call Add on a DescriptorPool - # that uses a DescriptorDatabase. - # TODO(jieluo): Fix python and cpp extension diff. - return - file_desc = descriptor_pb2.FileDescriptorProto(name='some/file.proto') - self.pool.Add(file_desc) - self.pool.AddSerializedFile(file_desc.SerializeToString()) - - def testComplexNesting(self): - if isinstance(self, SecondaryDescriptorFromDescriptorDB): - if api_implementation.Type() != 'python': - # Cpp extension cannot call Add on a DescriptorPool - # that uses a DescriptorDatabase. - # TODO(jieluo): Fix python and cpp extension diff. - return - more_messages_desc = descriptor_pb2.FileDescriptorProto.FromString( - more_messages_pb2.DESCRIPTOR.serialized_pb) - test1_desc = descriptor_pb2.FileDescriptorProto.FromString( - descriptor_pool_test1_pb2.DESCRIPTOR.serialized_pb) - test2_desc = descriptor_pb2.FileDescriptorProto.FromString( - descriptor_pool_test2_pb2.DESCRIPTOR.serialized_pb) - self.pool.Add(more_messages_desc) - self.pool.Add(test1_desc) - self.pool.Add(test2_desc) - TEST1_FILE.CheckFile(self, self.pool) - TEST2_FILE.CheckFile(self, self.pool) - - def testConflictRegister(self): - if isinstance(self, SecondaryDescriptorFromDescriptorDB): - if api_implementation.Type() != 'python': - # Cpp extension cannot call Add on a DescriptorPool - # that uses a DescriptorDatabase. - # TODO(jieluo): Fix python and cpp extension diff. - return - unittest_fd = descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb) - conflict_fd = copy.deepcopy(unittest_fd) - conflict_fd.name = 'other_file' - if api_implementation.Type() != 'python': - pass - else: - pool = copy.deepcopy(self.pool) - file_descriptor = unittest_pb2.DESCRIPTOR - pool._AddDescriptor( - file_descriptor.message_types_by_name['TestAllTypes']) - pool._AddEnumDescriptor( - file_descriptor.enum_types_by_name['ForeignEnum']) - pool._AddServiceDescriptor( - file_descriptor.services_by_name['TestService']) - pool._AddExtensionDescriptor( - file_descriptor.extensions_by_name['optional_int32_extension']) - pool.Add(unittest_fd) - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - pool.Add(conflict_fd) - self.assertTrue(len(w)) - self.assertIs(w[0].category, RuntimeWarning) - self.assertIn('Conflict register for file "other_file": ', - str(w[0].message)) - pool.FindFileByName(unittest_fd.name) - with self.assertRaises(TypeError): - pool.FindFileByName(conflict_fd.name) - - -@testing_refleaks.TestCase -class DefaultDescriptorPoolTest(DescriptorPoolTestBase, unittest.TestCase): - - def setUp(self): - self.pool = descriptor_pool.Default() - self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test1_pb2.DESCRIPTOR.serialized_pb) - self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test2_pb2.DESCRIPTOR.serialized_pb) - - def testFindMethods(self): - self.assertIs( - self.pool.FindFileByName('google/protobuf/unittest.proto'), - unittest_pb2.DESCRIPTOR) - self.assertIs( - self.pool.FindMessageTypeByName('protobuf_unittest.TestAllTypes'), - unittest_pb2.TestAllTypes.DESCRIPTOR) - self.assertIs( - self.pool.FindFieldByName( - 'protobuf_unittest.TestAllTypes.optional_int32'), - unittest_pb2.TestAllTypes.DESCRIPTOR.fields_by_name['optional_int32']) - self.assertIs( - self.pool.FindEnumTypeByName('protobuf_unittest.ForeignEnum'), - unittest_pb2.ForeignEnum.DESCRIPTOR) - self.assertIs( - self.pool.FindExtensionByName( - 'protobuf_unittest.optional_int32_extension'), - unittest_pb2.DESCRIPTOR.extensions_by_name['optional_int32_extension']) - self.assertIs( - self.pool.FindOneofByName('protobuf_unittest.TestAllTypes.oneof_field'), - unittest_pb2.TestAllTypes.DESCRIPTOR.oneofs_by_name['oneof_field']) - self.assertIs( - self.pool.FindServiceByName('protobuf_unittest.TestService'), - unittest_pb2.DESCRIPTOR.services_by_name['TestService']) - - -@testing_refleaks.TestCase -class CreateDescriptorPoolTest(DescriptorPoolTestBase, unittest.TestCase): - - def setUp(self): - self.pool = descriptor_pool.DescriptorPool() - self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test1_pb2.DESCRIPTOR.serialized_pb) - self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test2_pb2.DESCRIPTOR.serialized_pb) - self.pool.Add(self.factory_test1_fd) - self.pool.Add(self.factory_test2_fd) - - self.pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_public_pb2.DESCRIPTOR.serialized_pb)) - self.pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_pb2.DESCRIPTOR.serialized_pb)) - self.pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb)) - self.pool.Add(descriptor_pb2.FileDescriptorProto.FromString( - no_package_pb2.DESCRIPTOR.serialized_pb)) - - -@testing_refleaks.TestCase -class SecondaryDescriptorFromDescriptorDB(DescriptorPoolTestBase, - unittest.TestCase): - - def setUp(self): - self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test1_pb2.DESCRIPTOR.serialized_pb) - self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test2_pb2.DESCRIPTOR.serialized_pb) - self.db = descriptor_database.DescriptorDatabase() - self.db.Add(self.factory_test1_fd) - self.db.Add(self.factory_test2_fd) - self.db.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_public_pb2.DESCRIPTOR.serialized_pb)) - self.db.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_import_pb2.DESCRIPTOR.serialized_pb)) - self.db.Add(descriptor_pb2.FileDescriptorProto.FromString( - unittest_pb2.DESCRIPTOR.serialized_pb)) - self.db.Add(descriptor_pb2.FileDescriptorProto.FromString( - no_package_pb2.DESCRIPTOR.serialized_pb)) - self.pool = descriptor_pool.DescriptorPool(descriptor_db=self.db) - - def testErrorCollector(self): - file_proto = descriptor_pb2.FileDescriptorProto() - file_proto.package = 'collector' - file_proto.name = 'error_file' - message_type = file_proto.message_type.add() - message_type.name = 'ErrorMessage' - field = message_type.field.add() - field.number = 1 - field.name = 'nested_message_field' - field.label = descriptor.FieldDescriptor.LABEL_OPTIONAL - field.type = descriptor.FieldDescriptor.TYPE_MESSAGE - field.type_name = 'SubMessage' - oneof = message_type.oneof_decl.add() - oneof.name = 'MyOneof' - enum_type = file_proto.enum_type.add() - enum_type.name = 'MyEnum' - enum_value = enum_type.value.add() - enum_value.name = 'MyEnumValue' - enum_value.number = 0 - self.db.Add(file_proto) - - self.assertRaisesRegex(KeyError, 'SubMessage', - self.pool.FindMessageTypeByName, - 'collector.ErrorMessage') - self.assertRaisesRegex(KeyError, 'SubMessage', self.pool.FindFileByName, - 'error_file') - with self.assertRaises(KeyError) as exc: - self.pool.FindFileByName('none_file') - self.assertIn(str(exc.exception), ('\'none_file\'', - '\"Couldn\'t find file none_file\"')) - - # Pure python _ConvertFileProtoToFileDescriptor() method has side effect - # that all the symbols found in the file will load into the pool even the - # file can not build. So when FindMessageTypeByName('ErrorMessage') was - # called the first time, a KeyError will be raised but call the find - # method later will return a descriptor which is not build. - # TODO(jieluo): fix pure python to revert the load if file can not be build - if api_implementation.Type() != 'python': - error_msg = ('Invalid proto descriptor for file "error_file":\\n ' - 'collector.ErrorMessage.nested_message_field: "SubMessage" ' - 'is not defined.\\n collector.ErrorMessage.MyOneof: Oneof ' - 'must have at least one field.\\n\'') - with self.assertRaises(KeyError) as exc: - self.pool.FindMessageTypeByName('collector.ErrorMessage') - self.assertEqual(str(exc.exception), '\'Couldn\\\'t build file for ' - 'message collector.ErrorMessage\\n' + error_msg) - - with self.assertRaises(KeyError) as exc: - self.pool.FindFieldByName('collector.ErrorMessage.nested_message_field') - self.assertEqual(str(exc.exception), '\'Couldn\\\'t build file for field' - ' collector.ErrorMessage.nested_message_field\\n' - + error_msg) - - with self.assertRaises(KeyError) as exc: - self.pool.FindEnumTypeByName('collector.MyEnum') - self.assertEqual(str(exc.exception), '\'Couldn\\\'t build file for enum' - ' collector.MyEnum\\n' + error_msg) - - with self.assertRaises(KeyError) as exc: - self.pool.FindFileContainingSymbol('collector.MyEnumValue') - self.assertEqual(str(exc.exception), '\'Couldn\\\'t build file for symbol' - ' collector.MyEnumValue\\n' + error_msg) - - with self.assertRaises(KeyError) as exc: - self.pool.FindOneofByName('collector.ErrorMessage.MyOneof') - self.assertEqual(str(exc.exception), '\'Couldn\\\'t build file for oneof' - ' collector.ErrorMessage.MyOneof\\n' + error_msg) - - -class ProtoFile(object): - - def __init__(self, name, package, messages, dependencies=None, - public_dependencies=None): - self.name = name - self.package = package - self.messages = messages - self.dependencies = dependencies or [] - self.public_dependencies = public_dependencies or [] - - def CheckFile(self, test, pool): - file_desc = pool.FindFileByName(self.name) - test.assertEqual(self.name, file_desc.name) - test.assertEqual(self.package, file_desc.package) - dependencies_names = [f.name for f in file_desc.dependencies] - test.assertEqual(self.dependencies, dependencies_names) - public_dependencies_names = [f.name for f in file_desc.public_dependencies] - test.assertEqual(self.public_dependencies, public_dependencies_names) - for name, msg_type in self.messages.items(): - msg_type.CheckType(test, None, name, file_desc) - - -class EnumType(object): - - def __init__(self, values): - self.values = values - - def CheckType(self, test, msg_desc, name, file_desc): - enum_desc = msg_desc.enum_types_by_name[name] - test.assertEqual(name, enum_desc.name) - expected_enum_full_name = '.'.join([msg_desc.full_name, name]) - test.assertEqual(expected_enum_full_name, enum_desc.full_name) - test.assertEqual(msg_desc, enum_desc.containing_type) - test.assertEqual(file_desc, enum_desc.file) - for index, (value, number) in enumerate(self.values): - value_desc = enum_desc.values_by_name[value] - test.assertEqual(value, value_desc.name) - test.assertEqual(index, value_desc.index) - test.assertEqual(number, value_desc.number) - test.assertEqual(enum_desc, value_desc.type) - test.assertIn(value, msg_desc.enum_values_by_name) - - -class MessageType(object): - - def __init__(self, type_dict, field_list, is_extendable=False, - extensions=None): - self.type_dict = type_dict - self.field_list = field_list - self.is_extendable = is_extendable - self.extensions = extensions or [] - - def CheckType(self, test, containing_type_desc, name, file_desc): - if containing_type_desc is None: - desc = file_desc.message_types_by_name[name] - expected_full_name = '.'.join([file_desc.package, name]) - else: - desc = containing_type_desc.nested_types_by_name[name] - expected_full_name = '.'.join([containing_type_desc.full_name, name]) - - test.assertEqual(name, desc.name) - test.assertEqual(expected_full_name, desc.full_name) - test.assertEqual(containing_type_desc, desc.containing_type) - test.assertEqual(desc.file, file_desc) - test.assertEqual(self.is_extendable, desc.is_extendable) - for name, subtype in self.type_dict.items(): - subtype.CheckType(test, desc, name, file_desc) - - for index, (name, field) in enumerate(self.field_list): - field.CheckField(test, desc, name, index, file_desc) - - for index, (name, field) in enumerate(self.extensions): - field.CheckField(test, desc, name, index, file_desc) - - -class EnumField(object): - - def __init__(self, number, type_name, default_value): - self.number = number - self.type_name = type_name - self.default_value = default_value - - def CheckField(self, test, msg_desc, name, index, file_desc): - field_desc = msg_desc.fields_by_name[name] - enum_desc = msg_desc.enum_types_by_name[self.type_name] - test.assertEqual(name, field_desc.name) - expected_field_full_name = '.'.join([msg_desc.full_name, name]) - test.assertEqual(expected_field_full_name, field_desc.full_name) - test.assertEqual(index, field_desc.index) - test.assertEqual(self.number, field_desc.number) - test.assertEqual(descriptor.FieldDescriptor.TYPE_ENUM, field_desc.type) - test.assertEqual(descriptor.FieldDescriptor.CPPTYPE_ENUM, - field_desc.cpp_type) - test.assertTrue(field_desc.has_default_value) - test.assertEqual(enum_desc.values_by_name[self.default_value].number, - field_desc.default_value) - test.assertFalse(enum_desc.values_by_name[self.default_value].has_options) - test.assertEqual(msg_desc, field_desc.containing_type) - test.assertEqual(enum_desc, field_desc.enum_type) - test.assertEqual(file_desc, enum_desc.file) - - -class MessageField(object): - - def __init__(self, number, type_name): - self.number = number - self.type_name = type_name - - def CheckField(self, test, msg_desc, name, index, file_desc): - field_desc = msg_desc.fields_by_name[name] - field_type_desc = msg_desc.nested_types_by_name[self.type_name] - test.assertEqual(name, field_desc.name) - expected_field_full_name = '.'.join([msg_desc.full_name, name]) - test.assertEqual(expected_field_full_name, field_desc.full_name) - test.assertEqual(index, field_desc.index) - test.assertEqual(self.number, field_desc.number) - test.assertEqual(descriptor.FieldDescriptor.TYPE_MESSAGE, field_desc.type) - test.assertEqual(descriptor.FieldDescriptor.CPPTYPE_MESSAGE, - field_desc.cpp_type) - test.assertFalse(field_desc.has_default_value) - test.assertEqual(msg_desc, field_desc.containing_type) - test.assertEqual(field_type_desc, field_desc.message_type) - test.assertEqual(file_desc, field_desc.file) - test.assertEqual(field_desc.default_value, None) - - -class StringField(object): - - def __init__(self, number, default_value): - self.number = number - self.default_value = default_value - - def CheckField(self, test, msg_desc, name, index, file_desc): - field_desc = msg_desc.fields_by_name[name] - test.assertEqual(name, field_desc.name) - expected_field_full_name = '.'.join([msg_desc.full_name, name]) - test.assertEqual(expected_field_full_name, field_desc.full_name) - test.assertEqual(index, field_desc.index) - test.assertEqual(self.number, field_desc.number) - test.assertEqual(descriptor.FieldDescriptor.TYPE_STRING, field_desc.type) - test.assertEqual(descriptor.FieldDescriptor.CPPTYPE_STRING, - field_desc.cpp_type) - test.assertTrue(field_desc.has_default_value) - test.assertEqual(self.default_value, field_desc.default_value) - test.assertEqual(file_desc, field_desc.file) - - -class ExtensionField(object): - - def __init__(self, number, extended_type): - self.number = number - self.extended_type = extended_type - - def CheckField(self, test, msg_desc, name, index, file_desc): - field_desc = msg_desc.extensions_by_name[name] - test.assertEqual(name, field_desc.name) - expected_field_full_name = '.'.join([msg_desc.full_name, name]) - test.assertEqual(expected_field_full_name, field_desc.full_name) - test.assertEqual(self.number, field_desc.number) - test.assertEqual(index, field_desc.index) - test.assertEqual(descriptor.FieldDescriptor.TYPE_MESSAGE, field_desc.type) - test.assertEqual(descriptor.FieldDescriptor.CPPTYPE_MESSAGE, - field_desc.cpp_type) - test.assertFalse(field_desc.has_default_value) - test.assertTrue(field_desc.is_extension) - test.assertEqual(msg_desc, field_desc.extension_scope) - test.assertEqual(msg_desc, field_desc.message_type) - test.assertEqual(self.extended_type, field_desc.containing_type.name) - test.assertEqual(file_desc, field_desc.file) - - -@testing_refleaks.TestCase -class AddDescriptorTest(unittest.TestCase): - - def _TestMessage(self, prefix): - pool = descriptor_pool.DescriptorPool() - pool._AddDescriptor(unittest_pb2.TestAllTypes.DESCRIPTOR) - self.assertEqual( - 'protobuf_unittest.TestAllTypes', - pool.FindMessageTypeByName( - prefix + 'protobuf_unittest.TestAllTypes').full_name) - - # AddDescriptor is not recursive. - with self.assertRaises(KeyError): - pool.FindMessageTypeByName( - prefix + 'protobuf_unittest.TestAllTypes.NestedMessage') - - pool._AddDescriptor(unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR) - self.assertEqual( - 'protobuf_unittest.TestAllTypes.NestedMessage', - pool.FindMessageTypeByName( - prefix + 'protobuf_unittest.TestAllTypes.NestedMessage').full_name) - - # Files are implicitly also indexed when messages are added. - self.assertEqual( - 'google/protobuf/unittest.proto', - pool.FindFileByName( - 'google/protobuf/unittest.proto').name) - - self.assertEqual( - 'google/protobuf/unittest.proto', - pool.FindFileContainingSymbol( - prefix + 'protobuf_unittest.TestAllTypes.NestedMessage').name) - - @unittest.skipIf(api_implementation.Type() != 'python', - 'Only pure python allows _Add*()') - def testMessage(self): - self._TestMessage('') - self._TestMessage('.') - - def _TestEnum(self, prefix): - pool = descriptor_pool.DescriptorPool() - if api_implementation.Type() == 'cpp': - pool.AddEnumDescriptor(unittest_pb2.ForeignEnum.DESCRIPTOR) - else: - pool._AddEnumDescriptor(unittest_pb2.ForeignEnum.DESCRIPTOR) - self.assertEqual( - 'protobuf_unittest.ForeignEnum', - pool.FindEnumTypeByName( - prefix + 'protobuf_unittest.ForeignEnum').full_name) - - # AddEnumDescriptor is not recursive. - with self.assertRaises(KeyError): - pool.FindEnumTypeByName( - prefix + 'protobuf_unittest.ForeignEnum.NestedEnum') - - if api_implementation.Type() == 'cpp': - pool.AddEnumDescriptor(unittest_pb2.TestAllTypes.NestedEnum.DESCRIPTOR) - else: - pool._AddEnumDescriptor(unittest_pb2.TestAllTypes.NestedEnum.DESCRIPTOR) - self.assertEqual( - 'protobuf_unittest.TestAllTypes.NestedEnum', - pool.FindEnumTypeByName( - prefix + 'protobuf_unittest.TestAllTypes.NestedEnum').full_name) - - # Files are implicitly also indexed when enums are added. - self.assertEqual( - 'google/protobuf/unittest.proto', - pool.FindFileByName( - 'google/protobuf/unittest.proto').name) - - self.assertEqual( - 'google/protobuf/unittest.proto', - pool.FindFileContainingSymbol( - prefix + 'protobuf_unittest.TestAllTypes.NestedEnum').name) - - @unittest.skipIf(api_implementation.Type() != 'python', - 'Only pure python allows _Add*()') - def testEnum(self): - self._TestEnum('') - self._TestEnum('.') - - @unittest.skipIf(api_implementation.Type() != 'python', - 'Only pure python allows _Add*()') - def testService(self): - pool = descriptor_pool.DescriptorPool() - with self.assertRaises(KeyError): - pool.FindServiceByName('protobuf_unittest.TestService') - pool._AddServiceDescriptor(unittest_pb2._TESTSERVICE) - self.assertEqual( - 'protobuf_unittest.TestService', - pool.FindServiceByName('protobuf_unittest.TestService').full_name) - - @unittest.skipIf(api_implementation.Type() != 'python', - 'Only pure python allows _Add*()') - def testFile(self): - pool = descriptor_pool.DescriptorPool() - pool._AddFileDescriptor(unittest_pb2.DESCRIPTOR) - self.assertEqual( - 'google/protobuf/unittest.proto', - pool.FindFileByName( - 'google/protobuf/unittest.proto').name) - - # AddFileDescriptor is not recursive; messages and enums within files must - # be explicitly registered. - with self.assertRaises(KeyError): - pool.FindFileContainingSymbol( - 'protobuf_unittest.TestAllTypes') - - def testEmptyDescriptorPool(self): - # Check that an empty DescriptorPool() contains no messages. - pool = descriptor_pool.DescriptorPool() - proto_file_name = descriptor_pb2.DESCRIPTOR.name - self.assertRaises(KeyError, pool.FindFileByName, proto_file_name) - # Add the above file to the pool - file_descriptor = descriptor_pb2.FileDescriptorProto() - descriptor_pb2.DESCRIPTOR.CopyToProto(file_descriptor) - pool.Add(file_descriptor) - # Now it exists. - self.assertTrue(pool.FindFileByName(proto_file_name)) - - def testCustomDescriptorPool(self): - # Create a new pool, and add a file descriptor. - pool = descriptor_pool.DescriptorPool() - file_desc = descriptor_pb2.FileDescriptorProto( - name='some/file.proto', package='package') - file_desc.message_type.add(name='Message') - pool.Add(file_desc) - self.assertEqual(pool.FindFileByName('some/file.proto').name, - 'some/file.proto') - self.assertEqual(pool.FindMessageTypeByName('package.Message').name, - 'Message') - # Test no package - file_proto = descriptor_pb2.FileDescriptorProto( - name='some/filename/container.proto') - message_proto = file_proto.message_type.add( - name='TopMessage') - message_proto.field.add( - name='bb', - number=1, - type=descriptor_pb2.FieldDescriptorProto.TYPE_INT32, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL) - enum_proto = file_proto.enum_type.add(name='TopEnum') - enum_proto.value.add(name='FOREIGN_FOO', number=4) - file_proto.service.add(name='TopService') - pool = descriptor_pool.DescriptorPool() - pool.Add(file_proto) - self.assertEqual('TopMessage', - pool.FindMessageTypeByName('TopMessage').name) - self.assertEqual('TopEnum', pool.FindEnumTypeByName('TopEnum').name) - self.assertEqual('TopService', pool.FindServiceByName('TopService').name) - - def testFileDescriptorOptionsWithCustomDescriptorPool(self): - # Create a descriptor pool, and add a new FileDescriptorProto to it. - pool = descriptor_pool.DescriptorPool() - file_name = 'file_descriptor_options_with_custom_descriptor_pool.proto' - file_descriptor_proto = descriptor_pb2.FileDescriptorProto(name=file_name) - extension_id = file_options_test_pb2.foo_options - file_descriptor_proto.options.Extensions[extension_id].foo_name = 'foo' - pool.Add(file_descriptor_proto) - # The options set on the FileDescriptorProto should be available in the - # descriptor even if they contain extensions that cannot be deserialized - # using the pool. - file_descriptor = pool.FindFileByName(file_name) - options = file_descriptor.GetOptions() - self.assertEqual('foo', options.Extensions[extension_id].foo_name) - # The object returned by GetOptions() is cached. - self.assertIs(options, file_descriptor.GetOptions()) - - def testAddTypeError(self): - pool = descriptor_pool.DescriptorPool() - if api_implementation.Type() != 'python': - with self.assertRaises(TypeError): - pool.AddDescriptor(0) - with self.assertRaises(TypeError): - pool.AddEnumDescriptor(0) - with self.assertRaises(TypeError): - pool.AddServiceDescriptor(0) - with self.assertRaises(TypeError): - pool.AddExtensionDescriptor(0) - with self.assertRaises(TypeError): - pool.AddFileDescriptor(0) - else: - with self.assertRaises(TypeError): - pool._AddDescriptor(0) - with self.assertRaises(TypeError): - pool._AddEnumDescriptor(0) - with self.assertRaises(TypeError): - pool._AddServiceDescriptor(0) - with self.assertRaises(TypeError): - pool._AddExtensionDescriptor(0) - with self.assertRaises(TypeError): - pool._AddFileDescriptor(0) - - -TEST1_FILE = ProtoFile( - 'google/protobuf/internal/descriptor_pool_test1.proto', - 'google.protobuf.python.internal', - { - 'DescriptorPoolTest1': MessageType({ - 'NestedEnum': EnumType([('ALPHA', 1), ('BETA', 2)]), - 'NestedMessage': MessageType({ - 'NestedEnum': EnumType([('EPSILON', 5), ('ZETA', 6)]), - 'DeepNestedMessage': MessageType({ - 'NestedEnum': EnumType([('ETA', 7), ('THETA', 8)]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'ETA')), - ('nested_field', StringField(2, 'theta')), - ]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'ZETA')), - ('nested_field', StringField(2, 'beta')), - ('deep_nested_message', MessageField(3, 'DeepNestedMessage')), - ]) - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'BETA')), - ('nested_message', MessageField(2, 'NestedMessage')), - ], is_extendable=True), - - 'DescriptorPoolTest2': MessageType({ - 'NestedEnum': EnumType([('GAMMA', 3), ('DELTA', 4)]), - 'NestedMessage': MessageType({ - 'NestedEnum': EnumType([('IOTA', 9), ('KAPPA', 10)]), - 'DeepNestedMessage': MessageType({ - 'NestedEnum': EnumType([('LAMBDA', 11), ('MU', 12)]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'MU')), - ('nested_field', StringField(2, 'lambda')), - ]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'IOTA')), - ('nested_field', StringField(2, 'delta')), - ('deep_nested_message', MessageField(3, 'DeepNestedMessage')), - ]) - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'GAMMA')), - ('nested_message', MessageField(2, 'NestedMessage')), - ]), - }) - - -TEST2_FILE = ProtoFile( - 'google/protobuf/internal/descriptor_pool_test2.proto', - 'google.protobuf.python.internal', - { - 'DescriptorPoolTest3': MessageType({ - 'NestedEnum': EnumType([('NU', 13), ('XI', 14)]), - 'NestedMessage': MessageType({ - 'NestedEnum': EnumType([('OMICRON', 15), ('PI', 16)]), - 'DeepNestedMessage': MessageType({ - 'NestedEnum': EnumType([('RHO', 17), ('SIGMA', 18)]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'RHO')), - ('nested_field', StringField(2, 'sigma')), - ]), - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'PI')), - ('nested_field', StringField(2, 'nu')), - ('deep_nested_message', MessageField(3, 'DeepNestedMessage')), - ]) - }, [ - ('nested_enum', EnumField(1, 'NestedEnum', 'XI')), - ('nested_message', MessageField(2, 'NestedMessage')), - ], extensions=[ - ('descriptor_pool_test', - ExtensionField(1001, 'DescriptorPoolTest1')), - ]), - }, - dependencies=['google/protobuf/internal/descriptor_pool_test1.proto', - 'google/protobuf/internal/more_messages.proto'], - public_dependencies=['google/protobuf/internal/more_messages.proto']) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/descriptor_test.py b/ext/protobuf/Python/google/protobuf/internal/descriptor_test.py deleted file mode 100644 index 6a8532c40..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/descriptor_test.py +++ /dev/null @@ -1,1076 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unittest for google.protobuf.internal.descriptor.""" - -__author__ = 'robinson@google.com (Will Robinson)' - -import unittest -import warnings - -from google.protobuf import unittest_custom_options_pb2 -from google.protobuf import unittest_import_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import descriptor_pb2 -from google.protobuf.internal import api_implementation -from google.protobuf.internal import test_util -from google.protobuf import descriptor -from google.protobuf import descriptor_pool -from google.protobuf import symbol_database -from google.protobuf import text_format - - -TEST_EMPTY_MESSAGE_DESCRIPTOR_ASCII = """ -name: 'TestEmptyMessage' -""" - -TEST_FILE_DESCRIPTOR_DEBUG = """syntax = "proto2"; - -package protobuf_unittest; - -message NestedMessage { - enum ForeignEnum { - FOREIGN_FOO = 4; - FOREIGN_BAR = 5; - FOREIGN_BAZ = 6; - } - optional int32 bb = 1; -} - -message ResponseMessage { -} - -service Service { - rpc CallMethod(.protobuf_unittest.NestedMessage) returns (.protobuf_unittest.ResponseMessage); -} - -""" - - -warnings.simplefilter('error', DeprecationWarning) - - -class DescriptorTest(unittest.TestCase): - - def setUp(self): - file_proto = descriptor_pb2.FileDescriptorProto( - name='some/filename/some.proto', - package='protobuf_unittest') - message_proto = file_proto.message_type.add( - name='NestedMessage') - message_proto.field.add( - name='bb', - number=1, - type=descriptor_pb2.FieldDescriptorProto.TYPE_INT32, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL) - enum_proto = message_proto.enum_type.add( - name='ForeignEnum') - enum_proto.value.add(name='FOREIGN_FOO', number=4) - enum_proto.value.add(name='FOREIGN_BAR', number=5) - enum_proto.value.add(name='FOREIGN_BAZ', number=6) - - file_proto.message_type.add(name='ResponseMessage') - service_proto = file_proto.service.add( - name='Service') - method_proto = service_proto.method.add( - name='CallMethod', - input_type='.protobuf_unittest.NestedMessage', - output_type='.protobuf_unittest.ResponseMessage') - - # Note: Calling DescriptorPool.Add() multiple times with the same file only - # works if the input is canonical; in particular, all type names must be - # fully qualified. - self.pool = self.GetDescriptorPool() - self.pool.Add(file_proto) - self.my_file = self.pool.FindFileByName(file_proto.name) - self.my_message = self.my_file.message_types_by_name[message_proto.name] - self.my_enum = self.my_message.enum_types_by_name[enum_proto.name] - self.my_service = self.my_file.services_by_name[service_proto.name] - self.my_method = self.my_service.methods_by_name[method_proto.name] - - def GetDescriptorPool(self): - return symbol_database.Default().pool - - def testEnumValueName(self): - self.assertEqual(self.my_message.EnumValueName('ForeignEnum', 4), - 'FOREIGN_FOO') - - self.assertEqual( - self.my_message.enum_types_by_name[ - 'ForeignEnum'].values_by_number[4].name, - self.my_message.EnumValueName('ForeignEnum', 4)) - with self.assertRaises(KeyError): - self.my_message.EnumValueName('ForeignEnum', 999) - with self.assertRaises(KeyError): - self.my_message.EnumValueName('NoneEnum', 999) - with self.assertRaises(TypeError): - self.my_message.EnumValueName() - - def testEnumFixups(self): - self.assertEqual(self.my_enum, self.my_enum.values[0].type) - - def testContainingTypeFixups(self): - self.assertEqual(self.my_message, self.my_message.fields[0].containing_type) - self.assertEqual(self.my_message, self.my_enum.containing_type) - - def testContainingServiceFixups(self): - self.assertEqual(self.my_service, self.my_method.containing_service) - - @unittest.skipIf( - api_implementation.Type() == 'python', - 'GetDebugString is only available with the cpp implementation', - ) - def testGetDebugString(self): - self.assertEqual(self.my_file.GetDebugString(), TEST_FILE_DESCRIPTOR_DEBUG) - - def testGetOptions(self): - self.assertEqual(self.my_enum.GetOptions(), - descriptor_pb2.EnumOptions()) - self.assertEqual(self.my_enum.values[0].GetOptions(), - descriptor_pb2.EnumValueOptions()) - self.assertEqual(self.my_message.GetOptions(), - descriptor_pb2.MessageOptions()) - self.assertEqual(self.my_message.fields[0].GetOptions(), - descriptor_pb2.FieldOptions()) - self.assertEqual(self.my_method.GetOptions(), - descriptor_pb2.MethodOptions()) - self.assertEqual(self.my_service.GetOptions(), - descriptor_pb2.ServiceOptions()) - - def testSimpleCustomOptions(self): - file_descriptor = unittest_custom_options_pb2.DESCRIPTOR - message_descriptor = (unittest_custom_options_pb2. - TestMessageWithCustomOptions.DESCRIPTOR) - field_descriptor = message_descriptor.fields_by_name['field1'] - oneof_descriptor = message_descriptor.oneofs_by_name['AnOneof'] - enum_descriptor = message_descriptor.enum_types_by_name['AnEnum'] - enum_value_descriptor = (message_descriptor. - enum_values_by_name['ANENUM_VAL2']) - other_enum_value_descriptor = (message_descriptor. - enum_values_by_name['ANENUM_VAL1']) - service_descriptor = (unittest_custom_options_pb2. - TestServiceWithCustomOptions.DESCRIPTOR) - method_descriptor = service_descriptor.FindMethodByName('Foo') - - file_options = file_descriptor.GetOptions() - file_opt1 = unittest_custom_options_pb2.file_opt1 - self.assertEqual(9876543210, file_options.Extensions[file_opt1]) - message_options = message_descriptor.GetOptions() - message_opt1 = unittest_custom_options_pb2.message_opt1 - self.assertEqual(-56, message_options.Extensions[message_opt1]) - field_options = field_descriptor.GetOptions() - field_opt1 = unittest_custom_options_pb2.field_opt1 - self.assertEqual(8765432109, field_options.Extensions[field_opt1]) - field_opt2 = unittest_custom_options_pb2.field_opt2 - self.assertEqual(42, field_options.Extensions[field_opt2]) - oneof_options = oneof_descriptor.GetOptions() - oneof_opt1 = unittest_custom_options_pb2.oneof_opt1 - self.assertEqual(-99, oneof_options.Extensions[oneof_opt1]) - enum_options = enum_descriptor.GetOptions() - enum_opt1 = unittest_custom_options_pb2.enum_opt1 - self.assertEqual(-789, enum_options.Extensions[enum_opt1]) - enum_value_options = enum_value_descriptor.GetOptions() - enum_value_opt1 = unittest_custom_options_pb2.enum_value_opt1 - self.assertEqual(123, enum_value_options.Extensions[enum_value_opt1]) - - service_options = service_descriptor.GetOptions() - service_opt1 = unittest_custom_options_pb2.service_opt1 - self.assertEqual(-9876543210, service_options.Extensions[service_opt1]) - method_options = method_descriptor.GetOptions() - method_opt1 = unittest_custom_options_pb2.method_opt1 - self.assertEqual(unittest_custom_options_pb2.METHODOPT1_VAL2, - method_options.Extensions[method_opt1]) - - message_descriptor = ( - unittest_custom_options_pb2.DummyMessageContainingEnum.DESCRIPTOR) - self.assertTrue(file_descriptor.has_options) - self.assertFalse(message_descriptor.has_options) - self.assertTrue(field_descriptor.has_options) - self.assertTrue(oneof_descriptor.has_options) - self.assertTrue(enum_descriptor.has_options) - self.assertTrue(enum_value_descriptor.has_options) - self.assertFalse(other_enum_value_descriptor.has_options) - - def testCustomOptionsCopyTo(self): - message_descriptor = (unittest_custom_options_pb2. - TestMessageWithCustomOptions.DESCRIPTOR) - message_proto = descriptor_pb2.DescriptorProto() - message_descriptor.CopyToProto(message_proto) - self.assertEqual(len(message_proto.options.ListFields()), - 2) - - def testDifferentCustomOptionTypes(self): - kint32min = -2**31 - kint64min = -2**63 - kint32max = 2**31 - 1 - kint64max = 2**63 - 1 - kuint32max = 2**32 - 1 - kuint64max = 2**64 - 1 - - message_descriptor =\ - unittest_custom_options_pb2.CustomOptionMinIntegerValues.DESCRIPTOR - message_options = message_descriptor.GetOptions() - self.assertEqual(False, message_options.Extensions[ - unittest_custom_options_pb2.bool_opt]) - self.assertEqual(kint32min, message_options.Extensions[ - unittest_custom_options_pb2.int32_opt]) - self.assertEqual(kint64min, message_options.Extensions[ - unittest_custom_options_pb2.int64_opt]) - self.assertEqual(0, message_options.Extensions[ - unittest_custom_options_pb2.uint32_opt]) - self.assertEqual(0, message_options.Extensions[ - unittest_custom_options_pb2.uint64_opt]) - self.assertEqual(kint32min, message_options.Extensions[ - unittest_custom_options_pb2.sint32_opt]) - self.assertEqual(kint64min, message_options.Extensions[ - unittest_custom_options_pb2.sint64_opt]) - self.assertEqual(0, message_options.Extensions[ - unittest_custom_options_pb2.fixed32_opt]) - self.assertEqual(0, message_options.Extensions[ - unittest_custom_options_pb2.fixed64_opt]) - self.assertEqual(kint32min, message_options.Extensions[ - unittest_custom_options_pb2.sfixed32_opt]) - self.assertEqual(kint64min, message_options.Extensions[ - unittest_custom_options_pb2.sfixed64_opt]) - - message_descriptor =\ - unittest_custom_options_pb2.CustomOptionMaxIntegerValues.DESCRIPTOR - message_options = message_descriptor.GetOptions() - self.assertEqual(True, message_options.Extensions[ - unittest_custom_options_pb2.bool_opt]) - self.assertEqual(kint32max, message_options.Extensions[ - unittest_custom_options_pb2.int32_opt]) - self.assertEqual(kint64max, message_options.Extensions[ - unittest_custom_options_pb2.int64_opt]) - self.assertEqual(kuint32max, message_options.Extensions[ - unittest_custom_options_pb2.uint32_opt]) - self.assertEqual(kuint64max, message_options.Extensions[ - unittest_custom_options_pb2.uint64_opt]) - self.assertEqual(kint32max, message_options.Extensions[ - unittest_custom_options_pb2.sint32_opt]) - self.assertEqual(kint64max, message_options.Extensions[ - unittest_custom_options_pb2.sint64_opt]) - self.assertEqual(kuint32max, message_options.Extensions[ - unittest_custom_options_pb2.fixed32_opt]) - self.assertEqual(kuint64max, message_options.Extensions[ - unittest_custom_options_pb2.fixed64_opt]) - self.assertEqual(kint32max, message_options.Extensions[ - unittest_custom_options_pb2.sfixed32_opt]) - self.assertEqual(kint64max, message_options.Extensions[ - unittest_custom_options_pb2.sfixed64_opt]) - - message_descriptor =\ - unittest_custom_options_pb2.CustomOptionOtherValues.DESCRIPTOR - message_options = message_descriptor.GetOptions() - self.assertEqual(-100, message_options.Extensions[ - unittest_custom_options_pb2.int32_opt]) - self.assertAlmostEqual(12.3456789, message_options.Extensions[ - unittest_custom_options_pb2.float_opt], 6) - self.assertAlmostEqual(1.234567890123456789, message_options.Extensions[ - unittest_custom_options_pb2.double_opt]) - self.assertEqual("Hello, \"World\"", message_options.Extensions[ - unittest_custom_options_pb2.string_opt]) - self.assertEqual(b"Hello\0World", message_options.Extensions[ - unittest_custom_options_pb2.bytes_opt]) - dummy_enum = unittest_custom_options_pb2.DummyMessageContainingEnum - self.assertEqual( - dummy_enum.TEST_OPTION_ENUM_TYPE2, - message_options.Extensions[unittest_custom_options_pb2.enum_opt]) - - message_descriptor =\ - unittest_custom_options_pb2.SettingRealsFromPositiveInts.DESCRIPTOR - message_options = message_descriptor.GetOptions() - self.assertAlmostEqual(12, message_options.Extensions[ - unittest_custom_options_pb2.float_opt], 6) - self.assertAlmostEqual(154, message_options.Extensions[ - unittest_custom_options_pb2.double_opt]) - - message_descriptor =\ - unittest_custom_options_pb2.SettingRealsFromNegativeInts.DESCRIPTOR - message_options = message_descriptor.GetOptions() - self.assertAlmostEqual(-12, message_options.Extensions[ - unittest_custom_options_pb2.float_opt], 6) - self.assertAlmostEqual(-154, message_options.Extensions[ - unittest_custom_options_pb2.double_opt]) - - def testComplexExtensionOptions(self): - descriptor =\ - unittest_custom_options_pb2.VariousComplexOptions.DESCRIPTOR - options = descriptor.GetOptions() - self.assertEqual(42, options.Extensions[ - unittest_custom_options_pb2.complex_opt1].foo) - self.assertEqual(324, options.Extensions[ - unittest_custom_options_pb2.complex_opt1].Extensions[ - unittest_custom_options_pb2.mooo]) - self.assertEqual(876, options.Extensions[ - unittest_custom_options_pb2.complex_opt1].Extensions[ - unittest_custom_options_pb2.corge].moo) - self.assertEqual(987, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].baz) - self.assertEqual(654, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].Extensions[ - unittest_custom_options_pb2.grault]) - self.assertEqual(743, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].bar.foo) - self.assertEqual(1999, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].bar.Extensions[ - unittest_custom_options_pb2.mooo]) - self.assertEqual(2008, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].bar.Extensions[ - unittest_custom_options_pb2.corge].moo) - self.assertEqual(741, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].Extensions[ - unittest_custom_options_pb2.garply].foo) - self.assertEqual(1998, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].Extensions[ - unittest_custom_options_pb2.garply].Extensions[ - unittest_custom_options_pb2.mooo]) - self.assertEqual(2121, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].Extensions[ - unittest_custom_options_pb2.garply].Extensions[ - unittest_custom_options_pb2.corge].moo) - self.assertEqual(1971, options.Extensions[ - unittest_custom_options_pb2.ComplexOptionType2 - .ComplexOptionType4.complex_opt4].waldo) - self.assertEqual(321, options.Extensions[ - unittest_custom_options_pb2.complex_opt2].fred.waldo) - self.assertEqual(9, options.Extensions[ - unittest_custom_options_pb2.complex_opt3].moo) - self.assertEqual(22, options.Extensions[ - unittest_custom_options_pb2.complex_opt3].complexoptiontype5.plugh) - self.assertEqual(24, options.Extensions[ - unittest_custom_options_pb2.complexopt6].xyzzy) - - # Check that aggregate options were parsed and saved correctly in - # the appropriate descriptors. - def testAggregateOptions(self): - file_descriptor = unittest_custom_options_pb2.DESCRIPTOR - message_descriptor =\ - unittest_custom_options_pb2.AggregateMessage.DESCRIPTOR - field_descriptor = message_descriptor.fields_by_name["fieldname"] - enum_descriptor = unittest_custom_options_pb2.AggregateEnum.DESCRIPTOR - enum_value_descriptor = enum_descriptor.values_by_name["VALUE"] - service_descriptor =\ - unittest_custom_options_pb2.AggregateService.DESCRIPTOR - method_descriptor = service_descriptor.FindMethodByName("Method") - - # Tests for the different types of data embedded in fileopt - file_options = file_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.fileopt] - self.assertEqual(100, file_options.i) - self.assertEqual("FileAnnotation", file_options.s) - self.assertEqual("NestedFileAnnotation", file_options.sub.s) - self.assertEqual("FileExtensionAnnotation", file_options.file.Extensions[ - unittest_custom_options_pb2.fileopt].s) - self.assertEqual("EmbeddedMessageSetElement", file_options.mset.Extensions[ - unittest_custom_options_pb2.AggregateMessageSetElement - .message_set_extension].s) - - # Simple tests for all the other types of annotations - self.assertEqual( - "MessageAnnotation", - message_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.msgopt].s) - self.assertEqual( - "FieldAnnotation", - field_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.fieldopt].s) - self.assertEqual( - "EnumAnnotation", - enum_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.enumopt].s) - self.assertEqual( - "EnumValueAnnotation", - enum_value_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.enumvalopt].s) - self.assertEqual( - "ServiceAnnotation", - service_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.serviceopt].s) - self.assertEqual( - "MethodAnnotation", - method_descriptor.GetOptions().Extensions[ - unittest_custom_options_pb2.methodopt].s) - - def testNestedOptions(self): - nested_message =\ - unittest_custom_options_pb2.NestedOptionType.NestedMessage.DESCRIPTOR - self.assertEqual(1001, nested_message.GetOptions().Extensions[ - unittest_custom_options_pb2.message_opt1]) - nested_field = nested_message.fields_by_name["nested_field"] - self.assertEqual(1002, nested_field.GetOptions().Extensions[ - unittest_custom_options_pb2.field_opt1]) - outer_message =\ - unittest_custom_options_pb2.NestedOptionType.DESCRIPTOR - nested_enum = outer_message.enum_types_by_name["NestedEnum"] - self.assertEqual(1003, nested_enum.GetOptions().Extensions[ - unittest_custom_options_pb2.enum_opt1]) - nested_enum_value = outer_message.enum_values_by_name["NESTED_ENUM_VALUE"] - self.assertEqual(1004, nested_enum_value.GetOptions().Extensions[ - unittest_custom_options_pb2.enum_value_opt1]) - nested_extension = outer_message.extensions_by_name["nested_extension"] - self.assertEqual(1005, nested_extension.GetOptions().Extensions[ - unittest_custom_options_pb2.field_opt2]) - - def testFileDescriptorReferences(self): - self.assertEqual(self.my_enum.file, self.my_file) - self.assertEqual(self.my_message.file, self.my_file) - - def testFileDescriptor(self): - self.assertEqual(self.my_file.name, 'some/filename/some.proto') - self.assertEqual(self.my_file.package, 'protobuf_unittest') - self.assertEqual(self.my_file.pool, self.pool) - self.assertFalse(self.my_file.has_options) - self.assertEqual('proto2', self.my_file.syntax) - file_proto = descriptor_pb2.FileDescriptorProto() - self.my_file.CopyToProto(file_proto) - self.assertEqual(self.my_file.serialized_pb, - file_proto.SerializeToString()) - # Generated modules also belong to the default pool. - self.assertEqual(unittest_pb2.DESCRIPTOR.pool, descriptor_pool.Default()) - - @unittest.skipIf( - api_implementation.Type() == 'python', - 'Immutability of descriptors is only enforced in v2 implementation') - def testImmutableCppDescriptor(self): - file_descriptor = unittest_pb2.DESCRIPTOR - message_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - field_descriptor = message_descriptor.fields_by_name['optional_int32'] - enum_descriptor = message_descriptor.enum_types_by_name['NestedEnum'] - oneof_descriptor = message_descriptor.oneofs_by_name['oneof_field'] - with self.assertRaises(AttributeError): - message_descriptor.fields_by_name = None - with self.assertRaises(TypeError): - message_descriptor.fields_by_name['Another'] = None - with self.assertRaises(TypeError): - message_descriptor.fields.append(None) - with self.assertRaises(AttributeError): - field_descriptor.containing_type = message_descriptor - with self.assertRaises(AttributeError): - file_descriptor.has_options = False - with self.assertRaises(AttributeError): - field_descriptor.has_options = False - with self.assertRaises(AttributeError): - oneof_descriptor.has_options = False - with self.assertRaises(AttributeError): - enum_descriptor.has_options = False - with self.assertRaises(AttributeError) as e: - message_descriptor.has_options = True - self.assertEqual('attribute is not writable: has_options', - str(e.exception)) - - def testDefault(self): - message_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - field = message_descriptor.fields_by_name['repeated_int32'] - self.assertEqual(field.default_value, []) - field = message_descriptor.fields_by_name['repeated_nested_message'] - self.assertEqual(field.default_value, []) - field = message_descriptor.fields_by_name['optionalgroup'] - self.assertEqual(field.default_value, None) - field = message_descriptor.fields_by_name['optional_nested_message'] - self.assertEqual(field.default_value, None) - - -class NewDescriptorTest(DescriptorTest): - """Redo the same tests as above, but with a separate DescriptorPool.""" - - def GetDescriptorPool(self): - return descriptor_pool.DescriptorPool() - - -class GeneratedDescriptorTest(unittest.TestCase): - """Tests for the properties of descriptors in generated code.""" - - def CheckMessageDescriptor(self, message_descriptor): - # Basic properties - self.assertEqual(message_descriptor.name, 'TestAllTypes') - self.assertEqual(message_descriptor.full_name, - 'protobuf_unittest.TestAllTypes') - # Test equality and hashability - self.assertEqual(message_descriptor, message_descriptor) - self.assertEqual(message_descriptor.fields[0].containing_type, - message_descriptor) - self.assertIn(message_descriptor, [message_descriptor]) - self.assertIn(message_descriptor, {message_descriptor: None}) - # Test field containers - self.CheckDescriptorSequence(message_descriptor.fields) - self.CheckDescriptorMapping(message_descriptor.fields_by_name) - self.CheckDescriptorMapping(message_descriptor.fields_by_number) - self.CheckDescriptorMapping(message_descriptor.fields_by_camelcase_name) - self.CheckDescriptorMapping(message_descriptor.enum_types_by_name) - self.CheckDescriptorMapping(message_descriptor.enum_values_by_name) - self.CheckDescriptorMapping(message_descriptor.oneofs_by_name) - self.CheckDescriptorMapping(message_descriptor.enum_types[0].values_by_name) - # Test extension range - self.assertEqual(message_descriptor.extension_ranges, []) - - def CheckFieldDescriptor(self, field_descriptor): - # Basic properties - self.assertEqual(field_descriptor.name, 'optional_int32') - self.assertEqual(field_descriptor.camelcase_name, 'optionalInt32') - self.assertEqual(field_descriptor.full_name, - 'protobuf_unittest.TestAllTypes.optional_int32') - self.assertEqual(field_descriptor.containing_type.name, 'TestAllTypes') - self.assertEqual(field_descriptor.file, unittest_pb2.DESCRIPTOR) - # Test equality and hashability - self.assertEqual(field_descriptor, field_descriptor) - self.assertEqual( - field_descriptor.containing_type.fields_by_name['optional_int32'], - field_descriptor) - self.assertEqual( - field_descriptor.containing_type.fields_by_camelcase_name[ - 'optionalInt32'], - field_descriptor) - self.assertIn(field_descriptor, [field_descriptor]) - self.assertIn(field_descriptor, {field_descriptor: None}) - self.assertEqual(None, field_descriptor.extension_scope) - self.assertEqual(None, field_descriptor.enum_type) - self.assertTrue(field_descriptor.has_presence) - if api_implementation.Type() == 'cpp': - # For test coverage only - self.assertEqual(field_descriptor.id, field_descriptor.id) - - def CheckDescriptorSequence(self, sequence): - # Verifies that a property like 'messageDescriptor.fields' has all the - # properties of an immutable abc.Sequence. - self.assertNotEqual(sequence, - unittest_pb2.TestAllExtensions.DESCRIPTOR.fields) - self.assertNotEqual(sequence, []) - self.assertNotEqual(sequence, 1) - self.assertFalse(sequence == 1) # Only for cpp test coverage - self.assertEqual(sequence, sequence) - expected_list = list(sequence) - self.assertEqual(expected_list, sequence) - self.assertGreater(len(sequence), 0) # Sized - self.assertEqual(len(sequence), len(expected_list)) # Iterable - self.assertEqual(sequence[len(sequence) -1], sequence[-1]) - item = sequence[0] - self.assertEqual(item, sequence[0]) - self.assertIn(item, sequence) # Container - self.assertEqual(sequence.index(item), 0) - self.assertEqual(sequence.count(item), 1) - other_item = unittest_pb2.NestedTestAllTypes.DESCRIPTOR.fields[0] - self.assertNotIn(other_item, sequence) - self.assertEqual(sequence.count(other_item), 0) - self.assertRaises(ValueError, sequence.index, other_item) - self.assertRaises(ValueError, sequence.index, []) - reversed_iterator = reversed(sequence) - self.assertEqual(list(reversed_iterator), list(sequence)[::-1]) - self.assertRaises(StopIteration, next, reversed_iterator) - expected_list[0] = 'change value' - self.assertNotEqual(expected_list, sequence) - # TODO(jieluo): Change __repr__ support for DescriptorSequence. - if api_implementation.Type() == 'python': - self.assertEqual(str(list(sequence)), str(sequence)) - else: - self.assertEqual(str(sequence)[0], '<') - - def CheckDescriptorMapping(self, mapping): - # Verifies that a property like 'messageDescriptor.fields' has all the - # properties of an immutable abc.Mapping. - self.assertNotEqual( - mapping, unittest_pb2.TestAllExtensions.DESCRIPTOR.fields_by_name) - self.assertNotEqual(mapping, {}) - self.assertNotEqual(mapping, 1) - self.assertFalse(mapping == 1) # Only for cpp test coverage - excepted_dict = dict(mapping.items()) - self.assertEqual(mapping, excepted_dict) - self.assertEqual(mapping, mapping) - self.assertGreater(len(mapping), 0) # Sized - self.assertEqual(len(mapping), len(excepted_dict)) # Iterable - key, item = next(iter(mapping.items())) - self.assertIn(key, mapping) # Container - self.assertEqual(mapping.get(key), item) - with self.assertRaises(TypeError): - mapping.get() - # TODO(jieluo): Fix python and cpp extension diff. - if api_implementation.Type() == 'python': - self.assertRaises(TypeError, mapping.get, []) - else: - self.assertEqual(None, mapping.get([])) - # keys(), iterkeys() &co - item = (next(iter(mapping.keys())), next(iter(mapping.values()))) - self.assertEqual(item, next(iter(mapping.items()))) - excepted_dict[key] = 'change value' - self.assertNotEqual(mapping, excepted_dict) - del excepted_dict[key] - excepted_dict['new_key'] = 'new' - self.assertNotEqual(mapping, excepted_dict) - self.assertRaises(KeyError, mapping.__getitem__, 'key_error') - self.assertRaises(KeyError, mapping.__getitem__, len(mapping) + 1) - # TODO(jieluo): Add __repr__ support for DescriptorMapping. - if api_implementation.Type() == 'python': - self.assertEqual(len(str(dict(mapping.items()))), len(str(mapping))) - else: - self.assertEqual(str(mapping)[0], '<') - - def testDescriptor(self): - message_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - self.CheckMessageDescriptor(message_descriptor) - field_descriptor = message_descriptor.fields_by_name['optional_int32'] - self.CheckFieldDescriptor(field_descriptor) - field_descriptor = message_descriptor.fields_by_camelcase_name[ - 'optionalInt32'] - self.CheckFieldDescriptor(field_descriptor) - enum_descriptor = unittest_pb2.DESCRIPTOR.enum_types_by_name[ - 'ForeignEnum'] - self.assertEqual(None, enum_descriptor.containing_type) - # Test extension range - self.assertEqual( - unittest_pb2.TestAllExtensions.DESCRIPTOR.extension_ranges, - [(1, 536870912)]) - self.assertEqual( - unittest_pb2.TestMultipleExtensionRanges.DESCRIPTOR.extension_ranges, - [(42, 43), (4143, 4244), (65536, 536870912)]) - - def testCppDescriptorContainer(self): - containing_file = unittest_pb2.DESCRIPTOR - self.CheckDescriptorSequence(containing_file.dependencies) - self.CheckDescriptorMapping(containing_file.message_types_by_name) - self.CheckDescriptorMapping(containing_file.enum_types_by_name) - self.CheckDescriptorMapping(containing_file.services_by_name) - self.CheckDescriptorMapping(containing_file.extensions_by_name) - self.CheckDescriptorMapping( - unittest_pb2.TestNestedExtension.DESCRIPTOR.extensions_by_name) - - def testCppDescriptorContainer_Iterator(self): - # Same test with the iterator - enum = unittest_pb2.TestAllTypes.DESCRIPTOR.enum_types_by_name['NestedEnum'] - values_iter = iter(enum.values) - del enum - self.assertEqual('FOO', next(values_iter).name) - - def testDescriptorNestedTypesContainer(self): - message_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - nested_message_descriptor = unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR - self.assertEqual(len(message_descriptor.nested_types), 3) - self.assertFalse(None in message_descriptor.nested_types) - self.assertTrue( - nested_message_descriptor in message_descriptor.nested_types) - - def testServiceDescriptor(self): - service_descriptor = unittest_pb2.DESCRIPTOR.services_by_name['TestService'] - self.assertEqual(service_descriptor.name, 'TestService') - self.assertEqual(service_descriptor.methods[0].name, 'Foo') - self.assertIs(service_descriptor.file, unittest_pb2.DESCRIPTOR) - self.assertEqual(service_descriptor.index, 0) - self.CheckDescriptorMapping(service_descriptor.methods_by_name) - - def testOneofDescriptor(self): - message_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - oneof_descriptor = message_descriptor.oneofs_by_name['oneof_field'] - self.assertFalse(oneof_descriptor.has_options) - self.assertEqual(message_descriptor, oneof_descriptor.containing_type) - self.assertEqual('oneof_field', oneof_descriptor.name) - self.assertEqual('protobuf_unittest.TestAllTypes.oneof_field', - oneof_descriptor.full_name) - self.assertEqual(0, oneof_descriptor.index) - - -class DescriptorCopyToProtoTest(unittest.TestCase): - """Tests for CopyTo functions of Descriptor.""" - - def _AssertProtoEqual(self, actual_proto, expected_class, expected_ascii): - expected_proto = expected_class() - text_format.Merge(expected_ascii, expected_proto) - - self.assertEqual( - actual_proto, expected_proto, - 'Not equal,\nActual:\n%s\nExpected:\n%s\n' - % (str(actual_proto), str(expected_proto))) - - def _InternalTestCopyToProto(self, desc, expected_proto_class, - expected_proto_ascii): - actual = expected_proto_class() - desc.CopyToProto(actual) - self._AssertProtoEqual( - actual, expected_proto_class, expected_proto_ascii) - - def testCopyToProto_EmptyMessage(self): - self._InternalTestCopyToProto( - unittest_pb2.TestEmptyMessage.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_EMPTY_MESSAGE_DESCRIPTOR_ASCII) - - def testCopyToProto_NestedMessage(self): - TEST_NESTED_MESSAGE_ASCII = """ - name: 'NestedMessage' - field: < - name: 'bb' - number: 1 - label: 1 # Optional - type: 5 # TYPE_INT32 - > - """ - - self._InternalTestCopyToProto( - unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_NESTED_MESSAGE_ASCII) - - def testCopyToProto_ForeignNestedMessage(self): - TEST_FOREIGN_NESTED_ASCII = """ - name: 'TestForeignNested' - field: < - name: 'foreign_nested' - number: 1 - label: 1 # Optional - type: 11 # TYPE_MESSAGE - type_name: '.protobuf_unittest.TestAllTypes.NestedMessage' - > - """ - - self._InternalTestCopyToProto( - unittest_pb2.TestForeignNested.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_FOREIGN_NESTED_ASCII) - - def testCopyToProto_ForeignEnum(self): - TEST_FOREIGN_ENUM_ASCII = """ - name: 'ForeignEnum' - value: < - name: 'FOREIGN_FOO' - number: 4 - > - value: < - name: 'FOREIGN_BAR' - number: 5 - > - value: < - name: 'FOREIGN_BAZ' - number: 6 - > - """ - - self._InternalTestCopyToProto( - unittest_pb2.ForeignEnum.DESCRIPTOR, - descriptor_pb2.EnumDescriptorProto, - TEST_FOREIGN_ENUM_ASCII) - - def testCopyToProto_Options(self): - TEST_DEPRECATED_FIELDS_ASCII = """ - name: 'TestDeprecatedFields' - field: < - name: 'deprecated_int32' - number: 1 - label: 1 # Optional - type: 5 # TYPE_INT32 - options: < - deprecated: true - > - > - field { - name: "deprecated_int32_in_oneof" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_INT32 - options { - deprecated: true - } - oneof_index: 0 - } - oneof_decl { - name: "oneof_fields" - } - """ - - self._InternalTestCopyToProto( - unittest_pb2.TestDeprecatedFields.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_DEPRECATED_FIELDS_ASCII) - - def testCopyToProto_AllExtensions(self): - TEST_EMPTY_MESSAGE_WITH_EXTENSIONS_ASCII = """ - name: 'TestEmptyMessageWithExtensions' - extension_range: < - start: 1 - end: 536870912 - > - """ - - self._InternalTestCopyToProto( - unittest_pb2.TestEmptyMessageWithExtensions.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_EMPTY_MESSAGE_WITH_EXTENSIONS_ASCII) - - def testCopyToProto_SeveralExtensions(self): - TEST_MESSAGE_WITH_SEVERAL_EXTENSIONS_ASCII = """ - name: 'TestMultipleExtensionRanges' - extension_range: < - start: 42 - end: 43 - > - extension_range: < - start: 4143 - end: 4244 - > - extension_range: < - start: 65536 - end: 536870912 - > - """ - - self._InternalTestCopyToProto( - unittest_pb2.TestMultipleExtensionRanges.DESCRIPTOR, - descriptor_pb2.DescriptorProto, - TEST_MESSAGE_WITH_SEVERAL_EXTENSIONS_ASCII) - - def testCopyToProto_FileDescriptor(self): - UNITTEST_IMPORT_FILE_DESCRIPTOR_ASCII = (""" - name: 'google/protobuf/unittest_import.proto' - package: 'protobuf_unittest_import' - dependency: 'google/protobuf/unittest_import_public.proto' - message_type: < - name: 'ImportMessage' - field: < - name: 'd' - number: 1 - label: 1 # Optional - type: 5 # TYPE_INT32 - > - > - """ + - """enum_type: < - name: 'ImportEnum' - value: < - name: 'IMPORT_FOO' - number: 7 - > - value: < - name: 'IMPORT_BAR' - number: 8 - > - value: < - name: 'IMPORT_BAZ' - number: 9 - > - > - enum_type: < - name: 'ImportEnumForMap' - value: < - name: 'UNKNOWN' - number: 0 - > - value: < - name: 'FOO' - number: 1 - > - value: < - name: 'BAR' - number: 2 - > - > - options: < - java_package: 'com.google.protobuf.test' - optimize_for: 1 # SPEED - """ + - """ - cc_enable_arenas: true - > - public_dependency: 0 - """) - self._InternalTestCopyToProto( - unittest_import_pb2.DESCRIPTOR, - descriptor_pb2.FileDescriptorProto, - UNITTEST_IMPORT_FILE_DESCRIPTOR_ASCII) - - def testCopyToProto_ServiceDescriptor(self): - TEST_SERVICE_ASCII = """ - name: 'TestService' - method: < - name: 'Foo' - input_type: '.protobuf_unittest.FooRequest' - output_type: '.protobuf_unittest.FooResponse' - > - method: < - name: 'Bar' - input_type: '.protobuf_unittest.BarRequest' - output_type: '.protobuf_unittest.BarResponse' - > - """ - self._InternalTestCopyToProto( - unittest_pb2.TestService.DESCRIPTOR, - descriptor_pb2.ServiceDescriptorProto, - TEST_SERVICE_ASCII) - - def testCopyToProto_MethodDescriptor(self): - expected_ascii = """ - name: 'Foo' - input_type: '.protobuf_unittest.FooRequest' - output_type: '.protobuf_unittest.FooResponse' - """ - method_descriptor = unittest_pb2.TestService.DESCRIPTOR.FindMethodByName( - 'Foo') - self._InternalTestCopyToProto( - method_descriptor, - descriptor_pb2.MethodDescriptorProto, - expected_ascii) - - @unittest.skipIf( - api_implementation.Type() == 'python', - 'Pure python does not raise error.') - # TODO(jieluo): Fix pure python to check with the proto type. - def testCopyToProto_TypeError(self): - file_proto = descriptor_pb2.FileDescriptorProto() - self.assertRaises(TypeError, - unittest_pb2.TestEmptyMessage.DESCRIPTOR.CopyToProto, - file_proto) - self.assertRaises(TypeError, - unittest_pb2.ForeignEnum.DESCRIPTOR.CopyToProto, - file_proto) - self.assertRaises(TypeError, - unittest_pb2.TestService.DESCRIPTOR.CopyToProto, - file_proto) - proto = descriptor_pb2.DescriptorProto() - self.assertRaises(TypeError, - unittest_import_pb2.DESCRIPTOR.CopyToProto, - proto) - - -class MakeDescriptorTest(unittest.TestCase): - - def testMakeDescriptorWithNestedFields(self): - file_descriptor_proto = descriptor_pb2.FileDescriptorProto() - file_descriptor_proto.name = 'Foo2' - message_type = file_descriptor_proto.message_type.add() - message_type.name = file_descriptor_proto.name - nested_type = message_type.nested_type.add() - nested_type.name = 'Sub' - enum_type = nested_type.enum_type.add() - enum_type.name = 'FOO' - enum_type_val = enum_type.value.add() - enum_type_val.name = 'BAR' - enum_type_val.number = 3 - field = message_type.field.add() - field.number = 1 - field.name = 'uint64_field' - field.label = descriptor.FieldDescriptor.LABEL_REQUIRED - field.type = descriptor.FieldDescriptor.TYPE_UINT64 - field = message_type.field.add() - field.number = 2 - field.name = 'nested_message_field' - field.label = descriptor.FieldDescriptor.LABEL_REQUIRED - field.type = descriptor.FieldDescriptor.TYPE_MESSAGE - field.type_name = 'Sub' - enum_field = nested_type.field.add() - enum_field.number = 2 - enum_field.name = 'bar_field' - enum_field.label = descriptor.FieldDescriptor.LABEL_REQUIRED - enum_field.type = descriptor.FieldDescriptor.TYPE_ENUM - enum_field.type_name = 'Foo2.Sub.FOO' - - result = descriptor.MakeDescriptor(message_type) - self.assertEqual(result.fields[0].cpp_type, - descriptor.FieldDescriptor.CPPTYPE_UINT64) - self.assertEqual(result.fields[1].cpp_type, - descriptor.FieldDescriptor.CPPTYPE_MESSAGE) - self.assertEqual(result.fields[1].message_type.containing_type, - result) - self.assertEqual(result.nested_types[0].fields[0].full_name, - 'Foo2.Sub.bar_field') - self.assertEqual(result.nested_types[0].fields[0].enum_type, - result.nested_types[0].enum_types[0]) - self.assertFalse(result.has_options) - self.assertFalse(result.fields[0].has_options) - if api_implementation.Type() == 'cpp': - with self.assertRaises(AttributeError): - result.fields[0].has_options = False - - def testMakeDescriptorWithUnsignedIntField(self): - file_descriptor_proto = descriptor_pb2.FileDescriptorProto() - file_descriptor_proto.name = 'Foo' - message_type = file_descriptor_proto.message_type.add() - message_type.name = file_descriptor_proto.name - enum_type = message_type.enum_type.add() - enum_type.name = 'FOO' - enum_type_val = enum_type.value.add() - enum_type_val.name = 'BAR' - enum_type_val.number = 3 - field = message_type.field.add() - field.number = 1 - field.name = 'uint64_field' - field.label = descriptor.FieldDescriptor.LABEL_REQUIRED - field.type = descriptor.FieldDescriptor.TYPE_UINT64 - enum_field = message_type.field.add() - enum_field.number = 2 - enum_field.name = 'bar_field' - enum_field.label = descriptor.FieldDescriptor.LABEL_REQUIRED - enum_field.type = descriptor.FieldDescriptor.TYPE_ENUM - enum_field.type_name = 'Foo.FOO' - - result = descriptor.MakeDescriptor(message_type) - self.assertEqual(result.fields[0].cpp_type, - descriptor.FieldDescriptor.CPPTYPE_UINT64) - - - def testMakeDescriptorWithOptions(self): - descriptor_proto = descriptor_pb2.DescriptorProto() - aggregate_message = unittest_custom_options_pb2.AggregateMessage - aggregate_message.DESCRIPTOR.CopyToProto(descriptor_proto) - reformed_descriptor = descriptor.MakeDescriptor(descriptor_proto) - - options = reformed_descriptor.GetOptions() - self.assertEqual(101, - options.Extensions[unittest_custom_options_pb2.msgopt].i) - - def testCamelcaseName(self): - descriptor_proto = descriptor_pb2.DescriptorProto() - descriptor_proto.name = 'Bar' - names = ['foo_foo', 'FooBar', 'fooBaz', 'fooFoo', 'foobar'] - camelcase_names = ['fooFoo', 'fooBar', 'fooBaz', 'fooFoo', 'foobar'] - for index in range(len(names)): - field = descriptor_proto.field.add() - field.number = index + 1 - field.name = names[index] - result = descriptor.MakeDescriptor(descriptor_proto) - for index in range(len(camelcase_names)): - self.assertEqual(result.fields[index].camelcase_name, - camelcase_names[index]) - - def testJsonName(self): - descriptor_proto = descriptor_pb2.DescriptorProto() - descriptor_proto.name = 'TestJsonName' - names = ['field_name', 'fieldName', 'FieldName', - '_field_name', 'FIELD_NAME', 'json_name'] - json_names = ['fieldName', 'fieldName', 'FieldName', - 'FieldName', 'FIELDNAME', '@type'] - for index in range(len(names)): - field = descriptor_proto.field.add() - field.number = index + 1 - field.name = names[index] - field.json_name = '@type' - result = descriptor.MakeDescriptor(descriptor_proto) - for index in range(len(json_names)): - self.assertEqual(result.fields[index].json_name, - json_names[index]) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/extension_dict.py b/ext/protobuf/Python/google/protobuf/internal/extension_dict.py index b346cf283..83c4cb5dc 100644 --- a/ext/protobuf/Python/google/protobuf/internal/extension_dict.py +++ b/ext/protobuf/Python/google/protobuf/internal/extension_dict.py @@ -89,8 +89,9 @@ def __getitem__(self, extension_handle): elif extension_handle.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: message_type = extension_handle.message_type if not hasattr(message_type, '_concrete_class'): - # pylint: disable=protected-access - self._extended_message._FACTORY.GetPrototype(message_type) + # pylint: disable=g-import-not-at-top + from google.protobuf import message_factory + message_factory.GetMessageClass(message_type) assert getattr(extension_handle.message_type, '_concrete_class', None), ( 'Uninitialized concrete class found for field %r (message type %r)' % (extension_handle.full_name, diff --git a/ext/protobuf/Python/google/protobuf/internal/field_mask.py b/ext/protobuf/Python/google/protobuf/internal/field_mask.py new file mode 100644 index 000000000..489769901 --- /dev/null +++ b/ext/protobuf/Python/google/protobuf/internal/field_mask.py @@ -0,0 +1,333 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2008 Google Inc. All rights reserved. +# https://developers.google.com/protocol-buffers/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Contains FieldMask class.""" + +from google.protobuf.descriptor import FieldDescriptor + + +class FieldMask(object): + """Class for FieldMask message type.""" + + __slots__ = () + + def ToJsonString(self): + """Converts FieldMask to string according to proto3 JSON spec.""" + camelcase_paths = [] + for path in self.paths: + camelcase_paths.append(_SnakeCaseToCamelCase(path)) + return ','.join(camelcase_paths) + + def FromJsonString(self, value): + """Converts string to FieldMask according to proto3 JSON spec.""" + if not isinstance(value, str): + raise ValueError('FieldMask JSON value not a string: {!r}'.format(value)) + self.Clear() + if value: + for path in value.split(','): + self.paths.append(_CamelCaseToSnakeCase(path)) + + def IsValidForDescriptor(self, message_descriptor): + """Checks whether the FieldMask is valid for Message Descriptor.""" + for path in self.paths: + if not _IsValidPath(message_descriptor, path): + return False + return True + + def AllFieldsFromDescriptor(self, message_descriptor): + """Gets all direct fields of Message Descriptor to FieldMask.""" + self.Clear() + for field in message_descriptor.fields: + self.paths.append(field.name) + + def CanonicalFormFromMask(self, mask): + """Converts a FieldMask to the canonical form. + + Removes paths that are covered by another path. For example, + "foo.bar" is covered by "foo" and will be removed if "foo" + is also in the FieldMask. Then sorts all paths in alphabetical order. + + Args: + mask: The original FieldMask to be converted. + """ + tree = _FieldMaskTree(mask) + tree.ToFieldMask(self) + + def Union(self, mask1, mask2): + """Merges mask1 and mask2 into this FieldMask.""" + _CheckFieldMaskMessage(mask1) + _CheckFieldMaskMessage(mask2) + tree = _FieldMaskTree(mask1) + tree.MergeFromFieldMask(mask2) + tree.ToFieldMask(self) + + def Intersect(self, mask1, mask2): + """Intersects mask1 and mask2 into this FieldMask.""" + _CheckFieldMaskMessage(mask1) + _CheckFieldMaskMessage(mask2) + tree = _FieldMaskTree(mask1) + intersection = _FieldMaskTree() + for path in mask2.paths: + tree.IntersectPath(path, intersection) + intersection.ToFieldMask(self) + + def MergeMessage( + self, source, destination, + replace_message_field=False, replace_repeated_field=False): + """Merges fields specified in FieldMask from source to destination. + + Args: + source: Source message. + destination: The destination message to be merged into. + replace_message_field: Replace message field if True. Merge message + field if False. + replace_repeated_field: Replace repeated field if True. Append + elements of repeated field if False. + """ + tree = _FieldMaskTree(self) + tree.MergeMessage( + source, destination, replace_message_field, replace_repeated_field) + + +def _IsValidPath(message_descriptor, path): + """Checks whether the path is valid for Message Descriptor.""" + parts = path.split('.') + last = parts.pop() + for name in parts: + field = message_descriptor.fields_by_name.get(name) + if (field is None or + field.label == FieldDescriptor.LABEL_REPEATED or + field.type != FieldDescriptor.TYPE_MESSAGE): + return False + message_descriptor = field.message_type + return last in message_descriptor.fields_by_name + + +def _CheckFieldMaskMessage(message): + """Raises ValueError if message is not a FieldMask.""" + message_descriptor = message.DESCRIPTOR + if (message_descriptor.name != 'FieldMask' or + message_descriptor.file.name != 'google/protobuf/field_mask.proto'): + raise ValueError('Message {0} is not a FieldMask.'.format( + message_descriptor.full_name)) + + +def _SnakeCaseToCamelCase(path_name): + """Converts a path name from snake_case to camelCase.""" + result = [] + after_underscore = False + for c in path_name: + if c.isupper(): + raise ValueError( + 'Fail to print FieldMask to Json string: Path name ' + '{0} must not contain uppercase letters.'.format(path_name)) + if after_underscore: + if c.islower(): + result.append(c.upper()) + after_underscore = False + else: + raise ValueError( + 'Fail to print FieldMask to Json string: The ' + 'character after a "_" must be a lowercase letter ' + 'in path name {0}.'.format(path_name)) + elif c == '_': + after_underscore = True + else: + result += c + + if after_underscore: + raise ValueError('Fail to print FieldMask to Json string: Trailing "_" ' + 'in path name {0}.'.format(path_name)) + return ''.join(result) + + +def _CamelCaseToSnakeCase(path_name): + """Converts a field name from camelCase to snake_case.""" + result = [] + for c in path_name: + if c == '_': + raise ValueError('Fail to parse FieldMask: Path name ' + '{0} must not contain "_"s.'.format(path_name)) + if c.isupper(): + result += '_' + result += c.lower() + else: + result += c + return ''.join(result) + + +class _FieldMaskTree(object): + """Represents a FieldMask in a tree structure. + + For example, given a FieldMask "foo.bar,foo.baz,bar.baz", + the FieldMaskTree will be: + [_root] -+- foo -+- bar + | | + | +- baz + | + +- bar --- baz + In the tree, each leaf node represents a field path. + """ + + __slots__ = ('_root',) + + def __init__(self, field_mask=None): + """Initializes the tree by FieldMask.""" + self._root = {} + if field_mask: + self.MergeFromFieldMask(field_mask) + + def MergeFromFieldMask(self, field_mask): + """Merges a FieldMask to the tree.""" + for path in field_mask.paths: + self.AddPath(path) + + def AddPath(self, path): + """Adds a field path into the tree. + + If the field path to add is a sub-path of an existing field path + in the tree (i.e., a leaf node), it means the tree already matches + the given path so nothing will be added to the tree. If the path + matches an existing non-leaf node in the tree, that non-leaf node + will be turned into a leaf node with all its children removed because + the path matches all the node's children. Otherwise, a new path will + be added. + + Args: + path: The field path to add. + """ + node = self._root + for name in path.split('.'): + if name not in node: + node[name] = {} + elif not node[name]: + # Pre-existing empty node implies we already have this entire tree. + return + node = node[name] + # Remove any sub-trees we might have had. + node.clear() + + def ToFieldMask(self, field_mask): + """Converts the tree to a FieldMask.""" + field_mask.Clear() + _AddFieldPaths(self._root, '', field_mask) + + def IntersectPath(self, path, intersection): + """Calculates the intersection part of a field path with this tree. + + Args: + path: The field path to calculates. + intersection: The out tree to record the intersection part. + """ + node = self._root + for name in path.split('.'): + if name not in node: + return + elif not node[name]: + intersection.AddPath(path) + return + node = node[name] + intersection.AddLeafNodes(path, node) + + def AddLeafNodes(self, prefix, node): + """Adds leaf nodes begin with prefix to this tree.""" + if not node: + self.AddPath(prefix) + for name in node: + child_path = prefix + '.' + name + self.AddLeafNodes(child_path, node[name]) + + def MergeMessage( + self, source, destination, + replace_message, replace_repeated): + """Merge all fields specified by this tree from source to destination.""" + _MergeMessage( + self._root, source, destination, replace_message, replace_repeated) + + +def _StrConvert(value): + """Converts value to str if it is not.""" + # This file is imported by c extension and some methods like ClearField + # requires string for the field name. py2/py3 has different text + # type and may use unicode. + if not isinstance(value, str): + return value.encode('utf-8') + return value + + +def _MergeMessage( + node, source, destination, replace_message, replace_repeated): + """Merge all fields specified by a sub-tree from source to destination.""" + source_descriptor = source.DESCRIPTOR + for name in node: + child = node[name] + field = source_descriptor.fields_by_name[name] + if field is None: + raise ValueError('Error: Can\'t find field {0} in message {1}.'.format( + name, source_descriptor.full_name)) + if child: + # Sub-paths are only allowed for singular message fields. + if (field.label == FieldDescriptor.LABEL_REPEATED or + field.cpp_type != FieldDescriptor.CPPTYPE_MESSAGE): + raise ValueError('Error: Field {0} in message {1} is not a singular ' + 'message field and cannot have sub-fields.'.format( + name, source_descriptor.full_name)) + if source.HasField(name): + _MergeMessage( + child, getattr(source, name), getattr(destination, name), + replace_message, replace_repeated) + continue + if field.label == FieldDescriptor.LABEL_REPEATED: + if replace_repeated: + destination.ClearField(_StrConvert(name)) + repeated_source = getattr(source, name) + repeated_destination = getattr(destination, name) + repeated_destination.MergeFrom(repeated_source) + else: + if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: + if replace_message: + destination.ClearField(_StrConvert(name)) + if source.HasField(name): + getattr(destination, name).MergeFrom(getattr(source, name)) + else: + setattr(destination, name, getattr(source, name)) + + +def _AddFieldPaths(node, prefix, field_mask): + """Adds the field paths descended from node to field_mask.""" + if not node and prefix: + field_mask.paths.append(prefix) + return + for name in sorted(node): + if prefix: + child_path = prefix + '.' + name + else: + child_path = name + _AddFieldPaths(node[name], child_path, field_mask) diff --git a/ext/protobuf/Python/google/protobuf/internal/generator_test.py b/ext/protobuf/Python/google/protobuf/internal/generator_test.py deleted file mode 100644 index 9883fce31..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/generator_test.py +++ /dev/null @@ -1,354 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -# TODO(robinson): Flesh this out considerably. We focused on reflection_test.py -# first, since it's testing the subtler code, and since it provides decent -# indirect testing of the protocol compiler output. - -"""Unittest that directly tests the output of the pure-Python protocol -compiler. See //google/protobuf/internal/reflection_test.py for a test which -further ensures that we can use Python protocol message objects as we expect. -""" - -__author__ = 'robinson@google.com (Will Robinson)' - -import unittest - -from google.protobuf.internal import test_bad_identifiers_pb2 -from google.protobuf import unittest_custom_options_pb2 -from google.protobuf import unittest_import_pb2 -from google.protobuf import unittest_import_public_pb2 -from google.protobuf import unittest_mset_pb2 -from google.protobuf import unittest_mset_wire_format_pb2 -from google.protobuf import unittest_no_generic_services_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import service -from google.protobuf import symbol_database - -MAX_EXTENSION = 536870912 - - -class GeneratorTest(unittest.TestCase): - - def testNestedMessageDescriptor(self): - field_name = 'optional_nested_message' - proto_type = unittest_pb2.TestAllTypes - self.assertEqual( - proto_type.NestedMessage.DESCRIPTOR, - proto_type.DESCRIPTOR.fields_by_name[field_name].message_type) - - def testEnums(self): - # We test only module-level enums here. - # TODO(robinson): Examine descriptors directly to check - # enum descriptor output. - self.assertEqual(4, unittest_pb2.FOREIGN_FOO) - self.assertEqual(5, unittest_pb2.FOREIGN_BAR) - self.assertEqual(6, unittest_pb2.FOREIGN_BAZ) - - proto = unittest_pb2.TestAllTypes() - self.assertEqual(1, proto.FOO) - self.assertEqual(1, unittest_pb2.TestAllTypes.FOO) - self.assertEqual(2, proto.BAR) - self.assertEqual(2, unittest_pb2.TestAllTypes.BAR) - self.assertEqual(3, proto.BAZ) - self.assertEqual(3, unittest_pb2.TestAllTypes.BAZ) - - def testExtremeDefaultValues(self): - message = unittest_pb2.TestExtremeDefaultValues() - - # Python pre-2.6 does not have isinf() or isnan() functions, so we have - # to provide our own. - def isnan(val): - # NaN is never equal to itself. - return val != val - def isinf(val): - # Infinity times zero equals NaN. - return not isnan(val) and isnan(val * 0) - - self.assertTrue(isinf(message.inf_double)) - self.assertTrue(message.inf_double > 0) - self.assertTrue(isinf(message.neg_inf_double)) - self.assertTrue(message.neg_inf_double < 0) - self.assertTrue(isnan(message.nan_double)) - - self.assertTrue(isinf(message.inf_float)) - self.assertTrue(message.inf_float > 0) - self.assertTrue(isinf(message.neg_inf_float)) - self.assertTrue(message.neg_inf_float < 0) - self.assertTrue(isnan(message.nan_float)) - self.assertEqual("? ? ?? ?? ??? ??/ ??-", message.cpp_trigraph) - - def testHasDefaultValues(self): - desc = unittest_pb2.TestAllTypes.DESCRIPTOR - - expected_has_default_by_name = { - 'optional_int32': False, - 'repeated_int32': False, - 'optional_nested_message': False, - 'default_int32': True, - } - - has_default_by_name = dict( - [(f.name, f.has_default_value) - for f in desc.fields - if f.name in expected_has_default_by_name]) - self.assertEqual(expected_has_default_by_name, has_default_by_name) - - def testContainingTypeBehaviorForExtensions(self): - self.assertEqual(unittest_pb2.optional_int32_extension.containing_type, - unittest_pb2.TestAllExtensions.DESCRIPTOR) - self.assertEqual(unittest_pb2.TestRequired.single.containing_type, - unittest_pb2.TestAllExtensions.DESCRIPTOR) - - def testExtensionScope(self): - self.assertEqual(unittest_pb2.optional_int32_extension.extension_scope, - None) - self.assertEqual(unittest_pb2.TestRequired.single.extension_scope, - unittest_pb2.TestRequired.DESCRIPTOR) - - def testIsExtension(self): - self.assertTrue(unittest_pb2.optional_int32_extension.is_extension) - self.assertTrue(unittest_pb2.TestRequired.single.is_extension) - - message_descriptor = unittest_pb2.TestRequired.DESCRIPTOR - non_extension_descriptor = message_descriptor.fields_by_name['a'] - self.assertTrue(not non_extension_descriptor.is_extension) - - def testOptions(self): - proto = unittest_mset_wire_format_pb2.TestMessageSet() - self.assertTrue(proto.DESCRIPTOR.GetOptions().message_set_wire_format) - - def testMessageWithCustomOptions(self): - proto = unittest_custom_options_pb2.TestMessageWithCustomOptions() - enum_options = proto.DESCRIPTOR.enum_types_by_name['AnEnum'].GetOptions() - self.assertTrue(enum_options is not None) - # TODO(gps): We really should test for the presence of the enum_opt1 - # extension and for its value to be set to -789. - - def testNestedTypes(self): - self.assertEqual( - set(unittest_pb2.TestAllTypes.DESCRIPTOR.nested_types), - set([ - unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR, - unittest_pb2.TestAllTypes.OptionalGroup.DESCRIPTOR, - unittest_pb2.TestAllTypes.RepeatedGroup.DESCRIPTOR, - ])) - self.assertEqual(unittest_pb2.TestEmptyMessage.DESCRIPTOR.nested_types, []) - self.assertEqual( - unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.nested_types, []) - - def testContainingType(self): - self.assertTrue( - unittest_pb2.TestEmptyMessage.DESCRIPTOR.containing_type is None) - self.assertTrue( - unittest_pb2.TestAllTypes.DESCRIPTOR.containing_type is None) - self.assertEqual( - unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.containing_type, - unittest_pb2.TestAllTypes.DESCRIPTOR) - self.assertEqual( - unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR.containing_type, - unittest_pb2.TestAllTypes.DESCRIPTOR) - self.assertEqual( - unittest_pb2.TestAllTypes.RepeatedGroup.DESCRIPTOR.containing_type, - unittest_pb2.TestAllTypes.DESCRIPTOR) - - def testContainingTypeInEnumDescriptor(self): - self.assertTrue(unittest_pb2._FOREIGNENUM.containing_type is None) - self.assertEqual(unittest_pb2._TESTALLTYPES_NESTEDENUM.containing_type, - unittest_pb2.TestAllTypes.DESCRIPTOR) - - def testPackage(self): - self.assertEqual( - unittest_pb2.TestAllTypes.DESCRIPTOR.file.package, - 'protobuf_unittest') - desc = unittest_pb2.TestAllTypes.NestedMessage.DESCRIPTOR - self.assertEqual(desc.file.package, 'protobuf_unittest') - self.assertEqual( - unittest_import_pb2.ImportMessage.DESCRIPTOR.file.package, - 'protobuf_unittest_import') - - self.assertEqual( - unittest_pb2._FOREIGNENUM.file.package, 'protobuf_unittest') - self.assertEqual( - unittest_pb2._TESTALLTYPES_NESTEDENUM.file.package, - 'protobuf_unittest') - self.assertEqual( - unittest_import_pb2._IMPORTENUM.file.package, - 'protobuf_unittest_import') - - def testExtensionRange(self): - self.assertEqual( - unittest_pb2.TestAllTypes.DESCRIPTOR.extension_ranges, []) - self.assertEqual( - unittest_pb2.TestAllExtensions.DESCRIPTOR.extension_ranges, - [(1, MAX_EXTENSION)]) - self.assertEqual( - unittest_pb2.TestMultipleExtensionRanges.DESCRIPTOR.extension_ranges, - [(42, 43), (4143, 4244), (65536, MAX_EXTENSION)]) - - def testFileDescriptor(self): - self.assertEqual(unittest_pb2.DESCRIPTOR.name, - 'google/protobuf/unittest.proto') - self.assertEqual(unittest_pb2.DESCRIPTOR.package, 'protobuf_unittest') - self.assertFalse(unittest_pb2.DESCRIPTOR.serialized_pb is None) - self.assertEqual(unittest_pb2.DESCRIPTOR.dependencies, - [unittest_import_pb2.DESCRIPTOR]) - self.assertEqual(unittest_import_pb2.DESCRIPTOR.dependencies, - [unittest_import_public_pb2.DESCRIPTOR]) - self.assertEqual(unittest_import_pb2.DESCRIPTOR.public_dependencies, - [unittest_import_public_pb2.DESCRIPTOR]) - def testNoGenericServices(self): - self.assertTrue(hasattr(unittest_no_generic_services_pb2, "TestMessage")) - self.assertTrue(hasattr(unittest_no_generic_services_pb2, "FOO")) - self.assertTrue(hasattr(unittest_no_generic_services_pb2, "test_extension")) - - # Make sure unittest_no_generic_services_pb2 has no services subclassing - # Proto2 Service class. - if hasattr(unittest_no_generic_services_pb2, "TestService"): - self.assertFalse(issubclass(unittest_no_generic_services_pb2.TestService, - service.Service)) - - def testMessageTypesByName(self): - file_type = unittest_pb2.DESCRIPTOR - self.assertEqual( - unittest_pb2._TESTALLTYPES, - file_type.message_types_by_name[unittest_pb2._TESTALLTYPES.name]) - - # Nested messages shouldn't be included in the message_types_by_name - # dictionary (like in the C++ API). - self.assertFalse( - unittest_pb2._TESTALLTYPES_NESTEDMESSAGE.name in - file_type.message_types_by_name) - - def testEnumTypesByName(self): - file_type = unittest_pb2.DESCRIPTOR - self.assertEqual( - unittest_pb2._FOREIGNENUM, - file_type.enum_types_by_name[unittest_pb2._FOREIGNENUM.name]) - - def testExtensionsByName(self): - file_type = unittest_pb2.DESCRIPTOR - self.assertEqual( - unittest_pb2.my_extension_string, - file_type.extensions_by_name[unittest_pb2.my_extension_string.name]) - - def testPublicImports(self): - # Test public imports as embedded message. - all_type_proto = unittest_pb2.TestAllTypes() - self.assertEqual(0, all_type_proto.optional_public_import_message.e) - - # PublicImportMessage is actually defined in unittest_import_public_pb2 - # module, and is public imported by unittest_import_pb2 module. - public_import_proto = unittest_import_pb2.PublicImportMessage() - self.assertEqual(0, public_import_proto.e) - self.assertTrue(unittest_import_public_pb2.PublicImportMessage is - unittest_import_pb2.PublicImportMessage) - - def testBadIdentifiers(self): - # We're just testing that the code was imported without problems. - message = test_bad_identifiers_pb2.TestBadIdentifiers() - self.assertEqual(message.Extensions[test_bad_identifiers_pb2.message], - "foo") - self.assertEqual(message.Extensions[test_bad_identifiers_pb2.descriptor], - "bar") - self.assertEqual(message.Extensions[test_bad_identifiers_pb2.reflection], - "baz") - self.assertEqual(message.Extensions[test_bad_identifiers_pb2.service], - "qux") - - def testOneof(self): - desc = unittest_pb2.TestAllTypes.DESCRIPTOR - self.assertEqual(1, len(desc.oneofs)) - self.assertEqual('oneof_field', desc.oneofs[0].name) - self.assertEqual(0, desc.oneofs[0].index) - self.assertIs(desc, desc.oneofs[0].containing_type) - self.assertIs(desc.oneofs[0], desc.oneofs_by_name['oneof_field']) - nested_names = set(['oneof_uint32', 'oneof_nested_message', - 'oneof_string', 'oneof_bytes']) - self.assertEqual( - nested_names, - set([field.name for field in desc.oneofs[0].fields])) - for field_name, field_desc in desc.fields_by_name.items(): - if field_name in nested_names: - self.assertIs(desc.oneofs[0], field_desc.containing_oneof) - else: - self.assertIsNone(field_desc.containing_oneof) - - def testEnumWithDupValue(self): - self.assertEqual('FOO1', - unittest_pb2.TestEnumWithDupValue.Name(unittest_pb2.FOO1)) - self.assertEqual('FOO1', - unittest_pb2.TestEnumWithDupValue.Name(unittest_pb2.FOO2)) - self.assertEqual('BAR1', - unittest_pb2.TestEnumWithDupValue.Name(unittest_pb2.BAR1)) - self.assertEqual('BAR1', - unittest_pb2.TestEnumWithDupValue.Name(unittest_pb2.BAR2)) - - -class SymbolDatabaseRegistrationTest(unittest.TestCase): - """Checks that messages, enums and files are correctly registered.""" - - def testGetSymbol(self): - self.assertEqual( - unittest_pb2.TestAllTypes, symbol_database.Default().GetSymbol( - 'protobuf_unittest.TestAllTypes')) - self.assertEqual( - unittest_pb2.TestAllTypes.NestedMessage, - symbol_database.Default().GetSymbol( - 'protobuf_unittest.TestAllTypes.NestedMessage')) - with self.assertRaises(KeyError): - symbol_database.Default().GetSymbol('protobuf_unittest.NestedMessage') - self.assertEqual( - unittest_pb2.TestAllTypes.OptionalGroup, - symbol_database.Default().GetSymbol( - 'protobuf_unittest.TestAllTypes.OptionalGroup')) - self.assertEqual( - unittest_pb2.TestAllTypes.RepeatedGroup, - symbol_database.Default().GetSymbol( - 'protobuf_unittest.TestAllTypes.RepeatedGroup')) - - def testEnums(self): - self.assertEqual( - 'protobuf_unittest.ForeignEnum', - symbol_database.Default().pool.FindEnumTypeByName( - 'protobuf_unittest.ForeignEnum').full_name) - self.assertEqual( - 'protobuf_unittest.TestAllTypes.NestedEnum', - symbol_database.Default().pool.FindEnumTypeByName( - 'protobuf_unittest.TestAllTypes.NestedEnum').full_name) - - def testFindFileByName(self): - self.assertEqual( - 'google/protobuf/unittest.proto', - symbol_database.Default().pool.FindFileByName( - 'google/protobuf/unittest.proto').name) - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/import_test.py b/ext/protobuf/Python/google/protobuf/internal/import_test.py deleted file mode 100644 index b5c572cfc..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/import_test.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unittest for nested public imports.""" - -import unittest - -from google.protobuf.internal.import_test_package import outer_pb2 - - -class ImportTest(unittest.TestCase): - - def testPackageInitializationImport(self): - """Test that we can import nested import public messages.""" - - msg = outer_pb2.Outer() - self.assertEqual(58, msg.import_public_nested.value) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/import_test_package/__init__.py b/ext/protobuf/Python/google/protobuf/internal/import_test_package/__init__.py deleted file mode 100644 index 5121dd0ec..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/import_test_package/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Sample module importing a nested proto from itself.""" - -from google.protobuf.internal.import_test_package import outer_pb2 as myproto diff --git a/ext/protobuf/Python/google/protobuf/internal/json_format_test.py b/ext/protobuf/Python/google/protobuf/internal/json_format_test.py deleted file mode 100644 index d018c3f2e..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/json_format_test.py +++ /dev/null @@ -1,1285 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Test for google.protobuf.json_format.""" - -__author__ = 'jieluo@google.com (Jie Luo)' - -import json -import math -import struct - -import unittest - -from google.protobuf import any_pb2 -from google.protobuf import duration_pb2 -from google.protobuf import field_mask_pb2 -from google.protobuf import struct_pb2 -from google.protobuf import timestamp_pb2 -from google.protobuf import wrappers_pb2 -from google.protobuf import any_test_pb2 -from google.protobuf import unittest_mset_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf.internal import test_proto3_optional_pb2 -from google.protobuf import descriptor_pool -from google.protobuf import json_format -from google.protobuf.util import json_format_pb2 -from google.protobuf.util import json_format_proto3_pb2 - - -class JsonFormatBase(unittest.TestCase): - - def FillAllFields(self, message): - message.int32_value = 20 - message.int64_value = -20 - message.uint32_value = 3120987654 - message.uint64_value = 12345678900 - message.float_value = float('-inf') - message.double_value = 3.1415 - message.bool_value = True - message.string_value = 'foo' - message.bytes_value = b'bar' - message.message_value.value = 10 - message.enum_value = json_format_proto3_pb2.BAR - # Repeated - message.repeated_int32_value.append(0x7FFFFFFF) - message.repeated_int32_value.append(-2147483648) - message.repeated_int64_value.append(9007199254740992) - message.repeated_int64_value.append(-9007199254740992) - message.repeated_uint32_value.append(0xFFFFFFF) - message.repeated_uint32_value.append(0x7FFFFFF) - message.repeated_uint64_value.append(9007199254740992) - message.repeated_uint64_value.append(9007199254740991) - message.repeated_float_value.append(0) - - message.repeated_double_value.append(1E-15) - message.repeated_double_value.append(float('inf')) - message.repeated_bool_value.append(True) - message.repeated_bool_value.append(False) - message.repeated_string_value.append('Few symbols!#$,;') - message.repeated_string_value.append('bar') - message.repeated_bytes_value.append(b'foo') - message.repeated_bytes_value.append(b'bar') - message.repeated_message_value.add().value = 10 - message.repeated_message_value.add().value = 11 - message.repeated_enum_value.append(json_format_proto3_pb2.FOO) - message.repeated_enum_value.append(json_format_proto3_pb2.BAR) - self.message = message - - def CheckParseBack(self, message, parsed_message): - json_format.Parse(json_format.MessageToJson(message), - parsed_message) - self.assertEqual(message, parsed_message) - - def CheckError(self, text, error_message): - message = json_format_proto3_pb2.TestMessage() - self.assertRaisesRegex(json_format.ParseError, error_message, - json_format.Parse, text, message) - - -class JsonFormatTest(JsonFormatBase): - - def testEmptyMessageToJson(self): - message = json_format_proto3_pb2.TestMessage() - self.assertEqual(json_format.MessageToJson(message), - '{}') - parsed_message = json_format_proto3_pb2.TestMessage() - self.CheckParseBack(message, parsed_message) - - def testPartialMessageToJson(self): - message = json_format_proto3_pb2.TestMessage( - string_value='test', - repeated_int32_value=[89, 4]) - self.assertEqual(json.loads(json_format.MessageToJson(message)), - json.loads('{"stringValue": "test", ' - '"repeatedInt32Value": [89, 4]}')) - parsed_message = json_format_proto3_pb2.TestMessage() - self.CheckParseBack(message, parsed_message) - - def testAllFieldsToJson(self): - message = json_format_proto3_pb2.TestMessage() - text = ('{"int32Value": 20, ' - '"int64Value": "-20", ' - '"uint32Value": 3120987654,' - '"uint64Value": "12345678900",' - '"floatValue": "-Infinity",' - '"doubleValue": 3.1415,' - '"boolValue": true,' - '"stringValue": "foo",' - '"bytesValue": "YmFy",' - '"messageValue": {"value": 10},' - '"enumValue": "BAR",' - '"repeatedInt32Value": [2147483647, -2147483648],' - '"repeatedInt64Value": ["9007199254740992", "-9007199254740992"],' - '"repeatedUint32Value": [268435455, 134217727],' - '"repeatedUint64Value": ["9007199254740992", "9007199254740991"],' - '"repeatedFloatValue": [0],' - '"repeatedDoubleValue": [1e-15, "Infinity"],' - '"repeatedBoolValue": [true, false],' - '"repeatedStringValue": ["Few symbols!#$,;", "bar"],' - '"repeatedBytesValue": ["Zm9v", "YmFy"],' - '"repeatedMessageValue": [{"value": 10}, {"value": 11}],' - '"repeatedEnumValue": ["FOO", "BAR"]' - '}') - self.FillAllFields(message) - self.assertEqual( - json.loads(json_format.MessageToJson(message)), - json.loads(text)) - parsed_message = json_format_proto3_pb2.TestMessage() - json_format.Parse(text, parsed_message) - self.assertEqual(message, parsed_message) - - def testUnknownEnumToJsonAndBack(self): - text = '{\n "enumValue": 999\n}' - message = json_format_proto3_pb2.TestMessage() - message.enum_value = 999 - self.assertEqual(json_format.MessageToJson(message), - text) - parsed_message = json_format_proto3_pb2.TestMessage() - json_format.Parse(text, parsed_message) - self.assertEqual(message, parsed_message) - - def testExtensionToJsonAndBack(self): - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - message_text = json_format.MessageToJson( - message - ) - parsed_message = unittest_mset_pb2.TestMessageSetContainer() - json_format.Parse(message_text, parsed_message) - self.assertEqual(message, parsed_message) - - def testExtensionErrors(self): - self.CheckError('{"[extensionField]": {}}', - 'Message type proto3.TestMessage does not have extensions') - - def testExtensionToDictAndBack(self): - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - message_dict = json_format.MessageToDict( - message - ) - parsed_message = unittest_mset_pb2.TestMessageSetContainer() - json_format.ParseDict(message_dict, parsed_message) - self.assertEqual(message, parsed_message) - - def testExtensionToDictAndBackWithScalar(self): - message = unittest_pb2.TestAllExtensions() - ext1 = unittest_pb2.TestNestedExtension.test - message.Extensions[ext1] = 'data' - message_dict = json_format.MessageToDict( - message - ) - parsed_message = unittest_pb2.TestAllExtensions() - json_format.ParseDict(message_dict, parsed_message) - self.assertEqual(message, parsed_message) - - def testJsonParseDictToAnyDoesNotAlterInput(self): - orig_dict = { - 'int32Value': 20, - '@type': 'type.googleapis.com/proto3.TestMessage' - } - copied_dict = json.loads(json.dumps(orig_dict)) - parsed_message = any_pb2.Any() - json_format.ParseDict(copied_dict, parsed_message) - self.assertEqual(copied_dict, orig_dict) - - def testExtensionSerializationDictMatchesProto3Spec(self): - """See go/proto3-json-spec for spec. - """ - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - message_dict = json_format.MessageToDict( - message - ) - golden_dict = { - 'messageSet': { - '[protobuf_unittest.' - 'TestMessageSetExtension1.message_set_extension]': { - 'i': 23, - }, - '[protobuf_unittest.' - 'TestMessageSetExtension2.message_set_extension]': { - 'str': u'foo', - }, - }, - } - self.assertEqual(golden_dict, message_dict) - parsed_msg = unittest_mset_pb2.TestMessageSetContainer() - json_format.ParseDict(golden_dict, parsed_msg) - self.assertEqual(message, parsed_msg) - - def testExtensionSerializationDictMatchesProto3SpecMore(self): - """See go/proto3-json-spec for spec. - """ - message = json_format_pb2.TestMessageWithExtension() - ext = json_format_pb2.TestExtension.ext - message.Extensions[ext].value = 'stuff' - message_dict = json_format.MessageToDict( - message - ) - expected_dict = { - '[protobuf_unittest.TestExtension.ext]': { - 'value': u'stuff', - }, - } - self.assertEqual(expected_dict, message_dict) - - def testExtensionSerializationJsonMatchesProto3Spec(self): - """See go/proto3-json-spec for spec. - """ - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - message_text = json_format.MessageToJson( - message - ) - ext1_text = ('protobuf_unittest.TestMessageSetExtension1.' - 'message_set_extension') - ext2_text = ('protobuf_unittest.TestMessageSetExtension2.' - 'message_set_extension') - golden_text = ('{"messageSet": {' - ' "[%s]": {' - ' "i": 23' - ' },' - ' "[%s]": {' - ' "str": "foo"' - ' }' - '}}') % (ext1_text, ext2_text) - self.assertEqual(json.loads(golden_text), json.loads(message_text)) - - def testJsonEscapeString(self): - message = json_format_proto3_pb2.TestMessage() - message.string_value = '&\n<\"\r>\b\t\f\\\001/' - message.string_value += (b'\xe2\x80\xa8\xe2\x80\xa9').decode('utf-8') - self.assertEqual( - json_format.MessageToJson(message), - '{\n "stringValue": ' - '"&\\n<\\\"\\r>\\b\\t\\f\\\\\\u0001/\\u2028\\u2029"\n}') - parsed_message = json_format_proto3_pb2.TestMessage() - self.CheckParseBack(message, parsed_message) - text = u'{"int32Value": "\u0031"}' - json_format.Parse(text, message) - self.assertEqual(message.int32_value, 1) - - def testAlwaysSeriliaze(self): - message = json_format_proto3_pb2.TestMessage( - string_value='foo') - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"repeatedStringValue": [],' - '"stringValue": "foo",' - '"repeatedBoolValue": [],' - '"repeatedUint32Value": [],' - '"repeatedInt32Value": [],' - '"enumValue": "FOO",' - '"int32Value": 0,' - '"floatValue": 0,' - '"int64Value": "0",' - '"uint32Value": 0,' - '"repeatedBytesValue": [],' - '"repeatedUint64Value": [],' - '"repeatedDoubleValue": [],' - '"bytesValue": "",' - '"boolValue": false,' - '"repeatedEnumValue": [],' - '"uint64Value": "0",' - '"doubleValue": 0,' - '"repeatedFloatValue": [],' - '"repeatedInt64Value": [],' - '"repeatedMessageValue": []}')) - parsed_message = json_format_proto3_pb2.TestMessage() - self.CheckParseBack(message, parsed_message) - - def testProto3Optional(self): - message = test_proto3_optional_pb2.TestProto3Optional() - self.assertEqual( - json.loads( - json_format.MessageToJson( - message, including_default_value_fields=True)), - json.loads('{}')) - message.optional_int32 = 0 - self.assertEqual( - json.loads( - json_format.MessageToJson( - message, including_default_value_fields=True)), - json.loads('{"optionalInt32": 0}')) - - def testIntegersRepresentedAsFloat(self): - message = json_format_proto3_pb2.TestMessage() - json_format.Parse('{"int32Value": -2.147483648e9}', message) - self.assertEqual(message.int32_value, -2147483648) - json_format.Parse('{"int32Value": 1e5}', message) - self.assertEqual(message.int32_value, 100000) - json_format.Parse('{"int32Value": 1.0}', message) - self.assertEqual(message.int32_value, 1) - - def testMapFields(self): - message = json_format_proto3_pb2.TestNestedMap() - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"boolMap": {},' - '"int32Map": {},' - '"int64Map": {},' - '"uint32Map": {},' - '"uint64Map": {},' - '"stringMap": {},' - '"mapMap": {}' - '}')) - message.bool_map[True] = 1 - message.bool_map[False] = 2 - message.int32_map[1] = 2 - message.int32_map[2] = 3 - message.int64_map[1] = 2 - message.int64_map[2] = 3 - message.uint32_map[1] = 2 - message.uint32_map[2] = 3 - message.uint64_map[1] = 2 - message.uint64_map[2] = 3 - message.string_map['1'] = 2 - message.string_map['null'] = 3 - message.map_map['1'].bool_map[True] = 3 - self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads('{' - '"boolMap": {"false": 2, "true": 1},' - '"int32Map": {"1": 2, "2": 3},' - '"int64Map": {"1": 2, "2": 3},' - '"uint32Map": {"1": 2, "2": 3},' - '"uint64Map": {"1": 2, "2": 3},' - '"stringMap": {"1": 2, "null": 3},' - '"mapMap": {"1": {"boolMap": {"true": 3}}}' - '}')) - parsed_message = json_format_proto3_pb2.TestNestedMap() - self.CheckParseBack(message, parsed_message) - - def testOneofFields(self): - message = json_format_proto3_pb2.TestOneof() - # Always print does not affect oneof fields. - self.assertEqual( - json_format.MessageToJson(message, True), - '{}') - message.oneof_int32_value = 0 - self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "oneofInt32Value": 0\n' - '}') - parsed_message = json_format_proto3_pb2.TestOneof() - self.CheckParseBack(message, parsed_message) - - def testSurrogates(self): - # Test correct surrogate handling. - message = json_format_proto3_pb2.TestMessage() - json_format.Parse('{"stringValue": "\\uD83D\\uDE01"}', message) - self.assertEqual(message.string_value, - b'\xF0\x9F\x98\x81'.decode('utf-8', 'strict')) - - # Error case: unpaired high surrogate. - self.CheckError( - '{"stringValue": "\\uD83D"}', - r'Invalid \\uXXXX escape|Unpaired.*surrogate') - - # Unpaired low surrogate. - self.CheckError( - '{"stringValue": "\\uDE01"}', - r'Invalid \\uXXXX escape|Unpaired.*surrogate') - - def testTimestampMessage(self): - message = json_format_proto3_pb2.TestTimestamp() - message.value.seconds = 0 - message.value.nanos = 0 - message.repeated_value.add().seconds = 20 - message.repeated_value[0].nanos = 1 - message.repeated_value.add().seconds = 0 - message.repeated_value[1].nanos = 10000 - message.repeated_value.add().seconds = 100000000 - message.repeated_value[2].nanos = 0 - # Maximum time - message.repeated_value.add().seconds = 253402300799 - message.repeated_value[3].nanos = 999999999 - # Minimum time - message.repeated_value.add().seconds = -62135596800 - message.repeated_value[4].nanos = 0 - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"value": "1970-01-01T00:00:00Z",' - '"repeatedValue": [' - ' "1970-01-01T00:00:20.000000001Z",' - ' "1970-01-01T00:00:00.000010Z",' - ' "1973-03-03T09:46:40Z",' - ' "9999-12-31T23:59:59.999999999Z",' - ' "0001-01-01T00:00:00Z"' - ']' - '}')) - parsed_message = json_format_proto3_pb2.TestTimestamp() - self.CheckParseBack(message, parsed_message) - text = (r'{"value": "1970-01-01T00:00:00.01+08:00",' - r'"repeatedValue":[' - r' "1970-01-01T00:00:00.01+08:30",' - r' "1970-01-01T00:00:00.01-01:23"]}') - json_format.Parse(text, parsed_message) - self.assertEqual(parsed_message.value.seconds, -8 * 3600) - self.assertEqual(parsed_message.value.nanos, 10000000) - self.assertEqual(parsed_message.repeated_value[0].seconds, -8.5 * 3600) - self.assertEqual(parsed_message.repeated_value[1].seconds, 3600 + 23 * 60) - - def testDurationMessage(self): - message = json_format_proto3_pb2.TestDuration() - message.value.seconds = 1 - message.repeated_value.add().seconds = 0 - message.repeated_value[0].nanos = 10 - message.repeated_value.add().seconds = -1 - message.repeated_value[1].nanos = -1000 - message.repeated_value.add().seconds = 10 - message.repeated_value[2].nanos = 11000000 - message.repeated_value.add().seconds = -315576000000 - message.repeated_value.add().seconds = 315576000000 - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{' - '"value": "1s",' - '"repeatedValue": [' - ' "0.000000010s",' - ' "-1.000001s",' - ' "10.011s",' - ' "-315576000000s",' - ' "315576000000s"' - ']' - '}')) - parsed_message = json_format_proto3_pb2.TestDuration() - self.CheckParseBack(message, parsed_message) - - def testFieldMaskMessage(self): - message = json_format_proto3_pb2.TestFieldMask() - message.value.paths.append('foo.bar') - message.value.paths.append('bar') - self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "value": "foo.bar,bar"\n' - '}') - parsed_message = json_format_proto3_pb2.TestFieldMask() - self.CheckParseBack(message, parsed_message) - - message.value.Clear() - self.assertEqual( - json_format.MessageToJson(message, True), - '{\n' - ' "value": ""\n' - '}') - self.CheckParseBack(message, parsed_message) - - def testWrapperMessage(self): - message = json_format_proto3_pb2.TestWrapper() - message.bool_value.value = False - message.int32_value.value = 0 - message.string_value.value = '' - message.bytes_value.value = b'' - message.repeated_bool_value.add().value = True - message.repeated_bool_value.add().value = False - message.repeated_int32_value.add() - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads('{\n' - ' "int32Value": 0,' - ' "boolValue": false,' - ' "stringValue": "",' - ' "bytesValue": "",' - ' "repeatedBoolValue": [true, false],' - ' "repeatedInt32Value": [0],' - ' "repeatedUint32Value": [],' - ' "repeatedFloatValue": [],' - ' "repeatedDoubleValue": [],' - ' "repeatedBytesValue": [],' - ' "repeatedInt64Value": [],' - ' "repeatedUint64Value": [],' - ' "repeatedStringValue": []' - '}')) - parsed_message = json_format_proto3_pb2.TestWrapper() - self.CheckParseBack(message, parsed_message) - - def testStructMessage(self): - message = json_format_proto3_pb2.TestStruct() - message.value['name'] = 'Jim' - message.value['age'] = 10 - message.value['attend'] = True - message.value['email'] = None - message.value.get_or_create_struct('address')['city'] = 'SFO' - message.value['address']['house_number'] = 1024 - message.value.get_or_create_struct('empty_struct') - message.value.get_or_create_list('empty_list') - struct_list = message.value.get_or_create_list('list') - struct_list.extend([6, 'seven', True, False, None]) - struct_list.add_struct()['subkey2'] = 9 - message.repeated_value.add()['age'] = 11 - message.repeated_value.add() - self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads( - '{' - ' "value": {' - ' "address": {' - ' "city": "SFO", ' - ' "house_number": 1024' - ' }, ' - ' "empty_struct": {}, ' - ' "empty_list": [], ' - ' "age": 10, ' - ' "name": "Jim", ' - ' "attend": true, ' - ' "email": null, ' - ' "list": [6, "seven", true, false, null, {"subkey2": 9}]' - ' },' - ' "repeatedValue": [{"age": 11}, {}]' - '}')) - parsed_message = json_format_proto3_pb2.TestStruct() - self.CheckParseBack(message, parsed_message) - # check for regression; this used to raise - parsed_message.value['empty_struct'] - parsed_message.value['empty_list'] - - def testValueMessage(self): - message = json_format_proto3_pb2.TestValue() - message.value.string_value = 'hello' - message.repeated_value.add().number_value = 11.1 - message.repeated_value.add().bool_value = False - message.repeated_value.add().null_value = 0 - self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads( - '{' - ' "value": "hello",' - ' "repeatedValue": [11.1, false, null]' - '}')) - parsed_message = json_format_proto3_pb2.TestValue() - self.CheckParseBack(message, parsed_message) - # Can't parse back if the Value message is not set. - message.repeated_value.add() - self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads( - '{' - ' "value": "hello",' - ' "repeatedValue": [11.1, false, null, null]' - '}')) - message.Clear() - json_format.Parse('{"value": null}', message) - self.assertEqual(message.value.WhichOneof('kind'), 'null_value') - - def testListValueMessage(self): - message = json_format_proto3_pb2.TestListValue() - message.value.values.add().number_value = 11.1 - message.value.values.add().null_value = 0 - message.value.values.add().bool_value = True - message.value.values.add().string_value = 'hello' - message.value.values.add().struct_value['name'] = 'Jim' - message.repeated_value.add().values.add().number_value = 1 - message.repeated_value.add() - self.assertEqual( - json.loads(json_format.MessageToJson(message, False)), - json.loads( - '{"value": [11.1, null, true, "hello", {"name": "Jim"}]\n,' - '"repeatedValue": [[1], []]}')) - parsed_message = json_format_proto3_pb2.TestListValue() - self.CheckParseBack(message, parsed_message) - - def testNullValue(self): - message = json_format_proto3_pb2.TestOneof() - message.oneof_null_value = 0 - self.assertEqual(json_format.MessageToJson(message), - '{\n "oneofNullValue": null\n}') - parsed_message = json_format_proto3_pb2.TestOneof() - self.CheckParseBack(message, parsed_message) - # Check old format is also accepted - new_message = json_format_proto3_pb2.TestOneof() - json_format.Parse('{\n "oneofNullValue": "NULL_VALUE"\n}', - new_message) - self.assertEqual(json_format.MessageToJson(new_message), - '{\n "oneofNullValue": null\n}') - - def testAnyMessage(self): - message = json_format_proto3_pb2.TestAny() - value1 = json_format_proto3_pb2.MessageType() - value2 = json_format_proto3_pb2.MessageType() - value1.value = 1234 - value2.value = 5678 - message.value.Pack(value1) - message.repeated_value.add().Pack(value1) - message.repeated_value.add().Pack(value2) - message.repeated_value.add() - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "repeatedValue": [ {\n' - ' "@type": "type.googleapis.com/proto3.MessageType",\n' - ' "value": 1234\n' - ' }, {\n' - ' "@type": "type.googleapis.com/proto3.MessageType",\n' - ' "value": 5678\n' - ' },\n' - ' {}],\n' - ' "value": {\n' - ' "@type": "type.googleapis.com/proto3.MessageType",\n' - ' "value": 1234\n' - ' }\n' - '}\n')) - parsed_message = json_format_proto3_pb2.TestAny() - self.CheckParseBack(message, parsed_message) - # Must print @type first - test_message = json_format_proto3_pb2.TestMessage( - bool_value=True, - int32_value=20, - int64_value=-20, - uint32_value=20, - uint64_value=20, - double_value=3.14, - string_value='foo') - message.Clear() - message.value.Pack(test_message) - self.assertEqual( - json_format.MessageToJson(message, False)[0:68], - '{\n' - ' "value": {\n' - ' "@type": "type.googleapis.com/proto3.TestMessage"') - - def testAnyMessageDescriptorPoolMissingType(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - empty_pool = descriptor_pool.DescriptorPool() - with self.assertRaises(TypeError) as cm: - json_format.MessageToJson(message, True, descriptor_pool=empty_pool) - self.assertEqual( - 'Can not find message descriptor by type_url:' - ' type.googleapis.com/protobuf_unittest.OneString', str(cm.exception)) - - def testWellKnownInAnyMessage(self): - message = any_pb2.Any() - int32_value = wrappers_pb2.Int32Value() - int32_value.value = 1234 - message.Pack(int32_value) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": \"type.googleapis.com/google.protobuf.Int32Value\",\n' - ' "value": 1234\n' - '}\n')) - parsed_message = any_pb2.Any() - self.CheckParseBack(message, parsed_message) - - timestamp = timestamp_pb2.Timestamp() - message.Pack(timestamp) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": "type.googleapis.com/google.protobuf.Timestamp",\n' - ' "value": "1970-01-01T00:00:00Z"\n' - '}\n')) - self.CheckParseBack(message, parsed_message) - - duration = duration_pb2.Duration() - duration.seconds = 1 - message.Pack(duration) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": "type.googleapis.com/google.protobuf.Duration",\n' - ' "value": "1s"\n' - '}\n')) - self.CheckParseBack(message, parsed_message) - - field_mask = field_mask_pb2.FieldMask() - field_mask.paths.append('foo.bar') - field_mask.paths.append('bar') - message.Pack(field_mask) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": "type.googleapis.com/google.protobuf.FieldMask",\n' - ' "value": "foo.bar,bar"\n' - '}\n')) - self.CheckParseBack(message, parsed_message) - - struct_message = struct_pb2.Struct() - struct_message['name'] = 'Jim' - message.Pack(struct_message) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": "type.googleapis.com/google.protobuf.Struct",\n' - ' "value": {"name": "Jim"}\n' - '}\n')) - self.CheckParseBack(message, parsed_message) - - nested_any = any_pb2.Any() - int32_value.value = 5678 - nested_any.Pack(int32_value) - message.Pack(nested_any) - self.assertEqual( - json.loads(json_format.MessageToJson(message, True)), - json.loads( - '{\n' - ' "@type": "type.googleapis.com/google.protobuf.Any",\n' - ' "value": {\n' - ' "@type": "type.googleapis.com/google.protobuf.Int32Value",\n' - ' "value": 5678\n' - ' }\n' - '}\n')) - self.CheckParseBack(message, parsed_message) - - def testParseNull(self): - message = json_format_proto3_pb2.TestMessage() - parsed_message = json_format_proto3_pb2.TestMessage() - self.FillAllFields(parsed_message) - json_format.Parse('{"int32Value": null, ' - '"int64Value": null, ' - '"uint32Value": null,' - '"uint64Value": null,' - '"floatValue": null,' - '"doubleValue": null,' - '"boolValue": null,' - '"stringValue": null,' - '"bytesValue": null,' - '"messageValue": null,' - '"enumValue": null,' - '"repeatedInt32Value": null,' - '"repeatedInt64Value": null,' - '"repeatedUint32Value": null,' - '"repeatedUint64Value": null,' - '"repeatedFloatValue": null,' - '"repeatedDoubleValue": null,' - '"repeatedBoolValue": null,' - '"repeatedStringValue": null,' - '"repeatedBytesValue": null,' - '"repeatedMessageValue": null,' - '"repeatedEnumValue": null' - '}', - parsed_message) - self.assertEqual(message, parsed_message) - # Null and {} should have different behavior for sub message. - self.assertFalse(parsed_message.HasField('message_value')) - json_format.Parse('{"messageValue": {}}', parsed_message) - self.assertTrue(parsed_message.HasField('message_value')) - # Null is not allowed to be used as an element in repeated field. - self.assertRaisesRegex( - json_format.ParseError, r'Failed to parse repeatedInt32Value field: ' - r'null is not allowed to be used as an element in a repeated field ' - r'at TestMessage.repeatedInt32Value\[1\].', json_format.Parse, - '{"repeatedInt32Value":[1, null]}', parsed_message) - self.CheckError( - '{"repeatedMessageValue":[null]}', - r'Failed to parse repeatedMessageValue field: null is not' - r' allowed to be used as an element in a repeated field ' - r'at TestMessage.repeatedMessageValue\[0\].') - - def testNanFloat(self): - message = json_format_proto3_pb2.TestMessage() - message.float_value = float('nan') - text = '{\n "floatValue": "NaN"\n}' - self.assertEqual(json_format.MessageToJson(message), text) - parsed_message = json_format_proto3_pb2.TestMessage() - json_format.Parse(text, parsed_message) - self.assertTrue(math.isnan(parsed_message.float_value)) - - def testParseDoubleToFloat(self): - message = json_format_proto3_pb2.TestMessage() - text = ('{"repeatedDoubleValue": [3.4028235e+39, 1.4028235e-39]\n}') - json_format.Parse(text, message) - self.assertEqual(message.repeated_double_value[0], 3.4028235e+39) - self.assertEqual(message.repeated_double_value[1], 1.4028235e-39) - text = ('{"repeatedFloatValue": [3.4028235e+39, 1.4028235e-39]\n}') - self.CheckError( - text, r'Failed to parse repeatedFloatValue field: ' - r'Float value too large at TestMessage.repeatedFloatValue\[0\].') - - def testFloatPrecision(self): - message = json_format_proto3_pb2.TestMessage() - message.float_value = 1.123456789 - # Set to 8 valid digits. - text = '{\n "floatValue": 1.1234568\n}' - self.assertEqual( - json_format.MessageToJson(message, float_precision=8), text) - # Set to 7 valid digits. - text = '{\n "floatValue": 1.123457\n}' - self.assertEqual( - json_format.MessageToJson(message, float_precision=7), text) - - # Default float_precision will automatic print shortest float. - message.float_value = 1.1000000011 - text = '{\n "floatValue": 1.1\n}' - self.assertEqual( - json_format.MessageToJson(message), text) - message.float_value = 1.00000075e-36 - text = '{\n "floatValue": 1.00000075e-36\n}' - self.assertEqual( - json_format.MessageToJson(message), text) - message.float_value = 12345678912345e+11 - text = '{\n "floatValue": 1.234568e+24\n}' - self.assertEqual( - json_format.MessageToJson(message), text) - - # Test a bunch of data and check json encode/decode do not - # lose precision - value_list = [0x00, 0xD8, 0x6E, 0x00] - msg2 = json_format_proto3_pb2.TestMessage() - for a in range(0, 256): - value_list[3] = a - for b in range(0, 256): - value_list[0] = b - byte_array = bytearray(value_list) - message.float_value = struct.unpack('.", - json_format.ParseDict, {'value': UnknownClass()}, message) - - def testMessageToDict(self): - message = json_format_proto3_pb2.TestMessage() - message.int32_value = 12345 - expected = {'int32Value': 12345} - self.assertEqual(expected, - json_format.MessageToDict(message)) - - def testJsonName(self): - message = json_format_proto3_pb2.TestCustomJsonName() - message.value = 12345 - self.assertEqual('{\n "@value": 12345\n}', - json_format.MessageToJson(message)) - parsed_message = json_format_proto3_pb2.TestCustomJsonName() - self.CheckParseBack(message, parsed_message) - - def testSortKeys(self): - # Testing sort_keys is not perfectly working, as by random luck we could - # get the output sorted. We just use a selection of names. - message = json_format_proto3_pb2.TestMessage(bool_value=True, - int32_value=1, - int64_value=3, - uint32_value=4, - string_value='bla') - self.assertEqual( - json_format.MessageToJson(message, sort_keys=True), - # We use json.dumps() instead of a hardcoded string due to differences - # between Python 2 and Python 3. - json.dumps({'boolValue': True, 'int32Value': 1, 'int64Value': '3', - 'uint32Value': 4, 'stringValue': 'bla'}, - indent=2, sort_keys=True)) - - def testNestedRecursiveLimit(self): - message = unittest_pb2.NestedTestAllTypes() - self.assertRaisesRegex( - json_format.ParseError, - 'Message too deep. Max recursion depth is 3', - json_format.Parse, - '{"child": {"child": {"child" : {}}}}', - message, - max_recursion_depth=3) - # The following one can pass - json_format.Parse('{"payload": {}, "child": {"child":{}}}', - message, max_recursion_depth=3) - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/keywords_test.py b/ext/protobuf/Python/google/protobuf/internal/keywords_test.py deleted file mode 100644 index 4182cf6be..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/keywords_test.py +++ /dev/null @@ -1,103 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.internal.keywords.""" - -import unittest - - -from google.protobuf.internal import more_messages_pb2 -from google.protobuf import descriptor_pool - - -class KeywordsConflictTest(unittest.TestCase): - - def setUp(self): - super(KeywordsConflictTest, self).setUp() - self.pool = descriptor_pool.Default() - - def testMessage(self): - message = getattr(more_messages_pb2, 'class')() - message.int_field = 123 - self.assertEqual(message.int_field, 123) - des = self.pool.FindMessageTypeByName('google.protobuf.internal.class') - self.assertEqual(des.name, 'class') - - def testNestedMessage(self): - message = getattr(more_messages_pb2, 'class')() - message.nested_message.field = 234 - self.assertEqual(message.nested_message.field, 234) - des = self.pool.FindMessageTypeByName('google.protobuf.internal.class.try') - self.assertEqual(des.name, 'try') - - def testField(self): - message = getattr(more_messages_pb2, 'class')() - setattr(message, 'if', 123) - setattr(message, 'as', 1) - self.assertEqual(getattr(message, 'if'), 123) - self.assertEqual(getattr(message, 'as'), 1) - - def testEnum(self): - class_ = getattr(more_messages_pb2, 'class') - message = class_() - # Normal enum value. - message.enum_field = more_messages_pb2.default - self.assertEqual(message.enum_field, more_messages_pb2.default) - # Top level enum value. - message.enum_field = getattr(more_messages_pb2, 'else') - self.assertEqual(message.enum_field, 1) - # Nested enum value - message.nested_enum_field = getattr(class_, 'True') - self.assertEqual(message.nested_enum_field, 1) - - def testExtension(self): - message = getattr(more_messages_pb2, 'class')() - # Top level extension - extension1 = getattr(more_messages_pb2, 'continue') - message.Extensions[extension1] = 456 - self.assertEqual(message.Extensions[extension1], 456) - # None top level extension - extension2 = getattr(more_messages_pb2.ExtendClass, 'return') - message.Extensions[extension2] = 789 - self.assertEqual(message.Extensions[extension2], 789) - - def testExtensionForNestedMessage(self): - message = getattr(more_messages_pb2, 'class')() - extension = getattr(more_messages_pb2, 'with') - message.nested_message.Extensions[extension] = 999 - self.assertEqual(message.nested_message.Extensions[extension], 999) - - def TestFullKeywordUsed(self): - message = more_messages_pb2.TestFullKeyword() - message.field2.int_field = 123 - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/message_factory_test.py b/ext/protobuf/Python/google/protobuf/internal/message_factory_test.py deleted file mode 100644 index efba6194c..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/message_factory_test.py +++ /dev/null @@ -1,299 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.message_factory.""" - -__author__ = 'matthewtoia@google.com (Matt Toia)' - -import unittest - -from google.protobuf import descriptor_pb2 -from google.protobuf.internal import api_implementation -from google.protobuf.internal import factory_test1_pb2 -from google.protobuf.internal import factory_test2_pb2 -from google.protobuf.internal import testing_refleaks -from google.protobuf import descriptor_database -from google.protobuf import descriptor_pool -from google.protobuf import message_factory - - -@testing_refleaks.TestCase -class MessageFactoryTest(unittest.TestCase): - - def setUp(self): - self.factory_test1_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test1_pb2.DESCRIPTOR.serialized_pb) - self.factory_test2_fd = descriptor_pb2.FileDescriptorProto.FromString( - factory_test2_pb2.DESCRIPTOR.serialized_pb) - - def _ExerciseDynamicClass(self, cls): - msg = cls() - msg.mandatory = 42 - msg.nested_factory_2_enum = 0 - msg.nested_factory_2_message.value = 'nested message value' - msg.factory_1_message.factory_1_enum = 1 - msg.factory_1_message.nested_factory_1_enum = 0 - msg.factory_1_message.nested_factory_1_message.value = ( - 'nested message value') - msg.factory_1_message.scalar_value = 22 - msg.factory_1_message.list_value.extend([u'one', u'two', u'three']) - msg.factory_1_message.list_value.append(u'four') - msg.factory_1_enum = 1 - msg.nested_factory_1_enum = 0 - msg.nested_factory_1_message.value = 'nested message value' - msg.circular_message.mandatory = 1 - msg.circular_message.circular_message.mandatory = 2 - msg.circular_message.scalar_value = 'one deep' - msg.scalar_value = 'zero deep' - msg.list_value.extend([u'four', u'three', u'two']) - msg.list_value.append(u'one') - msg.grouped.add() - msg.grouped[0].part_1 = 'hello' - msg.grouped[0].part_2 = 'world' - msg.grouped.add(part_1='testing', part_2='123') - msg.loop.loop.mandatory = 2 - msg.loop.loop.loop.loop.mandatory = 4 - serialized = msg.SerializeToString() - converted = factory_test2_pb2.Factory2Message.FromString(serialized) - reserialized = converted.SerializeToString() - self.assertEqual(serialized, reserialized) - result = cls.FromString(reserialized) - self.assertEqual(msg, result) - - def testGetPrototype(self): - db = descriptor_database.DescriptorDatabase() - pool = descriptor_pool.DescriptorPool(db) - db.Add(self.factory_test1_fd) - db.Add(self.factory_test2_fd) - factory = message_factory.MessageFactory() - cls = factory.GetPrototype(pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message')) - self.assertFalse(cls is factory_test2_pb2.Factory2Message) - self._ExerciseDynamicClass(cls) - cls2 = factory.GetPrototype(pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message')) - self.assertTrue(cls is cls2) - - def testCreatePrototypeOverride(self): - class MyMessageFactory(message_factory.MessageFactory): - - def CreatePrototype(self, descriptor): - cls = super(MyMessageFactory, self).CreatePrototype(descriptor) - cls.additional_field = 'Some value' - return cls - - db = descriptor_database.DescriptorDatabase() - pool = descriptor_pool.DescriptorPool(db) - db.Add(self.factory_test1_fd) - db.Add(self.factory_test2_fd) - factory = MyMessageFactory() - cls = factory.GetPrototype(pool.FindMessageTypeByName( - 'google.protobuf.python.internal.Factory2Message')) - self.assertTrue(hasattr(cls, 'additional_field')) - - def testGetMessages(self): - # performed twice because multiple calls with the same input must be allowed - for _ in range(2): - # GetMessage should work regardless of the order the FileDescriptorProto - # are provided. In particular, the function should succeed when the files - # are not in the topological order of dependencies. - - # Assuming factory_test2_fd depends on factory_test1_fd. - self.assertIn(self.factory_test1_fd.name, - self.factory_test2_fd.dependency) - # Get messages should work when a file comes before its dependencies: - # factory_test2_fd comes before factory_test1_fd. - messages = message_factory.GetMessages([self.factory_test2_fd, - self.factory_test1_fd]) - self.assertTrue( - set(['google.protobuf.python.internal.Factory2Message', - 'google.protobuf.python.internal.Factory1Message'], - ).issubset(set(messages.keys()))) - self._ExerciseDynamicClass( - messages['google.protobuf.python.internal.Factory2Message']) - factory_msg1 = messages['google.protobuf.python.internal.Factory1Message'] - self.assertTrue(set( - ['google.protobuf.python.internal.Factory2Message.one_more_field', - 'google.protobuf.python.internal.another_field'],).issubset(set( - ext.full_name - for ext in factory_msg1.DESCRIPTOR.file.pool.FindAllExtensions( - factory_msg1.DESCRIPTOR)))) - msg1 = messages['google.protobuf.python.internal.Factory1Message']() - ext1 = msg1.Extensions._FindExtensionByName( - 'google.protobuf.python.internal.Factory2Message.one_more_field') - ext2 = msg1.Extensions._FindExtensionByName( - 'google.protobuf.python.internal.another_field') - self.assertEqual(0, len(msg1.Extensions)) - msg1.Extensions[ext1] = 'test1' - msg1.Extensions[ext2] = 'test2' - self.assertEqual('test1', msg1.Extensions[ext1]) - self.assertEqual('test2', msg1.Extensions[ext2]) - self.assertEqual(None, - msg1.Extensions._FindExtensionByNumber(12321)) - self.assertEqual(2, len(msg1.Extensions)) - if api_implementation.Type() == 'cpp': - self.assertRaises(TypeError, - msg1.Extensions._FindExtensionByName, 0) - self.assertRaises(TypeError, - msg1.Extensions._FindExtensionByNumber, '') - else: - self.assertEqual(None, - msg1.Extensions._FindExtensionByName(0)) - self.assertEqual(None, - msg1.Extensions._FindExtensionByNumber('')) - - def testDuplicateExtensionNumber(self): - pool = descriptor_pool.DescriptorPool() - factory = message_factory.MessageFactory(pool=pool) - - # Add Container message. - f = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/container.proto', - package='google.protobuf.python.internal') - f.message_type.add(name='Container').extension_range.add(start=1, end=10) - pool.Add(f) - msgs = factory.GetMessages([f.name]) - self.assertIn('google.protobuf.python.internal.Container', msgs) - - # Extend container. - f = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/extension.proto', - package='google.protobuf.python.internal', - dependency=['google/protobuf/internal/container.proto']) - msg = f.message_type.add(name='Extension') - msg.extension.add( - name='extension_field', - number=2, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type_name='Extension', - extendee='Container') - pool.Add(f) - msgs = factory.GetMessages([f.name]) - self.assertIn('google.protobuf.python.internal.Extension', msgs) - - # Add Duplicate extending the same field number. - f = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/duplicate.proto', - package='google.protobuf.python.internal', - dependency=['google/protobuf/internal/container.proto']) - msg = f.message_type.add(name='Duplicate') - msg.extension.add( - name='extension_field', - number=2, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type_name='Duplicate', - extendee='Container') - pool.Add(f) - - with self.assertRaises(Exception) as cm: - factory.GetMessages([f.name]) - - self.assertIn(str(cm.exception), - ['Extensions ' - '"google.protobuf.python.internal.Duplicate.extension_field" and' - ' "google.protobuf.python.internal.Extension.extension_field"' - ' both try to extend message type' - ' "google.protobuf.python.internal.Container"' - ' with field number 2.', - 'Double registration of Extensions']) - - def testExtensionValueInDifferentFile(self): - # Add Container message. - f1 = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/container.proto', - package='google.protobuf.python.internal') - f1.message_type.add(name='Container').extension_range.add(start=1, end=10) - - # Add ValueType message. - f2 = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/value_type.proto', - package='google.protobuf.python.internal') - f2.message_type.add(name='ValueType').field.add( - name='setting', - number=1, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type=descriptor_pb2.FieldDescriptorProto.TYPE_INT32, - default_value='123') - - # Extend container with field of ValueType. - f3 = descriptor_pb2.FileDescriptorProto( - name='google/protobuf/internal/extension.proto', - package='google.protobuf.python.internal', - dependency=[f1.name, f2.name]) - f3.extension.add( - name='top_level_extension_field', - number=2, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type_name='ValueType', - extendee='Container') - f3.message_type.add(name='Extension').extension.add( - name='nested_extension_field', - number=3, - label=descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL, - type_name='ValueType', - extendee='Container') - - class SimpleDescriptorDB: - - def __init__(self, files): - self._files = files - - def FindFileByName(self, name): - return self._files[name] - - db = SimpleDescriptorDB({f1.name: f1, f2.name: f2, f3.name: f3}) - - pool = descriptor_pool.DescriptorPool(db) - factory = message_factory.MessageFactory(pool=pool) - msgs = factory.GetMessages([f1.name, f3.name]) # Deliberately not f2. - msg = msgs['google.protobuf.python.internal.Container'] - desc = msgs['google.protobuf.python.internal.Extension'].DESCRIPTOR - ext1 = desc.file.extensions_by_name['top_level_extension_field'] - ext2 = desc.extensions_by_name['nested_extension_field'] - m = msg() - m.Extensions[ext1].setting = 234 - m.Extensions[ext2].setting = 345 - serialized = m.SerializeToString() - - pool = descriptor_pool.DescriptorPool(db) - factory = message_factory.MessageFactory(pool=pool) - msgs = factory.GetMessages([f1.name, f3.name]) # Deliberately not f2. - msg = msgs['google.protobuf.python.internal.Container'] - desc = msgs['google.protobuf.python.internal.Extension'].DESCRIPTOR - ext1 = desc.file.extensions_by_name['top_level_extension_field'] - ext2 = desc.extensions_by_name['nested_extension_field'] - m = msg.FromString(serialized) - self.assertEqual(2, len(m.ListFields())) - self.assertEqual(234, m.Extensions[ext1].setting) - self.assertEqual(345, m.Extensions[ext2].setting) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/message_test.py b/ext/protobuf/Python/google/protobuf/internal/message_test.py deleted file mode 100644 index 40abfe4a2..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/message_test.py +++ /dev/null @@ -1,2572 +0,0 @@ -# -*- coding: utf-8 -*- -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests python protocol buffers against the golden message. - -Note that the golden messages exercise every known field type, thus this -test ends up exercising and verifying nearly all of the parsing and -serialization code in the whole library. - -TODO(kenton): Merge with wire_format_test? It doesn't make a whole lot of -sense to call this a test of the "message" module, which only declares an -abstract interface. -""" - -__author__ = 'gps@google.com (Gregory P. Smith)' - -import collections -import copy -import math -import operator -import pickle -import pydoc -import sys -import unittest -import warnings - -cmp = lambda x, y: (x > y) - (x < y) - -from google.protobuf import map_proto2_unittest_pb2 -from google.protobuf import map_unittest_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import unittest_proto3_arena_pb2 -from google.protobuf import descriptor -from google.protobuf.internal import api_implementation -from google.protobuf.internal import encoder -from google.protobuf.internal import more_extensions_pb2 -from google.protobuf.internal import packed_field_test_pb2 -from google.protobuf.internal import test_util -from google.protobuf.internal import test_proto3_optional_pb2 -from google.protobuf.internal import testing_refleaks -from google.protobuf import message -from google.protobuf.internal import _parameterized - -UCS2_MAXUNICODE = 65535 - -warnings.simplefilter('error', DeprecationWarning) - - -@_parameterized.named_parameters(('_proto2', unittest_pb2), - ('_proto3', unittest_proto3_arena_pb2)) -@testing_refleaks.TestCase -class MessageTest(unittest.TestCase): - - def testBadUtf8String(self, message_module): - if api_implementation.Type() != 'python': - self.skipTest('Skipping testBadUtf8String, currently only the python ' - 'api implementation raises UnicodeDecodeError when a ' - 'string field contains bad utf-8.') - bad_utf8_data = test_util.GoldenFileData('bad_utf8_string') - with self.assertRaises(UnicodeDecodeError) as context: - message_module.TestAllTypes.FromString(bad_utf8_data) - self.assertIn('TestAllTypes.optional_string', str(context.exception)) - - def testGoldenMessage(self, message_module): - # Proto3 doesn't have the "default_foo" members or foreign enums, - # and doesn't preserve unknown fields, so for proto3 we use a golden - # message that doesn't have these fields set. - if message_module is unittest_pb2: - golden_data = test_util.GoldenFileData('golden_message_oneof_implemented') - else: - golden_data = test_util.GoldenFileData('golden_message_proto3') - - golden_message = message_module.TestAllTypes() - golden_message.ParseFromString(golden_data) - if message_module is unittest_pb2: - test_util.ExpectAllFieldsSet(self, golden_message) - self.assertEqual(golden_data, golden_message.SerializeToString()) - golden_copy = copy.deepcopy(golden_message) - self.assertEqual(golden_data, golden_copy.SerializeToString()) - - def testGoldenPackedMessage(self, message_module): - golden_data = test_util.GoldenFileData('golden_packed_fields_message') - golden_message = message_module.TestPackedTypes() - parsed_bytes = golden_message.ParseFromString(golden_data) - all_set = message_module.TestPackedTypes() - test_util.SetAllPackedFields(all_set) - self.assertEqual(parsed_bytes, len(golden_data)) - self.assertEqual(all_set, golden_message) - self.assertEqual(golden_data, all_set.SerializeToString()) - golden_copy = copy.deepcopy(golden_message) - self.assertEqual(golden_data, golden_copy.SerializeToString()) - - def testParseErrors(self, message_module): - msg = message_module.TestAllTypes() - self.assertRaises(TypeError, msg.FromString, 0) - self.assertRaises(Exception, msg.FromString, '0') - # TODO(jieluo): Fix cpp extension to raise error instead of warning. - # b/27494216 - end_tag = encoder.TagBytes(1, 4) - if (api_implementation.Type() == 'python' or - api_implementation.Type() == 'upb'): - with self.assertRaises(message.DecodeError) as context: - msg.FromString(end_tag) - if api_implementation.Type() == 'python': - # Only pure-Python has an error message this specific. - self.assertEqual('Unexpected end-group tag.', str(context.exception)) - - # Field number 0 is illegal. - self.assertRaises(message.DecodeError, msg.FromString, b'\3\4') - - def testDeterminismParameters(self, message_module): - # This message is always deterministically serialized, even if determinism - # is disabled, so we can use it to verify that all the determinism - # parameters work correctly. - golden_data = (b'\xe2\x02\nOne string' - b'\xe2\x02\nTwo string' - b'\xe2\x02\nRed string' - b'\xe2\x02\x0bBlue string') - golden_message = message_module.TestAllTypes() - golden_message.repeated_string.extend([ - 'One string', - 'Two string', - 'Red string', - 'Blue string', - ]) - self.assertEqual(golden_data, - golden_message.SerializeToString(deterministic=None)) - self.assertEqual(golden_data, - golden_message.SerializeToString(deterministic=False)) - self.assertEqual(golden_data, - golden_message.SerializeToString(deterministic=True)) - - class BadArgError(Exception): - pass - - class BadArg(object): - - def __nonzero__(self): - raise BadArgError() - - def __bool__(self): - raise BadArgError() - - with self.assertRaises(BadArgError): - golden_message.SerializeToString(deterministic=BadArg()) - - def testPickleSupport(self, message_module): - golden_data = test_util.GoldenFileData('golden_message') - golden_message = message_module.TestAllTypes() - golden_message.ParseFromString(golden_data) - pickled_message = pickle.dumps(golden_message) - - unpickled_message = pickle.loads(pickled_message) - self.assertEqual(unpickled_message, golden_message) - - def testPickleNestedMessage(self, message_module): - golden_message = message_module.TestPickleNestedMessage.NestedMessage(bb=1) - pickled_message = pickle.dumps(golden_message) - unpickled_message = pickle.loads(pickled_message) - self.assertEqual(unpickled_message, golden_message) - - def testPickleNestedNestedMessage(self, message_module): - cls = message_module.TestPickleNestedMessage.NestedMessage - golden_message = cls.NestedNestedMessage(cc=1) - pickled_message = pickle.dumps(golden_message) - unpickled_message = pickle.loads(pickled_message) - self.assertEqual(unpickled_message, golden_message) - - def testPositiveInfinity(self, message_module): - if message_module is unittest_pb2: - golden_data = (b'\x5D\x00\x00\x80\x7F' - b'\x61\x00\x00\x00\x00\x00\x00\xF0\x7F' - b'\xCD\x02\x00\x00\x80\x7F' - b'\xD1\x02\x00\x00\x00\x00\x00\x00\xF0\x7F') - else: - golden_data = (b'\x5D\x00\x00\x80\x7F' - b'\x61\x00\x00\x00\x00\x00\x00\xF0\x7F' - b'\xCA\x02\x04\x00\x00\x80\x7F' - b'\xD2\x02\x08\x00\x00\x00\x00\x00\x00\xF0\x7F') - - golden_message = message_module.TestAllTypes() - golden_message.ParseFromString(golden_data) - self.assertEqual(golden_message.optional_float, math.inf) - self.assertEqual(golden_message.optional_double, math.inf) - self.assertEqual(golden_message.repeated_float[0], math.inf) - self.assertEqual(golden_message.repeated_double[0], math.inf) - self.assertEqual(golden_data, golden_message.SerializeToString()) - - def testNegativeInfinity(self, message_module): - if message_module is unittest_pb2: - golden_data = (b'\x5D\x00\x00\x80\xFF' - b'\x61\x00\x00\x00\x00\x00\x00\xF0\xFF' - b'\xCD\x02\x00\x00\x80\xFF' - b'\xD1\x02\x00\x00\x00\x00\x00\x00\xF0\xFF') - else: - golden_data = (b'\x5D\x00\x00\x80\xFF' - b'\x61\x00\x00\x00\x00\x00\x00\xF0\xFF' - b'\xCA\x02\x04\x00\x00\x80\xFF' - b'\xD2\x02\x08\x00\x00\x00\x00\x00\x00\xF0\xFF') - - golden_message = message_module.TestAllTypes() - golden_message.ParseFromString(golden_data) - self.assertEqual(golden_message.optional_float, -math.inf) - self.assertEqual(golden_message.optional_double, -math.inf) - self.assertEqual(golden_message.repeated_float[0], -math.inf) - self.assertEqual(golden_message.repeated_double[0], -math.inf) - self.assertEqual(golden_data, golden_message.SerializeToString()) - - def testNotANumber(self, message_module): - golden_data = (b'\x5D\x00\x00\xC0\x7F' - b'\x61\x00\x00\x00\x00\x00\x00\xF8\x7F' - b'\xCD\x02\x00\x00\xC0\x7F' - b'\xD1\x02\x00\x00\x00\x00\x00\x00\xF8\x7F') - golden_message = message_module.TestAllTypes() - golden_message.ParseFromString(golden_data) - self.assertTrue(math.isnan(golden_message.optional_float)) - self.assertTrue(math.isnan(golden_message.optional_double)) - self.assertTrue(math.isnan(golden_message.repeated_float[0])) - self.assertTrue(math.isnan(golden_message.repeated_double[0])) - - # The protocol buffer may serialize to any one of multiple different - # representations of a NaN. Rather than verify a specific representation, - # verify the serialized string can be converted into a correctly - # behaving protocol buffer. - serialized = golden_message.SerializeToString() - message = message_module.TestAllTypes() - message.ParseFromString(serialized) - self.assertTrue(math.isnan(message.optional_float)) - self.assertTrue(math.isnan(message.optional_double)) - self.assertTrue(math.isnan(message.repeated_float[0])) - self.assertTrue(math.isnan(message.repeated_double[0])) - - def testPositiveInfinityPacked(self, message_module): - golden_data = (b'\xA2\x06\x04\x00\x00\x80\x7F' - b'\xAA\x06\x08\x00\x00\x00\x00\x00\x00\xF0\x7F') - golden_message = message_module.TestPackedTypes() - golden_message.ParseFromString(golden_data) - self.assertEqual(golden_message.packed_float[0], math.inf) - self.assertEqual(golden_message.packed_double[0], math.inf) - self.assertEqual(golden_data, golden_message.SerializeToString()) - - def testNegativeInfinityPacked(self, message_module): - golden_data = (b'\xA2\x06\x04\x00\x00\x80\xFF' - b'\xAA\x06\x08\x00\x00\x00\x00\x00\x00\xF0\xFF') - golden_message = message_module.TestPackedTypes() - golden_message.ParseFromString(golden_data) - self.assertEqual(golden_message.packed_float[0], -math.inf) - self.assertEqual(golden_message.packed_double[0], -math.inf) - self.assertEqual(golden_data, golden_message.SerializeToString()) - - def testNotANumberPacked(self, message_module): - golden_data = (b'\xA2\x06\x04\x00\x00\xC0\x7F' - b'\xAA\x06\x08\x00\x00\x00\x00\x00\x00\xF8\x7F') - golden_message = message_module.TestPackedTypes() - golden_message.ParseFromString(golden_data) - self.assertTrue(math.isnan(golden_message.packed_float[0])) - self.assertTrue(math.isnan(golden_message.packed_double[0])) - - serialized = golden_message.SerializeToString() - message = message_module.TestPackedTypes() - message.ParseFromString(serialized) - self.assertTrue(math.isnan(message.packed_float[0])) - self.assertTrue(math.isnan(message.packed_double[0])) - - def testExtremeFloatValues(self, message_module): - message = message_module.TestAllTypes() - - # Most positive exponent, no significand bits set. - kMostPosExponentNoSigBits = math.pow(2, 127) - message.optional_float = kMostPosExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == kMostPosExponentNoSigBits) - - # Most positive exponent, one significand bit set. - kMostPosExponentOneSigBit = 1.5 * math.pow(2, 127) - message.optional_float = kMostPosExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == kMostPosExponentOneSigBit) - - # Repeat last two cases with values of same magnitude, but negative. - message.optional_float = -kMostPosExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == -kMostPosExponentNoSigBits) - - message.optional_float = -kMostPosExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == -kMostPosExponentOneSigBit) - - # Most negative exponent, no significand bits set. - kMostNegExponentNoSigBits = math.pow(2, -127) - message.optional_float = kMostNegExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == kMostNegExponentNoSigBits) - - # Most negative exponent, one significand bit set. - kMostNegExponentOneSigBit = 1.5 * math.pow(2, -127) - message.optional_float = kMostNegExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == kMostNegExponentOneSigBit) - - # Repeat last two cases with values of the same magnitude, but negative. - message.optional_float = -kMostNegExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == -kMostNegExponentNoSigBits) - - message.optional_float = -kMostNegExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_float == -kMostNegExponentOneSigBit) - - # Max 4 bytes float value - max_float = float.fromhex('0x1.fffffep+127') - message.optional_float = max_float - self.assertAlmostEqual(message.optional_float, max_float) - serialized_data = message.SerializeToString() - message.ParseFromString(serialized_data) - self.assertAlmostEqual(message.optional_float, max_float) - - # Test set double to float field. - message.optional_float = 3.4028235e+39 - self.assertEqual(message.optional_float, float('inf')) - serialized_data = message.SerializeToString() - message.ParseFromString(serialized_data) - self.assertEqual(message.optional_float, float('inf')) - - message.optional_float = -3.4028235e+39 - self.assertEqual(message.optional_float, float('-inf')) - - message.optional_float = 1.4028235e-39 - self.assertAlmostEqual(message.optional_float, 1.4028235e-39) - - def testExtremeDoubleValues(self, message_module): - message = message_module.TestAllTypes() - - # Most positive exponent, no significand bits set. - kMostPosExponentNoSigBits = math.pow(2, 1023) - message.optional_double = kMostPosExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == kMostPosExponentNoSigBits) - - # Most positive exponent, one significand bit set. - kMostPosExponentOneSigBit = 1.5 * math.pow(2, 1023) - message.optional_double = kMostPosExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == kMostPosExponentOneSigBit) - - # Repeat last two cases with values of same magnitude, but negative. - message.optional_double = -kMostPosExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == -kMostPosExponentNoSigBits) - - message.optional_double = -kMostPosExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == -kMostPosExponentOneSigBit) - - # Most negative exponent, no significand bits set. - kMostNegExponentNoSigBits = math.pow(2, -1023) - message.optional_double = kMostNegExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == kMostNegExponentNoSigBits) - - # Most negative exponent, one significand bit set. - kMostNegExponentOneSigBit = 1.5 * math.pow(2, -1023) - message.optional_double = kMostNegExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == kMostNegExponentOneSigBit) - - # Repeat last two cases with values of the same magnitude, but negative. - message.optional_double = -kMostNegExponentNoSigBits - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == -kMostNegExponentNoSigBits) - - message.optional_double = -kMostNegExponentOneSigBit - message.ParseFromString(message.SerializeToString()) - self.assertTrue(message.optional_double == -kMostNegExponentOneSigBit) - - def testFloatPrinting(self, message_module): - message = message_module.TestAllTypes() - message.optional_float = 2.0 - self.assertEqual(str(message), 'optional_float: 2.0\n') - - def testHighPrecisionFloatPrinting(self, message_module): - msg = message_module.TestAllTypes() - msg.optional_float = 0.12345678912345678 - old_float = msg.optional_float - msg.ParseFromString(msg.SerializeToString()) - self.assertEqual(old_float, msg.optional_float) - - def testHighPrecisionDoublePrinting(self, message_module): - msg = message_module.TestAllTypes() - msg.optional_double = 0.12345678912345678 - self.assertEqual(str(msg), 'optional_double: 0.12345678912345678\n') - - def testUnknownFieldPrinting(self, message_module): - populated = message_module.TestAllTypes() - test_util.SetAllNonLazyFields(populated) - empty = message_module.TestEmptyMessage() - empty.ParseFromString(populated.SerializeToString()) - self.assertEqual(str(empty), '') - - def testAppendRepeatedCompositeField(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_nested_message.append( - message_module.TestAllTypes.NestedMessage(bb=1)) - nested = message_module.TestAllTypes.NestedMessage(bb=2) - msg.repeated_nested_message.append(nested) - try: - msg.repeated_nested_message.append(1) - except TypeError: - pass - self.assertEqual(2, len(msg.repeated_nested_message)) - self.assertEqual([1, 2], [m.bb for m in msg.repeated_nested_message]) - - def testInsertRepeatedCompositeField(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_nested_message.insert( - -1, message_module.TestAllTypes.NestedMessage(bb=1)) - sub_msg = msg.repeated_nested_message[0] - msg.repeated_nested_message.insert( - 0, message_module.TestAllTypes.NestedMessage(bb=2)) - msg.repeated_nested_message.insert( - 99, message_module.TestAllTypes.NestedMessage(bb=3)) - msg.repeated_nested_message.insert( - -2, message_module.TestAllTypes.NestedMessage(bb=-1)) - msg.repeated_nested_message.insert( - -1000, message_module.TestAllTypes.NestedMessage(bb=-1000)) - try: - msg.repeated_nested_message.insert(1, 999) - except TypeError: - pass - self.assertEqual(5, len(msg.repeated_nested_message)) - self.assertEqual([-1000, 2, -1, 1, 3], - [m.bb for m in msg.repeated_nested_message]) - self.assertEqual( - str(msg), 'repeated_nested_message {\n' - ' bb: -1000\n' - '}\n' - 'repeated_nested_message {\n' - ' bb: 2\n' - '}\n' - 'repeated_nested_message {\n' - ' bb: -1\n' - '}\n' - 'repeated_nested_message {\n' - ' bb: 1\n' - '}\n' - 'repeated_nested_message {\n' - ' bb: 3\n' - '}\n') - self.assertEqual(sub_msg.bb, 1) - - def testMergeFromRepeatedField(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.append(1) - msg.repeated_int32.append(3) - msg.repeated_nested_message.add(bb=1) - msg.repeated_nested_message.add(bb=2) - other_msg = message_module.TestAllTypes() - other_msg.repeated_nested_message.add(bb=3) - other_msg.repeated_nested_message.add(bb=4) - other_msg.repeated_int32.append(5) - other_msg.repeated_int32.append(7) - - msg.repeated_int32.MergeFrom(other_msg.repeated_int32) - self.assertEqual(4, len(msg.repeated_int32)) - - msg.repeated_nested_message.MergeFrom(other_msg.repeated_nested_message) - self.assertEqual([1, 2, 3, 4], [m.bb for m in msg.repeated_nested_message]) - - def testAddWrongRepeatedNestedField(self, message_module): - msg = message_module.TestAllTypes() - try: - msg.repeated_nested_message.add('wrong') - except TypeError: - pass - try: - msg.repeated_nested_message.add(value_field='wrong') - except ValueError: - pass - self.assertEqual(len(msg.repeated_nested_message), 0) - - def testRepeatedContains(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.extend([1, 2, 3]) - self.assertIn(2, msg.repeated_int32) - self.assertNotIn(0, msg.repeated_int32) - - msg.repeated_nested_message.add(bb=1) - sub_msg1 = msg.repeated_nested_message[0] - sub_msg2 = message_module.TestAllTypes.NestedMessage(bb=2) - sub_msg3 = message_module.TestAllTypes.NestedMessage(bb=3) - msg.repeated_nested_message.append(sub_msg2) - msg.repeated_nested_message.insert(0, sub_msg3) - self.assertIn(sub_msg1, msg.repeated_nested_message) - self.assertIn(sub_msg2, msg.repeated_nested_message) - self.assertIn(sub_msg3, msg.repeated_nested_message) - - def testRepeatedScalarIterable(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.extend([1, 2, 3]) - add = 0 - for item in msg.repeated_int32: - add += item - self.assertEqual(add, 6) - - def testRepeatedNestedFieldIteration(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_nested_message.add(bb=1) - msg.repeated_nested_message.add(bb=2) - msg.repeated_nested_message.add(bb=3) - msg.repeated_nested_message.add(bb=4) - - self.assertEqual([1, 2, 3, 4], [m.bb for m in msg.repeated_nested_message]) - self.assertEqual([4, 3, 2, 1], - [m.bb for m in reversed(msg.repeated_nested_message)]) - self.assertEqual([4, 3, 2, 1], - [m.bb for m in msg.repeated_nested_message[::-1]]) - - def testSortingRepeatedScalarFieldsDefaultComparator(self, message_module): - """Check some different types with the default comparator.""" - message = message_module.TestAllTypes() - - # TODO(mattp): would testing more scalar types strengthen test? - message.repeated_int32.append(1) - message.repeated_int32.append(3) - message.repeated_int32.append(2) - message.repeated_int32.sort() - self.assertEqual(message.repeated_int32[0], 1) - self.assertEqual(message.repeated_int32[1], 2) - self.assertEqual(message.repeated_int32[2], 3) - self.assertEqual(str(message.repeated_int32), str([1, 2, 3])) - - message.repeated_float.append(1.1) - message.repeated_float.append(1.3) - message.repeated_float.append(1.2) - message.repeated_float.sort() - self.assertAlmostEqual(message.repeated_float[0], 1.1) - self.assertAlmostEqual(message.repeated_float[1], 1.2) - self.assertAlmostEqual(message.repeated_float[2], 1.3) - - message.repeated_string.append('a') - message.repeated_string.append('c') - message.repeated_string.append('b') - message.repeated_string.sort() - self.assertEqual(message.repeated_string[0], 'a') - self.assertEqual(message.repeated_string[1], 'b') - self.assertEqual(message.repeated_string[2], 'c') - self.assertEqual(str(message.repeated_string), str([u'a', u'b', u'c'])) - - message.repeated_bytes.append(b'a') - message.repeated_bytes.append(b'c') - message.repeated_bytes.append(b'b') - message.repeated_bytes.sort() - self.assertEqual(message.repeated_bytes[0], b'a') - self.assertEqual(message.repeated_bytes[1], b'b') - self.assertEqual(message.repeated_bytes[2], b'c') - self.assertEqual(str(message.repeated_bytes), str([b'a', b'b', b'c'])) - - def testSortingRepeatedScalarFieldsCustomComparator(self, message_module): - """Check some different types with custom comparator.""" - message = message_module.TestAllTypes() - - message.repeated_int32.append(-3) - message.repeated_int32.append(-2) - message.repeated_int32.append(-1) - message.repeated_int32.sort(key=abs) - self.assertEqual(message.repeated_int32[0], -1) - self.assertEqual(message.repeated_int32[1], -2) - self.assertEqual(message.repeated_int32[2], -3) - - message.repeated_string.append('aaa') - message.repeated_string.append('bb') - message.repeated_string.append('c') - message.repeated_string.sort(key=len) - self.assertEqual(message.repeated_string[0], 'c') - self.assertEqual(message.repeated_string[1], 'bb') - self.assertEqual(message.repeated_string[2], 'aaa') - - def testSortingRepeatedCompositeFieldsCustomComparator(self, message_module): - """Check passing a custom comparator to sort a repeated composite field.""" - message = message_module.TestAllTypes() - - message.repeated_nested_message.add().bb = 1 - message.repeated_nested_message.add().bb = 3 - message.repeated_nested_message.add().bb = 2 - message.repeated_nested_message.add().bb = 6 - message.repeated_nested_message.add().bb = 5 - message.repeated_nested_message.add().bb = 4 - message.repeated_nested_message.sort(key=operator.attrgetter('bb')) - self.assertEqual(message.repeated_nested_message[0].bb, 1) - self.assertEqual(message.repeated_nested_message[1].bb, 2) - self.assertEqual(message.repeated_nested_message[2].bb, 3) - self.assertEqual(message.repeated_nested_message[3].bb, 4) - self.assertEqual(message.repeated_nested_message[4].bb, 5) - self.assertEqual(message.repeated_nested_message[5].bb, 6) - self.assertEqual( - str(message.repeated_nested_message), - '[bb: 1\n, bb: 2\n, bb: 3\n, bb: 4\n, bb: 5\n, bb: 6\n]') - - def testSortingRepeatedCompositeFieldsStable(self, message_module): - """Check passing a custom comparator to sort a repeated composite field.""" - message = message_module.TestAllTypes() - - message.repeated_nested_message.add().bb = 21 - message.repeated_nested_message.add().bb = 20 - message.repeated_nested_message.add().bb = 13 - message.repeated_nested_message.add().bb = 33 - message.repeated_nested_message.add().bb = 11 - message.repeated_nested_message.add().bb = 24 - message.repeated_nested_message.add().bb = 10 - message.repeated_nested_message.sort(key=lambda z: z.bb // 10) - self.assertEqual([13, 11, 10, 21, 20, 24, 33], - [n.bb for n in message.repeated_nested_message]) - - # Make sure that for the C++ implementation, the underlying fields - # are actually reordered. - pb = message.SerializeToString() - message.Clear() - message.MergeFromString(pb) - self.assertEqual([13, 11, 10, 21, 20, 24, 33], - [n.bb for n in message.repeated_nested_message]) - - def testRepeatedCompositeFieldSortArguments(self, message_module): - """Check sorting a repeated composite field using list.sort() arguments.""" - message = message_module.TestAllTypes() - - get_bb = operator.attrgetter('bb') - message.repeated_nested_message.add().bb = 1 - message.repeated_nested_message.add().bb = 3 - message.repeated_nested_message.add().bb = 2 - message.repeated_nested_message.add().bb = 6 - message.repeated_nested_message.add().bb = 5 - message.repeated_nested_message.add().bb = 4 - message.repeated_nested_message.sort(key=get_bb) - self.assertEqual([k.bb for k in message.repeated_nested_message], - [1, 2, 3, 4, 5, 6]) - message.repeated_nested_message.sort(key=get_bb, reverse=True) - self.assertEqual([k.bb for k in message.repeated_nested_message], - [6, 5, 4, 3, 2, 1]) - - def testRepeatedScalarFieldSortArguments(self, message_module): - """Check sorting a scalar field using list.sort() arguments.""" - message = message_module.TestAllTypes() - - message.repeated_int32.append(-3) - message.repeated_int32.append(-2) - message.repeated_int32.append(-1) - message.repeated_int32.sort(key=abs) - self.assertEqual(list(message.repeated_int32), [-1, -2, -3]) - message.repeated_int32.sort(key=abs, reverse=True) - self.assertEqual(list(message.repeated_int32), [-3, -2, -1]) - - message.repeated_string.append('aaa') - message.repeated_string.append('bb') - message.repeated_string.append('c') - message.repeated_string.sort(key=len) - self.assertEqual(list(message.repeated_string), ['c', 'bb', 'aaa']) - message.repeated_string.sort(key=len, reverse=True) - self.assertEqual(list(message.repeated_string), ['aaa', 'bb', 'c']) - - def testRepeatedFieldsComparable(self, message_module): - m1 = message_module.TestAllTypes() - m2 = message_module.TestAllTypes() - m1.repeated_int32.append(0) - m1.repeated_int32.append(1) - m1.repeated_int32.append(2) - m2.repeated_int32.append(0) - m2.repeated_int32.append(1) - m2.repeated_int32.append(2) - m1.repeated_nested_message.add().bb = 1 - m1.repeated_nested_message.add().bb = 2 - m1.repeated_nested_message.add().bb = 3 - m2.repeated_nested_message.add().bb = 1 - m2.repeated_nested_message.add().bb = 2 - m2.repeated_nested_message.add().bb = 3 - - def testRepeatedFieldsAreSequences(self, message_module): - m = message_module.TestAllTypes() - self.assertIsInstance(m.repeated_int32, collections.abc.MutableSequence) - self.assertIsInstance(m.repeated_nested_message, - collections.abc.MutableSequence) - - def testRepeatedFieldsNotHashable(self, message_module): - m = message_module.TestAllTypes() - with self.assertRaises(TypeError): - hash(m.repeated_int32) - with self.assertRaises(TypeError): - hash(m.repeated_nested_message) - - def testRepeatedFieldInsideNestedMessage(self, message_module): - m = message_module.NestedTestAllTypes() - m.payload.repeated_int32.extend([]) - self.assertTrue(m.HasField('payload')) - - def testMergeFrom(self, message_module): - m1 = message_module.TestAllTypes() - m2 = message_module.TestAllTypes() - # Cpp extension will lazily create a sub message which is immutable. - nested = m1.optional_nested_message - self.assertEqual(0, nested.bb) - m2.optional_nested_message.bb = 1 - # Make sure cmessage pointing to a mutable message after merge instead of - # the lazily created message. - m1.MergeFrom(m2) - self.assertEqual(1, nested.bb) - - # Test more nested sub message. - msg1 = message_module.NestedTestAllTypes() - msg2 = message_module.NestedTestAllTypes() - nested = msg1.child.payload.optional_nested_message - self.assertEqual(0, nested.bb) - msg2.child.payload.optional_nested_message.bb = 1 - msg1.MergeFrom(msg2) - self.assertEqual(1, nested.bb) - - # Test repeated field. - self.assertEqual(msg1.payload.repeated_nested_message, - msg1.payload.repeated_nested_message) - nested = msg2.payload.repeated_nested_message.add() - nested.bb = 1 - msg1.MergeFrom(msg2) - self.assertEqual(1, len(msg1.payload.repeated_nested_message)) - self.assertEqual(1, nested.bb) - - def testMergeFromString(self, message_module): - m1 = message_module.TestAllTypes() - m2 = message_module.TestAllTypes() - # Cpp extension will lazily create a sub message which is immutable. - self.assertEqual(0, m1.optional_nested_message.bb) - m2.optional_nested_message.bb = 1 - # Make sure cmessage pointing to a mutable message after merge instead of - # the lazily created message. - m1.MergeFromString(m2.SerializeToString()) - self.assertEqual(1, m1.optional_nested_message.bb) - - def testMergeFromStringUsingMemoryView(self, message_module): - m2 = message_module.TestAllTypes() - m2.optional_string = 'scalar string' - m2.repeated_string.append('repeated string') - m2.optional_bytes = b'scalar bytes' - m2.repeated_bytes.append(b'repeated bytes') - - serialized = m2.SerializeToString() - memview = memoryview(serialized) - m1 = message_module.TestAllTypes.FromString(memview) - - self.assertEqual(m1.optional_bytes, b'scalar bytes') - self.assertEqual(m1.repeated_bytes, [b'repeated bytes']) - self.assertEqual(m1.optional_string, 'scalar string') - self.assertEqual(m1.repeated_string, ['repeated string']) - # Make sure that the memoryview was correctly converted to bytes, and - # that a sub-sliced memoryview is not being used. - self.assertIsInstance(m1.optional_bytes, bytes) - self.assertIsInstance(m1.repeated_bytes[0], bytes) - self.assertIsInstance(m1.optional_string, str) - self.assertIsInstance(m1.repeated_string[0], str) - - def testMergeFromEmpty(self, message_module): - m1 = message_module.TestAllTypes() - # Cpp extension will lazily create a sub message which is immutable. - self.assertEqual(0, m1.optional_nested_message.bb) - self.assertFalse(m1.HasField('optional_nested_message')) - # Make sure the sub message is still immutable after merge from empty. - m1.MergeFromString(b'') # field state should not change - self.assertFalse(m1.HasField('optional_nested_message')) - - def ensureNestedMessageExists(self, msg, attribute): - """Make sure that a nested message object exists. - - As soon as a nested message attribute is accessed, it will be present in the - _fields dict, without being marked as actually being set. - """ - getattr(msg, attribute) - self.assertFalse(msg.HasField(attribute)) - - def testOneofGetCaseNonexistingField(self, message_module): - m = message_module.TestAllTypes() - self.assertRaises(ValueError, m.WhichOneof, 'no_such_oneof_field') - self.assertRaises(Exception, m.WhichOneof, 0) - - def testOneofDefaultValues(self, message_module): - m = message_module.TestAllTypes() - self.assertIs(None, m.WhichOneof('oneof_field')) - self.assertFalse(m.HasField('oneof_field')) - self.assertFalse(m.HasField('oneof_uint32')) - - # Oneof is set even when setting it to a default value. - m.oneof_uint32 = 0 - self.assertEqual('oneof_uint32', m.WhichOneof('oneof_field')) - self.assertTrue(m.HasField('oneof_field')) - self.assertTrue(m.HasField('oneof_uint32')) - self.assertFalse(m.HasField('oneof_string')) - - m.oneof_string = '' - self.assertEqual('oneof_string', m.WhichOneof('oneof_field')) - self.assertTrue(m.HasField('oneof_string')) - self.assertFalse(m.HasField('oneof_uint32')) - - def testOneofSemantics(self, message_module): - m = message_module.TestAllTypes() - self.assertIs(None, m.WhichOneof('oneof_field')) - - m.oneof_uint32 = 11 - self.assertEqual('oneof_uint32', m.WhichOneof('oneof_field')) - self.assertTrue(m.HasField('oneof_uint32')) - - m.oneof_string = u'foo' - self.assertEqual('oneof_string', m.WhichOneof('oneof_field')) - self.assertFalse(m.HasField('oneof_uint32')) - self.assertTrue(m.HasField('oneof_string')) - - # Read nested message accessor without accessing submessage. - m.oneof_nested_message - self.assertEqual('oneof_string', m.WhichOneof('oneof_field')) - self.assertTrue(m.HasField('oneof_string')) - self.assertFalse(m.HasField('oneof_nested_message')) - - # Read accessor of nested message without accessing submessage. - m.oneof_nested_message.bb - self.assertEqual('oneof_string', m.WhichOneof('oneof_field')) - self.assertTrue(m.HasField('oneof_string')) - self.assertFalse(m.HasField('oneof_nested_message')) - - m.oneof_nested_message.bb = 11 - self.assertEqual('oneof_nested_message', m.WhichOneof('oneof_field')) - self.assertFalse(m.HasField('oneof_string')) - self.assertTrue(m.HasField('oneof_nested_message')) - - m.oneof_bytes = b'bb' - self.assertEqual('oneof_bytes', m.WhichOneof('oneof_field')) - self.assertFalse(m.HasField('oneof_nested_message')) - self.assertTrue(m.HasField('oneof_bytes')) - - def testOneofCompositeFieldReadAccess(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - - self.ensureNestedMessageExists(m, 'oneof_nested_message') - self.assertEqual('oneof_uint32', m.WhichOneof('oneof_field')) - self.assertEqual(11, m.oneof_uint32) - - def testOneofWhichOneof(self, message_module): - m = message_module.TestAllTypes() - self.assertIs(None, m.WhichOneof('oneof_field')) - if message_module is unittest_pb2: - self.assertFalse(m.HasField('oneof_field')) - - m.oneof_uint32 = 11 - self.assertEqual('oneof_uint32', m.WhichOneof('oneof_field')) - if message_module is unittest_pb2: - self.assertTrue(m.HasField('oneof_field')) - - m.oneof_bytes = b'bb' - self.assertEqual('oneof_bytes', m.WhichOneof('oneof_field')) - - m.ClearField('oneof_bytes') - self.assertIs(None, m.WhichOneof('oneof_field')) - if message_module is unittest_pb2: - self.assertFalse(m.HasField('oneof_field')) - - def testOneofClearField(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m.ClearField('oneof_field') - if message_module is unittest_pb2: - self.assertFalse(m.HasField('oneof_field')) - self.assertFalse(m.HasField('oneof_uint32')) - self.assertIs(None, m.WhichOneof('oneof_field')) - - def testOneofClearSetField(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m.ClearField('oneof_uint32') - if message_module is unittest_pb2: - self.assertFalse(m.HasField('oneof_field')) - self.assertFalse(m.HasField('oneof_uint32')) - self.assertIs(None, m.WhichOneof('oneof_field')) - - def testOneofClearUnsetField(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - self.ensureNestedMessageExists(m, 'oneof_nested_message') - m.ClearField('oneof_nested_message') - self.assertEqual(11, m.oneof_uint32) - if message_module is unittest_pb2: - self.assertTrue(m.HasField('oneof_field')) - self.assertTrue(m.HasField('oneof_uint32')) - self.assertEqual('oneof_uint32', m.WhichOneof('oneof_field')) - - def testOneofDeserialize(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m2 = message_module.TestAllTypes() - m2.ParseFromString(m.SerializeToString()) - self.assertEqual('oneof_uint32', m2.WhichOneof('oneof_field')) - - def testOneofCopyFrom(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m2 = message_module.TestAllTypes() - m2.CopyFrom(m) - self.assertEqual('oneof_uint32', m2.WhichOneof('oneof_field')) - - def testOneofNestedMergeFrom(self, message_module): - m = message_module.NestedTestAllTypes() - m.payload.oneof_uint32 = 11 - m2 = message_module.NestedTestAllTypes() - m2.payload.oneof_bytes = b'bb' - m2.child.payload.oneof_bytes = b'bb' - m2.MergeFrom(m) - self.assertEqual('oneof_uint32', m2.payload.WhichOneof('oneof_field')) - self.assertEqual('oneof_bytes', m2.child.payload.WhichOneof('oneof_field')) - - def testOneofMessageMergeFrom(self, message_module): - m = message_module.NestedTestAllTypes() - m.payload.oneof_nested_message.bb = 11 - m.child.payload.oneof_nested_message.bb = 12 - m2 = message_module.NestedTestAllTypes() - m2.payload.oneof_uint32 = 13 - m2.MergeFrom(m) - self.assertEqual('oneof_nested_message', - m2.payload.WhichOneof('oneof_field')) - self.assertEqual('oneof_nested_message', - m2.child.payload.WhichOneof('oneof_field')) - - def testOneofNestedMessageInit(self, message_module): - m = message_module.TestAllTypes( - oneof_nested_message=message_module.TestAllTypes.NestedMessage()) - self.assertEqual('oneof_nested_message', m.WhichOneof('oneof_field')) - - def testOneofClear(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m.Clear() - self.assertIsNone(m.WhichOneof('oneof_field')) - m.oneof_bytes = b'bb' - self.assertEqual('oneof_bytes', m.WhichOneof('oneof_field')) - - def testAssignByteStringToUnicodeField(self, message_module): - """Assigning a byte string to a string field should result - - in the value being converted to a Unicode string. - """ - m = message_module.TestAllTypes() - m.optional_string = str('') - self.assertIsInstance(m.optional_string, str) - - def testLongValuedSlice(self, message_module): - """It should be possible to use int-valued indices in slices. - - This didn't used to work in the v2 C++ implementation. - """ - m = message_module.TestAllTypes() - - # Repeated scalar - m.repeated_int32.append(1) - sl = m.repeated_int32[int(0):int(len(m.repeated_int32))] - self.assertEqual(len(m.repeated_int32), len(sl)) - - # Repeated composite - m.repeated_nested_message.add().bb = 3 - sl = m.repeated_nested_message[int(0):int(len(m.repeated_nested_message))] - self.assertEqual(len(m.repeated_nested_message), len(sl)) - - def testExtendShouldNotSwallowExceptions(self, message_module): - """This didn't use to work in the v2 C++ implementation.""" - m = message_module.TestAllTypes() - with self.assertRaises(NameError) as _: - m.repeated_int32.extend(a for i in range(10)) # pylint: disable=undefined-variable - with self.assertRaises(NameError) as _: - m.repeated_nested_enum.extend(a for i in range(10)) # pylint: disable=undefined-variable - - FALSY_VALUES = [None, False, 0, 0.0, b'', u'', bytearray(), [], {}, set()] - - def testExtendInt32WithNothing(self, message_module): - """Test no-ops extending repeated int32 fields.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_int32) - - # TODO(ptucker): Deprecate this behavior. b/18413862 - for falsy_value in MessageTest.FALSY_VALUES: - m.repeated_int32.extend(falsy_value) - self.assertSequenceEqual([], m.repeated_int32) - - m.repeated_int32.extend([]) - self.assertSequenceEqual([], m.repeated_int32) - - def testExtendFloatWithNothing(self, message_module): - """Test no-ops extending repeated float fields.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_float) - - # TODO(ptucker): Deprecate this behavior. b/18413862 - for falsy_value in MessageTest.FALSY_VALUES: - m.repeated_float.extend(falsy_value) - self.assertSequenceEqual([], m.repeated_float) - - m.repeated_float.extend([]) - self.assertSequenceEqual([], m.repeated_float) - - def testExtendStringWithNothing(self, message_module): - """Test no-ops extending repeated string fields.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_string) - - # TODO(ptucker): Deprecate this behavior. b/18413862 - for falsy_value in MessageTest.FALSY_VALUES: - m.repeated_string.extend(falsy_value) - self.assertSequenceEqual([], m.repeated_string) - - m.repeated_string.extend([]) - self.assertSequenceEqual([], m.repeated_string) - - def testExtendInt32WithPythonList(self, message_module): - """Test extending repeated int32 fields with python lists.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_int32) - m.repeated_int32.extend([0]) - self.assertSequenceEqual([0], m.repeated_int32) - m.repeated_int32.extend([1, 2]) - self.assertSequenceEqual([0, 1, 2], m.repeated_int32) - m.repeated_int32.extend([3, 4]) - self.assertSequenceEqual([0, 1, 2, 3, 4], m.repeated_int32) - - def testExtendFloatWithPythonList(self, message_module): - """Test extending repeated float fields with python lists.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_float) - m.repeated_float.extend([0.0]) - self.assertSequenceEqual([0.0], m.repeated_float) - m.repeated_float.extend([1.0, 2.0]) - self.assertSequenceEqual([0.0, 1.0, 2.0], m.repeated_float) - m.repeated_float.extend([3.0, 4.0]) - self.assertSequenceEqual([0.0, 1.0, 2.0, 3.0, 4.0], m.repeated_float) - - def testExtendStringWithPythonList(self, message_module): - """Test extending repeated string fields with python lists.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_string) - m.repeated_string.extend(['']) - self.assertSequenceEqual([''], m.repeated_string) - m.repeated_string.extend(['11', '22']) - self.assertSequenceEqual(['', '11', '22'], m.repeated_string) - m.repeated_string.extend(['33', '44']) - self.assertSequenceEqual(['', '11', '22', '33', '44'], m.repeated_string) - - def testExtendStringWithString(self, message_module): - """Test extending repeated string fields with characters from a string.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_string) - m.repeated_string.extend('abc') - self.assertSequenceEqual(['a', 'b', 'c'], m.repeated_string) - - class TestIterable(object): - """This iterable object mimics the behavior of numpy.array. - - __nonzero__ fails for length > 1, and returns bool(item[0]) for length == 1. - - """ - - def __init__(self, values=None): - self._list = values or [] - - def __nonzero__(self): - size = len(self._list) - if size == 0: - return False - if size == 1: - return bool(self._list[0]) - raise ValueError('Truth value is ambiguous.') - - def __len__(self): - return len(self._list) - - def __iter__(self): - return self._list.__iter__() - - def testExtendInt32WithIterable(self, message_module): - """Test extending repeated int32 fields with iterable.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_int32) - m.repeated_int32.extend(MessageTest.TestIterable([])) - self.assertSequenceEqual([], m.repeated_int32) - m.repeated_int32.extend(MessageTest.TestIterable([0])) - self.assertSequenceEqual([0], m.repeated_int32) - m.repeated_int32.extend(MessageTest.TestIterable([1, 2])) - self.assertSequenceEqual([0, 1, 2], m.repeated_int32) - m.repeated_int32.extend(MessageTest.TestIterable([3, 4])) - self.assertSequenceEqual([0, 1, 2, 3, 4], m.repeated_int32) - - def testExtendFloatWithIterable(self, message_module): - """Test extending repeated float fields with iterable.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_float) - m.repeated_float.extend(MessageTest.TestIterable([])) - self.assertSequenceEqual([], m.repeated_float) - m.repeated_float.extend(MessageTest.TestIterable([0.0])) - self.assertSequenceEqual([0.0], m.repeated_float) - m.repeated_float.extend(MessageTest.TestIterable([1.0, 2.0])) - self.assertSequenceEqual([0.0, 1.0, 2.0], m.repeated_float) - m.repeated_float.extend(MessageTest.TestIterable([3.0, 4.0])) - self.assertSequenceEqual([0.0, 1.0, 2.0, 3.0, 4.0], m.repeated_float) - - def testExtendStringWithIterable(self, message_module): - """Test extending repeated string fields with iterable.""" - m = message_module.TestAllTypes() - self.assertSequenceEqual([], m.repeated_string) - m.repeated_string.extend(MessageTest.TestIterable([])) - self.assertSequenceEqual([], m.repeated_string) - m.repeated_string.extend(MessageTest.TestIterable([''])) - self.assertSequenceEqual([''], m.repeated_string) - m.repeated_string.extend(MessageTest.TestIterable(['1', '2'])) - self.assertSequenceEqual(['', '1', '2'], m.repeated_string) - m.repeated_string.extend(MessageTest.TestIterable(['3', '4'])) - self.assertSequenceEqual(['', '1', '2', '3', '4'], m.repeated_string) - - class TestIndex(object): - """This index object mimics the behavior of numpy.int64 and other types.""" - - def __init__(self, value=None): - self.value = value - - def __index__(self): - return self.value - - def testRepeatedIndexingWithIntIndex(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.extend([1, 2, 3]) - self.assertEqual(1, msg.repeated_int32[MessageTest.TestIndex(0)]) - - def testRepeatedIndexingWithNegative1IntIndex(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.extend([1, 2, 3]) - self.assertEqual(3, msg.repeated_int32[MessageTest.TestIndex(-1)]) - - def testRepeatedIndexingWithNegative1Int(self, message_module): - msg = message_module.TestAllTypes() - msg.repeated_int32.extend([1, 2, 3]) - self.assertEqual(3, msg.repeated_int32[-1]) - - def testPickleRepeatedScalarContainer(self, message_module): - # Pickle repeated scalar container is not supported. - m = message_module.TestAllTypes() - with self.assertRaises(pickle.PickleError) as _: - pickle.dumps(m.repeated_int32, pickle.HIGHEST_PROTOCOL) - - def testSortEmptyRepeatedCompositeContainer(self, message_module): - """Exercise a scenario that has led to segfaults in the past.""" - m = message_module.TestAllTypes() - m.repeated_nested_message.sort() - - def testHasFieldOnRepeatedField(self, message_module): - """Using HasField on a repeated field should raise an exception.""" - m = message_module.TestAllTypes() - with self.assertRaises(ValueError) as _: - m.HasField('repeated_int32') - - def testRepeatedScalarFieldPop(self, message_module): - m = message_module.TestAllTypes() - with self.assertRaises(IndexError) as _: - m.repeated_int32.pop() - m.repeated_int32.extend(range(5)) - self.assertEqual(4, m.repeated_int32.pop()) - self.assertEqual(0, m.repeated_int32.pop(0)) - self.assertEqual(2, m.repeated_int32.pop(1)) - self.assertEqual([1, 3], m.repeated_int32) - - def testRepeatedCompositeFieldPop(self, message_module): - m = message_module.TestAllTypes() - with self.assertRaises(IndexError) as _: - m.repeated_nested_message.pop() - with self.assertRaises(TypeError) as _: - m.repeated_nested_message.pop('0') - for i in range(5): - n = m.repeated_nested_message.add() - n.bb = i - self.assertEqual(4, m.repeated_nested_message.pop().bb) - self.assertEqual(0, m.repeated_nested_message.pop(0).bb) - self.assertEqual(2, m.repeated_nested_message.pop(1).bb) - self.assertEqual([1, 3], [n.bb for n in m.repeated_nested_message]) - - def testRepeatedCompareWithSelf(self, message_module): - m = message_module.TestAllTypes() - for i in range(5): - m.repeated_int32.insert(i, i) - n = m.repeated_nested_message.add() - n.bb = i - self.assertSequenceEqual(m.repeated_int32, m.repeated_int32) - self.assertEqual(m.repeated_nested_message, m.repeated_nested_message) - - def testReleasedNestedMessages(self, message_module): - """A case that lead to a segfault when a message detached from its parent - - container has itself a child container. - """ - m = message_module.NestedTestAllTypes() - m = m.repeated_child.add() - m = m.child - m = m.repeated_child.add() - self.assertEqual(m.payload.optional_int32, 0) - - def testSetRepeatedComposite(self, message_module): - m = message_module.TestAllTypes() - with self.assertRaises(AttributeError): - m.repeated_int32 = [] - m.repeated_int32.append(1) - with self.assertRaises(AttributeError): - m.repeated_int32 = [] - - def testReturningType(self, message_module): - m = message_module.TestAllTypes() - self.assertEqual(float, type(m.optional_float)) - self.assertEqual(float, type(m.optional_double)) - self.assertEqual(bool, type(m.optional_bool)) - m.optional_float = 1 - m.optional_double = 1 - m.optional_bool = 1 - m.repeated_float.append(1) - m.repeated_double.append(1) - m.repeated_bool.append(1) - m.ParseFromString(m.SerializeToString()) - self.assertEqual(float, type(m.optional_float)) - self.assertEqual(float, type(m.optional_double)) - self.assertEqual('1.0', str(m.optional_double)) - self.assertEqual(bool, type(m.optional_bool)) - self.assertEqual(float, type(m.repeated_float[0])) - self.assertEqual(float, type(m.repeated_double[0])) - self.assertEqual(bool, type(m.repeated_bool[0])) - self.assertEqual(True, m.repeated_bool[0]) - - -# Class to test proto2-only features (required, extensions, etc.) -@testing_refleaks.TestCase -class Proto2Test(unittest.TestCase): - - def testFieldPresence(self): - message = unittest_pb2.TestAllTypes() - - self.assertFalse(message.HasField('optional_int32')) - self.assertFalse(message.HasField('optional_bool')) - self.assertFalse(message.HasField('optional_nested_message')) - - with self.assertRaises(ValueError): - message.HasField('field_doesnt_exist') - - with self.assertRaises(ValueError): - message.HasField('repeated_int32') - with self.assertRaises(ValueError): - message.HasField('repeated_nested_message') - - self.assertEqual(0, message.optional_int32) - self.assertEqual(False, message.optional_bool) - self.assertEqual(0, message.optional_nested_message.bb) - - # Fields are set even when setting the values to default values. - message.optional_int32 = 0 - message.optional_bool = False - message.optional_nested_message.bb = 0 - self.assertTrue(message.HasField('optional_int32')) - self.assertTrue(message.HasField('optional_bool')) - self.assertTrue(message.HasField('optional_nested_message')) - - # Set the fields to non-default values. - message.optional_int32 = 5 - message.optional_bool = True - message.optional_nested_message.bb = 15 - - self.assertTrue(message.HasField(u'optional_int32')) - self.assertTrue(message.HasField('optional_bool')) - self.assertTrue(message.HasField('optional_nested_message')) - - # Clearing the fields unsets them and resets their value to default. - message.ClearField('optional_int32') - message.ClearField(u'optional_bool') - message.ClearField('optional_nested_message') - - self.assertFalse(message.HasField('optional_int32')) - self.assertFalse(message.HasField('optional_bool')) - self.assertFalse(message.HasField('optional_nested_message')) - self.assertEqual(0, message.optional_int32) - self.assertEqual(False, message.optional_bool) - self.assertEqual(0, message.optional_nested_message.bb) - - def testAssignInvalidEnum(self): - """Assigning an invalid enum number is not allowed in proto2.""" - m = unittest_pb2.TestAllTypes() - - # Proto2 can not assign unknown enum. - with self.assertRaises(ValueError) as _: - m.optional_nested_enum = 1234567 - self.assertRaises(ValueError, m.repeated_nested_enum.append, 1234567) - # Assignment is a different code path than append for the C++ impl. - m.repeated_nested_enum.append(2) - m.repeated_nested_enum[0] = 2 - with self.assertRaises(ValueError): - m.repeated_nested_enum[0] = 123456 - - # Unknown enum value can be parsed but is ignored. - m2 = unittest_proto3_arena_pb2.TestAllTypes() - m2.optional_nested_enum = 1234567 - m2.repeated_nested_enum.append(7654321) - serialized = m2.SerializeToString() - - m3 = unittest_pb2.TestAllTypes() - m3.ParseFromString(serialized) - self.assertFalse(m3.HasField('optional_nested_enum')) - # 1 is the default value for optional_nested_enum. - self.assertEqual(1, m3.optional_nested_enum) - self.assertEqual(0, len(m3.repeated_nested_enum)) - m2.Clear() - m2.ParseFromString(m3.SerializeToString()) - self.assertEqual(1234567, m2.optional_nested_enum) - self.assertEqual(7654321, m2.repeated_nested_enum[0]) - - def testUnknownEnumMap(self): - m = map_proto2_unittest_pb2.TestEnumMap() - m.known_map_field[123] = 0 - with self.assertRaises(ValueError): - m.unknown_map_field[1] = 123 - - def testExtensionsErrors(self): - msg = unittest_pb2.TestAllTypes() - self.assertRaises(AttributeError, getattr, msg, 'Extensions') - - def testMergeFromExtensions(self): - msg1 = more_extensions_pb2.TopLevelMessage() - msg2 = more_extensions_pb2.TopLevelMessage() - # Cpp extension will lazily create a sub message which is immutable. - self.assertEqual( - 0, - msg1.submessage.Extensions[more_extensions_pb2.optional_int_extension]) - self.assertFalse(msg1.HasField('submessage')) - msg2.submessage.Extensions[more_extensions_pb2.optional_int_extension] = 123 - # Make sure cmessage and extensions pointing to a mutable message - # after merge instead of the lazily created message. - msg1.MergeFrom(msg2) - self.assertEqual( - 123, - msg1.submessage.Extensions[more_extensions_pb2.optional_int_extension]) - - def testGoldenExtensions(self): - golden_data = test_util.GoldenFileData('golden_message') - golden_message = unittest_pb2.TestAllExtensions() - golden_message.ParseFromString(golden_data) - all_set = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(all_set) - self.assertEqual(all_set, golden_message) - self.assertEqual(golden_data, golden_message.SerializeToString()) - golden_copy = copy.deepcopy(golden_message) - self.assertEqual(golden_data, golden_copy.SerializeToString()) - - def testGoldenPackedExtensions(self): - golden_data = test_util.GoldenFileData('golden_packed_fields_message') - golden_message = unittest_pb2.TestPackedExtensions() - golden_message.ParseFromString(golden_data) - all_set = unittest_pb2.TestPackedExtensions() - test_util.SetAllPackedExtensions(all_set) - self.assertEqual(all_set, golden_message) - self.assertEqual(golden_data, all_set.SerializeToString()) - golden_copy = copy.deepcopy(golden_message) - self.assertEqual(golden_data, golden_copy.SerializeToString()) - - def testPickleIncompleteProto(self): - golden_message = unittest_pb2.TestRequired(a=1) - pickled_message = pickle.dumps(golden_message) - - unpickled_message = pickle.loads(pickled_message) - self.assertEqual(unpickled_message, golden_message) - self.assertEqual(unpickled_message.a, 1) - # This is still an incomplete proto - so serializing should fail - self.assertRaises(message.EncodeError, unpickled_message.SerializeToString) - - # TODO(haberman): this isn't really a proto2-specific test except that this - # message has a required field in it. Should probably be factored out so - # that we can test the other parts with proto3. - def testParsingMerge(self): - """Check the merge behavior when a required or optional field appears - - multiple times in the input. - """ - messages = [ - unittest_pb2.TestAllTypes(), - unittest_pb2.TestAllTypes(), - unittest_pb2.TestAllTypes() - ] - messages[0].optional_int32 = 1 - messages[1].optional_int64 = 2 - messages[2].optional_int32 = 3 - messages[2].optional_string = 'hello' - - merged_message = unittest_pb2.TestAllTypes() - merged_message.optional_int32 = 3 - merged_message.optional_int64 = 2 - merged_message.optional_string = 'hello' - - generator = unittest_pb2.TestParsingMerge.RepeatedFieldsGenerator() - generator.field1.extend(messages) - generator.field2.extend(messages) - generator.field3.extend(messages) - generator.ext1.extend(messages) - generator.ext2.extend(messages) - generator.group1.add().field1.MergeFrom(messages[0]) - generator.group1.add().field1.MergeFrom(messages[1]) - generator.group1.add().field1.MergeFrom(messages[2]) - generator.group2.add().field1.MergeFrom(messages[0]) - generator.group2.add().field1.MergeFrom(messages[1]) - generator.group2.add().field1.MergeFrom(messages[2]) - - data = generator.SerializeToString() - parsing_merge = unittest_pb2.TestParsingMerge() - parsing_merge.ParseFromString(data) - - # Required and optional fields should be merged. - self.assertEqual(parsing_merge.required_all_types, merged_message) - self.assertEqual(parsing_merge.optional_all_types, merged_message) - self.assertEqual(parsing_merge.optionalgroup.optional_group_all_types, - merged_message) - self.assertEqual( - parsing_merge.Extensions[unittest_pb2.TestParsingMerge.optional_ext], - merged_message) - - # Repeated fields should not be merged. - self.assertEqual(len(parsing_merge.repeated_all_types), 3) - self.assertEqual(len(parsing_merge.repeatedgroup), 3) - self.assertEqual( - len(parsing_merge.Extensions[ - unittest_pb2.TestParsingMerge.repeated_ext]), 3) - - def testPythonicInit(self): - message = unittest_pb2.TestAllTypes( - optional_int32=100, - optional_fixed32=200, - optional_float=300.5, - optional_bytes=b'x', - optionalgroup={'a': 400}, - optional_nested_message={'bb': 500}, - optional_foreign_message={}, - optional_nested_enum='BAZ', - repeatedgroup=[{ - 'a': 600 - }, { - 'a': 700 - }], - repeated_nested_enum=['FOO', unittest_pb2.TestAllTypes.BAR], - default_int32=800, - oneof_string='y') - self.assertIsInstance(message, unittest_pb2.TestAllTypes) - self.assertEqual(100, message.optional_int32) - self.assertEqual(200, message.optional_fixed32) - self.assertEqual(300.5, message.optional_float) - self.assertEqual(b'x', message.optional_bytes) - self.assertEqual(400, message.optionalgroup.a) - self.assertIsInstance(message.optional_nested_message, - unittest_pb2.TestAllTypes.NestedMessage) - self.assertEqual(500, message.optional_nested_message.bb) - self.assertTrue(message.HasField('optional_foreign_message')) - self.assertEqual(message.optional_foreign_message, - unittest_pb2.ForeignMessage()) - self.assertEqual(unittest_pb2.TestAllTypes.BAZ, - message.optional_nested_enum) - self.assertEqual(2, len(message.repeatedgroup)) - self.assertEqual(600, message.repeatedgroup[0].a) - self.assertEqual(700, message.repeatedgroup[1].a) - self.assertEqual(2, len(message.repeated_nested_enum)) - self.assertEqual(unittest_pb2.TestAllTypes.FOO, - message.repeated_nested_enum[0]) - self.assertEqual(unittest_pb2.TestAllTypes.BAR, - message.repeated_nested_enum[1]) - self.assertEqual(800, message.default_int32) - self.assertEqual('y', message.oneof_string) - self.assertFalse(message.HasField('optional_int64')) - self.assertEqual(0, len(message.repeated_float)) - self.assertEqual(42, message.default_int64) - - message = unittest_pb2.TestAllTypes(optional_nested_enum=u'BAZ') - self.assertEqual(unittest_pb2.TestAllTypes.BAZ, - message.optional_nested_enum) - - with self.assertRaises(ValueError): - unittest_pb2.TestAllTypes( - optional_nested_message={'INVALID_NESTED_FIELD': 17}) - - with self.assertRaises(TypeError): - unittest_pb2.TestAllTypes( - optional_nested_message={'bb': 'INVALID_VALUE_TYPE'}) - - with self.assertRaises(ValueError): - unittest_pb2.TestAllTypes(optional_nested_enum='INVALID_LABEL') - - with self.assertRaises(ValueError): - unittest_pb2.TestAllTypes(repeated_nested_enum='FOO') - - def testPythonicInitWithDict(self): - # Both string/unicode field name keys should work. - kwargs = { - 'optional_int32': 100, - u'optional_fixed32': 200, - } - msg = unittest_pb2.TestAllTypes(**kwargs) - self.assertEqual(100, msg.optional_int32) - self.assertEqual(200, msg.optional_fixed32) - - - def test_documentation(self): - # Also used by the interactive help() function. - doc = pydoc.html.document(unittest_pb2.TestAllTypes, 'message') - self.assertIn('class TestAllTypes', doc) - self.assertIn('SerializePartialToString', doc) - self.assertIn('repeated_float', doc) - base = unittest_pb2.TestAllTypes.__bases__[0] - self.assertRaises(AttributeError, getattr, base, '_extensions_by_name') - - -# Class to test proto3-only features/behavior (updated field presence & enums) -@testing_refleaks.TestCase -class Proto3Test(unittest.TestCase): - - # Utility method for comparing equality with a map. - def assertMapIterEquals(self, map_iter, dict_value): - # Avoid mutating caller's copy. - dict_value = dict(dict_value) - - for k, v in map_iter: - self.assertEqual(v, dict_value[k]) - del dict_value[k] - - self.assertEqual({}, dict_value) - - def testFieldPresence(self): - message = unittest_proto3_arena_pb2.TestAllTypes() - - # We can't test presence of non-repeated, non-submessage fields. - with self.assertRaises(ValueError): - message.HasField('optional_int32') - with self.assertRaises(ValueError): - message.HasField('optional_float') - with self.assertRaises(ValueError): - message.HasField('optional_string') - with self.assertRaises(ValueError): - message.HasField('optional_bool') - - # But we can still test presence of submessage fields. - self.assertFalse(message.HasField('optional_nested_message')) - - # As with proto2, we can't test presence of fields that don't exist, or - # repeated fields. - with self.assertRaises(ValueError): - message.HasField('field_doesnt_exist') - - with self.assertRaises(ValueError): - message.HasField('repeated_int32') - with self.assertRaises(ValueError): - message.HasField('repeated_nested_message') - - # Fields should default to their type-specific default. - self.assertEqual(0, message.optional_int32) - self.assertEqual(0, message.optional_float) - self.assertEqual('', message.optional_string) - self.assertEqual(False, message.optional_bool) - self.assertEqual(0, message.optional_nested_message.bb) - - # Setting a submessage should still return proper presence information. - message.optional_nested_message.bb = 0 - self.assertTrue(message.HasField('optional_nested_message')) - - # Set the fields to non-default values. - message.optional_int32 = 5 - message.optional_float = 1.1 - message.optional_string = 'abc' - message.optional_bool = True - message.optional_nested_message.bb = 15 - - # Clearing the fields unsets them and resets their value to default. - message.ClearField('optional_int32') - message.ClearField('optional_float') - message.ClearField('optional_string') - message.ClearField('optional_bool') - message.ClearField('optional_nested_message') - - self.assertEqual(0, message.optional_int32) - self.assertEqual(0, message.optional_float) - self.assertEqual('', message.optional_string) - self.assertEqual(False, message.optional_bool) - self.assertEqual(0, message.optional_nested_message.bb) - - def testProto3ParserDropDefaultScalar(self): - message_proto2 = unittest_pb2.TestAllTypes() - message_proto2.optional_int32 = 0 - message_proto2.optional_string = '' - message_proto2.optional_bytes = b'' - self.assertEqual(len(message_proto2.ListFields()), 3) - - message_proto3 = unittest_proto3_arena_pb2.TestAllTypes() - message_proto3.ParseFromString(message_proto2.SerializeToString()) - self.assertEqual(len(message_proto3.ListFields()), 0) - - def testProto3Optional(self): - msg = test_proto3_optional_pb2.TestProto3Optional() - self.assertFalse(msg.HasField('optional_int32')) - self.assertFalse(msg.HasField('optional_float')) - self.assertFalse(msg.HasField('optional_string')) - self.assertFalse(msg.HasField('optional_nested_message')) - self.assertFalse(msg.optional_nested_message.HasField('bb')) - - # Set fields. - msg.optional_int32 = 1 - msg.optional_float = 1.0 - msg.optional_string = '123' - msg.optional_nested_message.bb = 1 - self.assertTrue(msg.HasField('optional_int32')) - self.assertTrue(msg.HasField('optional_float')) - self.assertTrue(msg.HasField('optional_string')) - self.assertTrue(msg.HasField('optional_nested_message')) - self.assertTrue(msg.optional_nested_message.HasField('bb')) - # Set to default value does not clear the fields - msg.optional_int32 = 0 - msg.optional_float = 0.0 - msg.optional_string = '' - msg.optional_nested_message.bb = 0 - self.assertTrue(msg.HasField('optional_int32')) - self.assertTrue(msg.HasField('optional_float')) - self.assertTrue(msg.HasField('optional_string')) - self.assertTrue(msg.HasField('optional_nested_message')) - self.assertTrue(msg.optional_nested_message.HasField('bb')) - - # Test serialize - msg2 = test_proto3_optional_pb2.TestProto3Optional() - msg2.ParseFromString(msg.SerializeToString()) - self.assertTrue(msg2.HasField('optional_int32')) - self.assertTrue(msg2.HasField('optional_float')) - self.assertTrue(msg2.HasField('optional_string')) - self.assertTrue(msg2.HasField('optional_nested_message')) - self.assertTrue(msg2.optional_nested_message.HasField('bb')) - - self.assertEqual(msg.WhichOneof('_optional_int32'), 'optional_int32') - - # Clear these fields. - msg.ClearField('optional_int32') - msg.ClearField('optional_float') - msg.ClearField('optional_string') - msg.ClearField('optional_nested_message') - self.assertFalse(msg.HasField('optional_int32')) - self.assertFalse(msg.HasField('optional_float')) - self.assertFalse(msg.HasField('optional_string')) - self.assertFalse(msg.HasField('optional_nested_message')) - self.assertFalse(msg.optional_nested_message.HasField('bb')) - - self.assertEqual(msg.WhichOneof('_optional_int32'), None) - - # Test has presence: - for field in test_proto3_optional_pb2.TestProto3Optional.DESCRIPTOR.fields: - self.assertTrue(field.has_presence) - for field in unittest_pb2.TestAllTypes.DESCRIPTOR.fields: - if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: - self.assertFalse(field.has_presence) - else: - self.assertTrue(field.has_presence) - proto3_descriptor = unittest_proto3_arena_pb2.TestAllTypes.DESCRIPTOR - repeated_field = proto3_descriptor.fields_by_name['repeated_int32'] - self.assertFalse(repeated_field.has_presence) - singular_field = proto3_descriptor.fields_by_name['optional_int32'] - self.assertFalse(singular_field.has_presence) - optional_field = proto3_descriptor.fields_by_name['proto3_optional_int32'] - self.assertTrue(optional_field.has_presence) - message_field = proto3_descriptor.fields_by_name['optional_nested_message'] - self.assertTrue(message_field.has_presence) - oneof_field = proto3_descriptor.fields_by_name['oneof_uint32'] - self.assertTrue(oneof_field.has_presence) - - def testAssignUnknownEnum(self): - """Assigning an unknown enum value is allowed and preserves the value.""" - m = unittest_proto3_arena_pb2.TestAllTypes() - - # Proto3 can assign unknown enums. - m.optional_nested_enum = 1234567 - self.assertEqual(1234567, m.optional_nested_enum) - m.repeated_nested_enum.append(22334455) - self.assertEqual(22334455, m.repeated_nested_enum[0]) - # Assignment is a different code path than append for the C++ impl. - m.repeated_nested_enum[0] = 7654321 - self.assertEqual(7654321, m.repeated_nested_enum[0]) - serialized = m.SerializeToString() - - m2 = unittest_proto3_arena_pb2.TestAllTypes() - m2.ParseFromString(serialized) - self.assertEqual(1234567, m2.optional_nested_enum) - self.assertEqual(7654321, m2.repeated_nested_enum[0]) - - # Map isn't really a proto3-only feature. But there is no proto2 equivalent - # of google/protobuf/map_unittest.proto right now, so it's not easy to - # test both with the same test like we do for the other proto2/proto3 tests. - # (google/protobuf/map_proto2_unittest.proto is very different in the set - # of messages and fields it contains). - def testScalarMapDefaults(self): - msg = map_unittest_pb2.TestMap() - - # Scalars start out unset. - self.assertFalse(-123 in msg.map_int32_int32) - self.assertFalse(-2**33 in msg.map_int64_int64) - self.assertFalse(123 in msg.map_uint32_uint32) - self.assertFalse(2**33 in msg.map_uint64_uint64) - self.assertFalse(123 in msg.map_int32_double) - self.assertFalse(False in msg.map_bool_bool) - self.assertFalse('abc' in msg.map_string_string) - self.assertFalse(111 in msg.map_int32_bytes) - self.assertFalse(888 in msg.map_int32_enum) - - # Accessing an unset key returns the default. - self.assertEqual(0, msg.map_int32_int32[-123]) - self.assertEqual(0, msg.map_int64_int64[-2**33]) - self.assertEqual(0, msg.map_uint32_uint32[123]) - self.assertEqual(0, msg.map_uint64_uint64[2**33]) - self.assertEqual(0.0, msg.map_int32_double[123]) - self.assertTrue(isinstance(msg.map_int32_double[123], float)) - self.assertEqual(False, msg.map_bool_bool[False]) - self.assertTrue(isinstance(msg.map_bool_bool[False], bool)) - self.assertEqual('', msg.map_string_string['abc']) - self.assertEqual(b'', msg.map_int32_bytes[111]) - self.assertEqual(0, msg.map_int32_enum[888]) - - # It also sets the value in the map - self.assertTrue(-123 in msg.map_int32_int32) - self.assertTrue(-2**33 in msg.map_int64_int64) - self.assertTrue(123 in msg.map_uint32_uint32) - self.assertTrue(2**33 in msg.map_uint64_uint64) - self.assertTrue(123 in msg.map_int32_double) - self.assertTrue(False in msg.map_bool_bool) - self.assertTrue('abc' in msg.map_string_string) - self.assertTrue(111 in msg.map_int32_bytes) - self.assertTrue(888 in msg.map_int32_enum) - - self.assertIsInstance(msg.map_string_string['abc'], str) - - # Accessing an unset key still throws TypeError if the type of the key - # is incorrect. - with self.assertRaises(TypeError): - msg.map_string_string[123] - - with self.assertRaises(TypeError): - 123 in msg.map_string_string - - def testMapGet(self): - # Need to test that get() properly returns the default, even though the dict - # has defaultdict-like semantics. - msg = map_unittest_pb2.TestMap() - - self.assertIsNone(msg.map_int32_int32.get(5)) - self.assertEqual(10, msg.map_int32_int32.get(5, 10)) - self.assertEqual(10, msg.map_int32_int32.get(key=5, default=10)) - self.assertIsNone(msg.map_int32_int32.get(5)) - - msg.map_int32_int32[5] = 15 - self.assertEqual(15, msg.map_int32_int32.get(5)) - self.assertEqual(15, msg.map_int32_int32.get(5)) - with self.assertRaises(TypeError): - msg.map_int32_int32.get('') - - self.assertIsNone(msg.map_int32_foreign_message.get(5)) - self.assertEqual(10, msg.map_int32_foreign_message.get(5, 10)) - self.assertEqual(10, msg.map_int32_foreign_message.get(key=5, default=10)) - - submsg = msg.map_int32_foreign_message[5] - self.assertIs(submsg, msg.map_int32_foreign_message.get(5)) - with self.assertRaises(TypeError): - msg.map_int32_foreign_message.get('') - - def testScalarMap(self): - msg = map_unittest_pb2.TestMap() - - self.assertEqual(0, len(msg.map_int32_int32)) - self.assertFalse(5 in msg.map_int32_int32) - - msg.map_int32_int32[-123] = -456 - msg.map_int64_int64[-2**33] = -2**34 - msg.map_uint32_uint32[123] = 456 - msg.map_uint64_uint64[2**33] = 2**34 - msg.map_int32_float[2] = 1.2 - msg.map_int32_double[1] = 3.3 - msg.map_string_string['abc'] = '123' - msg.map_bool_bool[True] = True - msg.map_int32_enum[888] = 2 - # Unknown numeric enum is supported in proto3. - msg.map_int32_enum[123] = 456 - - self.assertEqual([], msg.FindInitializationErrors()) - - self.assertEqual(1, len(msg.map_string_string)) - - # Bad key. - with self.assertRaises(TypeError): - msg.map_string_string[123] = '123' - - # Verify that trying to assign a bad key doesn't actually add a member to - # the map. - self.assertEqual(1, len(msg.map_string_string)) - - # Bad value. - with self.assertRaises(TypeError): - msg.map_string_string['123'] = 123 - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMap() - msg2.ParseFromString(serialized) - - # Bad key. - with self.assertRaises(TypeError): - msg2.map_string_string[123] = '123' - - # Bad value. - with self.assertRaises(TypeError): - msg2.map_string_string['123'] = 123 - - self.assertEqual(-456, msg2.map_int32_int32[-123]) - self.assertEqual(-2**34, msg2.map_int64_int64[-2**33]) - self.assertEqual(456, msg2.map_uint32_uint32[123]) - self.assertEqual(2**34, msg2.map_uint64_uint64[2**33]) - self.assertAlmostEqual(1.2, msg.map_int32_float[2]) - self.assertEqual(3.3, msg.map_int32_double[1]) - self.assertEqual('123', msg2.map_string_string['abc']) - self.assertEqual(True, msg2.map_bool_bool[True]) - self.assertEqual(2, msg2.map_int32_enum[888]) - self.assertEqual(456, msg2.map_int32_enum[123]) - self.assertEqual('{-123: -456}', str(msg2.map_int32_int32)) - - def testMapEntryAlwaysSerialized(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[0] = 0 - msg.map_string_string[''] = '' - self.assertEqual(msg.ByteSize(), 12) - self.assertEqual(b'\n\x04\x08\x00\x10\x00r\x04\n\x00\x12\x00', - msg.SerializeToString()) - - def testStringUnicodeConversionInMap(self): - msg = map_unittest_pb2.TestMap() - - unicode_obj = u'\u1234' - bytes_obj = unicode_obj.encode('utf8') - - msg.map_string_string[bytes_obj] = bytes_obj - - (key, value) = list(msg.map_string_string.items())[0] - - self.assertEqual(key, unicode_obj) - self.assertEqual(value, unicode_obj) - - self.assertIsInstance(key, str) - self.assertIsInstance(value, str) - - def testMessageMap(self): - msg = map_unittest_pb2.TestMap() - - self.assertEqual(0, len(msg.map_int32_foreign_message)) - self.assertFalse(5 in msg.map_int32_foreign_message) - - msg.map_int32_foreign_message[123] - # get_or_create() is an alias for getitem. - msg.map_int32_foreign_message.get_or_create(-456) - - self.assertEqual(2, len(msg.map_int32_foreign_message)) - self.assertIn(123, msg.map_int32_foreign_message) - self.assertIn(-456, msg.map_int32_foreign_message) - self.assertEqual(2, len(msg.map_int32_foreign_message)) - - # Bad key. - with self.assertRaises(TypeError): - msg.map_int32_foreign_message['123'] - - # Can't assign directly to submessage. - with self.assertRaises(ValueError): - msg.map_int32_foreign_message[999] = msg.map_int32_foreign_message[123] - - # Verify that trying to assign a bad key doesn't actually add a member to - # the map. - self.assertEqual(2, len(msg.map_int32_foreign_message)) - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMap() - msg2.ParseFromString(serialized) - - self.assertEqual(2, len(msg2.map_int32_foreign_message)) - self.assertIn(123, msg2.map_int32_foreign_message) - self.assertIn(-456, msg2.map_int32_foreign_message) - self.assertEqual(2, len(msg2.map_int32_foreign_message)) - msg2.map_int32_foreign_message[123].c = 1 - # TODO(jieluo): Fix text format for message map. - self.assertIn( - str(msg2.map_int32_foreign_message), - ('{-456: , 123: c: 1\n}', '{123: c: 1\n, -456: }')) - - def testNestedMessageMapItemDelete(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_all_types[1].optional_nested_message.bb = 1 - del msg.map_int32_all_types[1] - msg.map_int32_all_types[2].optional_nested_message.bb = 2 - self.assertEqual(1, len(msg.map_int32_all_types)) - msg.map_int32_all_types[1].optional_nested_message.bb = 1 - self.assertEqual(2, len(msg.map_int32_all_types)) - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMap() - msg2.ParseFromString(serialized) - keys = [1, 2] - # The loop triggers PyErr_Occurred() in c extension. - for key in keys: - del msg2.map_int32_all_types[key] - - def testMapByteSize(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[1] = 1 - size = msg.ByteSize() - msg.map_int32_int32[1] = 128 - self.assertEqual(msg.ByteSize(), size + 1) - - msg.map_int32_foreign_message[19].c = 1 - size = msg.ByteSize() - msg.map_int32_foreign_message[19].c = 128 - self.assertEqual(msg.ByteSize(), size + 1) - - def testMergeFrom(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[12] = 34 - msg.map_int32_int32[56] = 78 - msg.map_int64_int64[22] = 33 - msg.map_int32_foreign_message[111].c = 5 - msg.map_int32_foreign_message[222].c = 10 - - msg2 = map_unittest_pb2.TestMap() - msg2.map_int32_int32[12] = 55 - msg2.map_int64_int64[88] = 99 - msg2.map_int32_foreign_message[222].c = 15 - msg2.map_int32_foreign_message[222].d = 20 - old_map_value = msg2.map_int32_foreign_message[222] - - msg2.MergeFrom(msg) - # Compare with expected message instead of call - # msg2.map_int32_foreign_message[222] to make sure MergeFrom does not - # sync with repeated field and there is no duplicated keys. - expected_msg = map_unittest_pb2.TestMap() - expected_msg.CopyFrom(msg) - expected_msg.map_int64_int64[88] = 99 - self.assertEqual(msg2, expected_msg) - - self.assertEqual(34, msg2.map_int32_int32[12]) - self.assertEqual(78, msg2.map_int32_int32[56]) - self.assertEqual(33, msg2.map_int64_int64[22]) - self.assertEqual(99, msg2.map_int64_int64[88]) - self.assertEqual(5, msg2.map_int32_foreign_message[111].c) - self.assertEqual(10, msg2.map_int32_foreign_message[222].c) - self.assertFalse(msg2.map_int32_foreign_message[222].HasField('d')) - if api_implementation.Type() != 'cpp': - # During the call to MergeFrom(), the C++ implementation will have - # deallocated the underlying message, but this is very difficult to detect - # properly. The line below is likely to cause a segmentation fault. - # With the Python implementation, old_map_value is just 'detached' from - # the main message. Using it will not crash of course, but since it still - # have a reference to the parent message I'm sure we can find interesting - # ways to cause inconsistencies. - self.assertEqual(15, old_map_value.c) - - # Verify that there is only one entry per key, even though the MergeFrom - # may have internally created multiple entries for a single key in the - # list representation. - as_dict = {} - for key in msg2.map_int32_foreign_message: - self.assertFalse(key in as_dict) - as_dict[key] = msg2.map_int32_foreign_message[key].c - - self.assertEqual({111: 5, 222: 10}, as_dict) - - # Special case: test that delete of item really removes the item, even if - # there might have physically been duplicate keys due to the previous merge. - # This is only a special case for the C++ implementation which stores the - # map as an array. - del msg2.map_int32_int32[12] - self.assertFalse(12 in msg2.map_int32_int32) - - del msg2.map_int32_foreign_message[222] - self.assertFalse(222 in msg2.map_int32_foreign_message) - with self.assertRaises(TypeError): - del msg2.map_int32_foreign_message[''] - - def testMapMergeFrom(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[12] = 34 - msg.map_int32_int32[56] = 78 - msg.map_int64_int64[22] = 33 - msg.map_int32_foreign_message[111].c = 5 - msg.map_int32_foreign_message[222].c = 10 - - msg2 = map_unittest_pb2.TestMap() - msg2.map_int32_int32[12] = 55 - msg2.map_int64_int64[88] = 99 - msg2.map_int32_foreign_message[222].c = 15 - msg2.map_int32_foreign_message[222].d = 20 - - msg2.map_int32_int32.MergeFrom(msg.map_int32_int32) - self.assertEqual(34, msg2.map_int32_int32[12]) - self.assertEqual(78, msg2.map_int32_int32[56]) - - msg2.map_int64_int64.MergeFrom(msg.map_int64_int64) - self.assertEqual(33, msg2.map_int64_int64[22]) - self.assertEqual(99, msg2.map_int64_int64[88]) - - msg2.map_int32_foreign_message.MergeFrom(msg.map_int32_foreign_message) - # Compare with expected message instead of call - # msg.map_int32_foreign_message[222] to make sure MergeFrom does not - # sync with repeated field and no duplicated keys. - expected_msg = map_unittest_pb2.TestMap() - expected_msg.CopyFrom(msg) - expected_msg.map_int64_int64[88] = 99 - self.assertEqual(msg2, expected_msg) - - # Test when cpp extension cache a map. - m1 = map_unittest_pb2.TestMap() - m2 = map_unittest_pb2.TestMap() - self.assertEqual(m1.map_int32_foreign_message, m1.map_int32_foreign_message) - m2.map_int32_foreign_message[123].c = 10 - m1.MergeFrom(m2) - self.assertEqual(10, m2.map_int32_foreign_message[123].c) - - # Test merge maps within different message types. - m1 = map_unittest_pb2.TestMap() - m2 = map_unittest_pb2.TestMessageMap() - m2.map_int32_message[123].optional_int32 = 10 - m1.map_int32_all_types.MergeFrom(m2.map_int32_message) - self.assertEqual(10, m1.map_int32_all_types[123].optional_int32) - - # Test overwrite message value map - msg = map_unittest_pb2.TestMap() - msg.map_int32_foreign_message[222].c = 123 - msg2 = map_unittest_pb2.TestMap() - msg2.map_int32_foreign_message[222].d = 20 - msg.MergeFromString(msg2.SerializeToString()) - self.assertEqual(msg.map_int32_foreign_message[222].d, 20) - self.assertNotEqual(msg.map_int32_foreign_message[222].c, 123) - - # Merge a dict to map field is not accepted - with self.assertRaises(AttributeError): - m1.map_int32_all_types.MergeFrom( - {1: unittest_proto3_arena_pb2.TestAllTypes()}) - - def testMergeFromBadType(self): - msg = map_unittest_pb2.TestMap() - with self.assertRaisesRegex( - TypeError, - r'Parameter to MergeFrom\(\) must be instance of same class: expected ' - r'.+TestMap got int\.'): - msg.MergeFrom(1) - - def testCopyFromBadType(self): - msg = map_unittest_pb2.TestMap() - with self.assertRaisesRegex( - TypeError, - r'Parameter to [A-Za-z]*From\(\) must be instance of same class: ' - r'expected .+TestMap got int\.'): - msg.CopyFrom(1) - - def testIntegerMapWithLongs(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[int(-123)] = int(-456) - msg.map_int64_int64[int(-2**33)] = int(-2**34) - msg.map_uint32_uint32[int(123)] = int(456) - msg.map_uint64_uint64[int(2**33)] = int(2**34) - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMap() - msg2.ParseFromString(serialized) - - self.assertEqual(-456, msg2.map_int32_int32[-123]) - self.assertEqual(-2**34, msg2.map_int64_int64[-2**33]) - self.assertEqual(456, msg2.map_uint32_uint32[123]) - self.assertEqual(2**34, msg2.map_uint64_uint64[2**33]) - - def testMapAssignmentCausesPresence(self): - msg = map_unittest_pb2.TestMapSubmessage() - msg.test_map.map_int32_int32[123] = 456 - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMapSubmessage() - msg2.ParseFromString(serialized) - - self.assertEqual(msg, msg2) - - # Now test that various mutations of the map properly invalidate the - # cached size of the submessage. - msg.test_map.map_int32_int32[888] = 999 - serialized = msg.SerializeToString() - msg2.ParseFromString(serialized) - self.assertEqual(msg, msg2) - - msg.test_map.map_int32_int32.clear() - serialized = msg.SerializeToString() - msg2.ParseFromString(serialized) - self.assertEqual(msg, msg2) - - def testMapAssignmentCausesPresenceForSubmessages(self): - msg = map_unittest_pb2.TestMapSubmessage() - msg.test_map.map_int32_foreign_message[123].c = 5 - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMapSubmessage() - msg2.ParseFromString(serialized) - - self.assertEqual(msg, msg2) - - # Now test that various mutations of the map properly invalidate the - # cached size of the submessage. - msg.test_map.map_int32_foreign_message[888].c = 7 - serialized = msg.SerializeToString() - msg2.ParseFromString(serialized) - self.assertEqual(msg, msg2) - - msg.test_map.map_int32_foreign_message[888].MergeFrom( - msg.test_map.map_int32_foreign_message[123]) - serialized = msg.SerializeToString() - msg2.ParseFromString(serialized) - self.assertEqual(msg, msg2) - - msg.test_map.map_int32_foreign_message.clear() - serialized = msg.SerializeToString() - msg2.ParseFromString(serialized) - self.assertEqual(msg, msg2) - - def testModifyMapWhileIterating(self): - msg = map_unittest_pb2.TestMap() - - string_string_iter = iter(msg.map_string_string) - int32_foreign_iter = iter(msg.map_int32_foreign_message) - - msg.map_string_string['abc'] = '123' - msg.map_int32_foreign_message[5].c = 5 - - with self.assertRaises(RuntimeError): - for key in string_string_iter: - pass - - with self.assertRaises(RuntimeError): - for key in int32_foreign_iter: - pass - - def testModifyMapEntryWhileIterating(self): - msg = map_unittest_pb2.TestMap() - - msg.map_string_string['abc'] = '123' - msg.map_string_string['def'] = '456' - msg.map_string_string['ghi'] = '789' - - msg.map_int32_foreign_message[5].c = 5 - msg.map_int32_foreign_message[6].c = 6 - msg.map_int32_foreign_message[7].c = 7 - - string_string_keys = list(msg.map_string_string.keys()) - int32_foreign_keys = list(msg.map_int32_foreign_message.keys()) - - keys = [] - for key in msg.map_string_string: - keys.append(key) - msg.map_string_string[key] = '000' - self.assertEqual(keys, string_string_keys) - self.assertEqual(keys, list(msg.map_string_string.keys())) - - keys = [] - for key in msg.map_int32_foreign_message: - keys.append(key) - msg.map_int32_foreign_message[key].c = 0 - self.assertEqual(keys, int32_foreign_keys) - self.assertEqual(keys, list(msg.map_int32_foreign_message.keys())) - - def testSubmessageMap(self): - msg = map_unittest_pb2.TestMap() - - submsg = msg.map_int32_foreign_message[111] - self.assertIs(submsg, msg.map_int32_foreign_message[111]) - self.assertIsInstance(submsg, unittest_pb2.ForeignMessage) - - submsg.c = 5 - - serialized = msg.SerializeToString() - msg2 = map_unittest_pb2.TestMap() - msg2.ParseFromString(serialized) - - self.assertEqual(5, msg2.map_int32_foreign_message[111].c) - - # Doesn't allow direct submessage assignment. - with self.assertRaises(ValueError): - msg.map_int32_foreign_message[88] = unittest_pb2.ForeignMessage() - - def testMapIteration(self): - msg = map_unittest_pb2.TestMap() - - for k, v in msg.map_int32_int32.items(): - # Should not be reached. - self.assertTrue(False) - - msg.map_int32_int32[2] = 4 - msg.map_int32_int32[3] = 6 - msg.map_int32_int32[4] = 8 - self.assertEqual(3, len(msg.map_int32_int32)) - - matching_dict = {2: 4, 3: 6, 4: 8} - self.assertMapIterEquals(msg.map_int32_int32.items(), matching_dict) - - def testMapItems(self): - # Map items used to have strange behaviors when use c extension. Because - # [] may reorder the map and invalidate any existing iterators. - # TODO(jieluo): Check if [] reordering the map is a bug or intended - # behavior. - msg = map_unittest_pb2.TestMap() - msg.map_string_string['local_init_op'] = '' - msg.map_string_string['trainable_variables'] = '' - msg.map_string_string['variables'] = '' - msg.map_string_string['init_op'] = '' - msg.map_string_string['summaries'] = '' - items1 = msg.map_string_string.items() - items2 = msg.map_string_string.items() - self.assertEqual(items1, items2) - - def testMapDeterministicSerialization(self): - golden_data = (b'r\x0c\n\x07init_op\x12\x01d' - b'r\n\n\x05item1\x12\x01e' - b'r\n\n\x05item2\x12\x01f' - b'r\n\n\x05item3\x12\x01g' - b'r\x0b\n\x05item4\x12\x02QQ' - b'r\x12\n\rlocal_init_op\x12\x01a' - b'r\x0e\n\tsummaries\x12\x01e' - b'r\x18\n\x13trainable_variables\x12\x01b' - b'r\x0e\n\tvariables\x12\x01c') - msg = map_unittest_pb2.TestMap() - msg.map_string_string['local_init_op'] = 'a' - msg.map_string_string['trainable_variables'] = 'b' - msg.map_string_string['variables'] = 'c' - msg.map_string_string['init_op'] = 'd' - msg.map_string_string['summaries'] = 'e' - msg.map_string_string['item1'] = 'e' - msg.map_string_string['item2'] = 'f' - msg.map_string_string['item3'] = 'g' - msg.map_string_string['item4'] = 'QQ' - - # If deterministic serialization is not working correctly, this will be - # "flaky" depending on the exact python dict hash seed. - # - # Fortunately, there are enough items in this map that it is extremely - # unlikely to ever hit the "right" in-order combination, so the test - # itself should fail reliably. - self.assertEqual(golden_data, msg.SerializeToString(deterministic=True)) - - def testMapIterationClearMessage(self): - # Iterator needs to work even if message and map are deleted. - msg = map_unittest_pb2.TestMap() - - msg.map_int32_int32[2] = 4 - msg.map_int32_int32[3] = 6 - msg.map_int32_int32[4] = 8 - - it = msg.map_int32_int32.items() - del msg - - matching_dict = {2: 4, 3: 6, 4: 8} - self.assertMapIterEquals(it, matching_dict) - - def testMapConstruction(self): - msg = map_unittest_pb2.TestMap(map_int32_int32={1: 2, 3: 4}) - self.assertEqual(2, msg.map_int32_int32[1]) - self.assertEqual(4, msg.map_int32_int32[3]) - - msg = map_unittest_pb2.TestMap( - map_int32_foreign_message={3: unittest_pb2.ForeignMessage(c=5)}) - self.assertEqual(5, msg.map_int32_foreign_message[3].c) - - def testMapScalarFieldConstruction(self): - msg1 = map_unittest_pb2.TestMap() - msg1.map_int32_int32[1] = 42 - msg2 = map_unittest_pb2.TestMap(map_int32_int32=msg1.map_int32_int32) - self.assertEqual(42, msg2.map_int32_int32[1]) - - def testMapMessageFieldConstruction(self): - msg1 = map_unittest_pb2.TestMap() - msg1.map_string_foreign_message['test'].c = 42 - msg2 = map_unittest_pb2.TestMap( - map_string_foreign_message=msg1.map_string_foreign_message) - self.assertEqual(42, msg2.map_string_foreign_message['test'].c) - - def testMapFieldRaisesCorrectError(self): - # Should raise a TypeError when given a non-iterable. - with self.assertRaises(TypeError): - map_unittest_pb2.TestMap(map_string_foreign_message=1) - - def testMapValidAfterFieldCleared(self): - # Map needs to work even if field is cleared. - # For the C++ implementation this tests the correctness of - # MapContainer::Release() - msg = map_unittest_pb2.TestMap() - int32_map = msg.map_int32_int32 - - int32_map[2] = 4 - int32_map[3] = 6 - int32_map[4] = 8 - - msg.ClearField('map_int32_int32') - self.assertEqual(b'', msg.SerializeToString()) - matching_dict = {2: 4, 3: 6, 4: 8} - self.assertMapIterEquals(int32_map.items(), matching_dict) - - def testMessageMapValidAfterFieldCleared(self): - # Map needs to work even if field is cleared. - # For the C++ implementation this tests the correctness of - # MapContainer::Release() - msg = map_unittest_pb2.TestMap() - int32_foreign_message = msg.map_int32_foreign_message - - int32_foreign_message[2].c = 5 - - msg.ClearField('map_int32_foreign_message') - self.assertEqual(b'', msg.SerializeToString()) - self.assertTrue(2 in int32_foreign_message.keys()) - - def testMessageMapItemValidAfterTopMessageCleared(self): - # Message map item needs to work even if it is cleared. - # For the C++ implementation this tests the correctness of - # MapContainer::Release() - msg = map_unittest_pb2.TestMap() - msg.map_int32_all_types[2].optional_string = 'bar' - - if api_implementation.Type() == 'cpp': - # Need to keep the map reference because of b/27942626. - # TODO(jieluo): Remove it. - unused_map = msg.map_int32_all_types # pylint: disable=unused-variable - msg_value = msg.map_int32_all_types[2] - msg.Clear() - - # Reset to trigger sync between repeated field and map in c++. - msg.map_int32_all_types[3].optional_string = 'foo' - self.assertEqual(msg_value.optional_string, 'bar') - - def testMapIterInvalidatedByClearField(self): - # Map iterator is invalidated when field is cleared. - # But this case does need to not crash the interpreter. - # For the C++ implementation this tests the correctness of - # ScalarMapContainer::Release() - msg = map_unittest_pb2.TestMap() - - it = iter(msg.map_int32_int32) - - msg.ClearField('map_int32_int32') - with self.assertRaises(RuntimeError): - for _ in it: - pass - - it = iter(msg.map_int32_foreign_message) - msg.ClearField('map_int32_foreign_message') - with self.assertRaises(RuntimeError): - for _ in it: - pass - - def testMapDelete(self): - msg = map_unittest_pb2.TestMap() - - self.assertEqual(0, len(msg.map_int32_int32)) - - msg.map_int32_int32[4] = 6 - self.assertEqual(1, len(msg.map_int32_int32)) - - with self.assertRaises(KeyError): - del msg.map_int32_int32[88] - - del msg.map_int32_int32[4] - self.assertEqual(0, len(msg.map_int32_int32)) - - with self.assertRaises(KeyError): - del msg.map_int32_all_types[32] - - def testMapsAreMapping(self): - msg = map_unittest_pb2.TestMap() - self.assertIsInstance(msg.map_int32_int32, collections.abc.Mapping) - self.assertIsInstance(msg.map_int32_int32, collections.abc.MutableMapping) - self.assertIsInstance(msg.map_int32_foreign_message, - collections.abc.Mapping) - self.assertIsInstance(msg.map_int32_foreign_message, - collections.abc.MutableMapping) - - def testMapsCompare(self): - msg = map_unittest_pb2.TestMap() - msg.map_int32_int32[-123] = -456 - self.assertEqual(msg.map_int32_int32, msg.map_int32_int32) - self.assertEqual(msg.map_int32_foreign_message, - msg.map_int32_foreign_message) - self.assertNotEqual(msg.map_int32_int32, 0) - - def testMapFindInitializationErrorsSmokeTest(self): - msg = map_unittest_pb2.TestMap() - msg.map_string_string['abc'] = '123' - msg.map_int32_int32[35] = 64 - msg.map_string_foreign_message['foo'].c = 5 - self.assertEqual(0, len(msg.FindInitializationErrors())) - - @unittest.skipIf(sys.maxunicode == UCS2_MAXUNICODE, 'Skip for ucs2') - def testStrictUtf8Check(self): - # Test u'\ud801' is rejected at parser in both python2 and python3. - serialized = (b'r\x03\xed\xa0\x81') - msg = unittest_proto3_arena_pb2.TestAllTypes() - with self.assertRaises(Exception) as context: - msg.MergeFromString(serialized) - if api_implementation.Type() == 'python': - self.assertIn('optional_string', str(context.exception)) - else: - self.assertIn('Error parsing message', str(context.exception)) - - # Test optional_string=u'😍' is accepted. - serialized = unittest_proto3_arena_pb2.TestAllTypes( - optional_string=u'😍').SerializeToString() - msg2 = unittest_proto3_arena_pb2.TestAllTypes() - msg2.MergeFromString(serialized) - self.assertEqual(msg2.optional_string, u'😍') - - msg = unittest_proto3_arena_pb2.TestAllTypes(optional_string=u'\ud001') - self.assertEqual(msg.optional_string, u'\ud001') - - def testSurrogatesInPython3(self): - # Surrogates are rejected at setters in Python3. - with self.assertRaises(ValueError): - unittest_proto3_arena_pb2.TestAllTypes(optional_string=u'\ud801\udc01') - with self.assertRaises(ValueError): - unittest_proto3_arena_pb2.TestAllTypes(optional_string=b'\xed\xa0\x81') - with self.assertRaises(ValueError): - unittest_proto3_arena_pb2.TestAllTypes(optional_string=u'\ud801') - with self.assertRaises(ValueError): - unittest_proto3_arena_pb2.TestAllTypes(optional_string=u'\ud801\ud801') - - - - -@testing_refleaks.TestCase -class ValidTypeNamesTest(unittest.TestCase): - - def assertImportFromName(self, msg, base_name): - # Parse to extra 'some.name' as a string. - tp_name = str(type(msg)).split("'")[1] - valid_names = ('Repeated%sContainer' % base_name, - 'Repeated%sFieldContainer' % base_name) - self.assertTrue( - any(tp_name.endswith(v) for v in valid_names), - '%r does end with any of %r' % (tp_name, valid_names)) - - parts = tp_name.split('.') - class_name = parts[-1] - module_name = '.'.join(parts[:-1]) - __import__(module_name, fromlist=[class_name]) - - def testTypeNamesCanBeImported(self): - # If import doesn't work, pickling won't work either. - pb = unittest_pb2.TestAllTypes() - self.assertImportFromName(pb.repeated_int32, 'Scalar') - self.assertImportFromName(pb.repeated_nested_message, 'Composite') - - -@testing_refleaks.TestCase -class PackedFieldTest(unittest.TestCase): - - def setMessage(self, message): - message.repeated_int32.append(1) - message.repeated_int64.append(1) - message.repeated_uint32.append(1) - message.repeated_uint64.append(1) - message.repeated_sint32.append(1) - message.repeated_sint64.append(1) - message.repeated_fixed32.append(1) - message.repeated_fixed64.append(1) - message.repeated_sfixed32.append(1) - message.repeated_sfixed64.append(1) - message.repeated_float.append(1.0) - message.repeated_double.append(1.0) - message.repeated_bool.append(True) - message.repeated_nested_enum.append(1) - - def testPackedFields(self): - message = packed_field_test_pb2.TestPackedTypes() - self.setMessage(message) - golden_data = (b'\x0A\x01\x01' - b'\x12\x01\x01' - b'\x1A\x01\x01' - b'\x22\x01\x01' - b'\x2A\x01\x02' - b'\x32\x01\x02' - b'\x3A\x04\x01\x00\x00\x00' - b'\x42\x08\x01\x00\x00\x00\x00\x00\x00\x00' - b'\x4A\x04\x01\x00\x00\x00' - b'\x52\x08\x01\x00\x00\x00\x00\x00\x00\x00' - b'\x5A\x04\x00\x00\x80\x3f' - b'\x62\x08\x00\x00\x00\x00\x00\x00\xf0\x3f' - b'\x6A\x01\x01' - b'\x72\x01\x01') - self.assertEqual(golden_data, message.SerializeToString()) - - def testUnpackedFields(self): - message = packed_field_test_pb2.TestUnpackedTypes() - self.setMessage(message) - golden_data = (b'\x08\x01' - b'\x10\x01' - b'\x18\x01' - b'\x20\x01' - b'\x28\x02' - b'\x30\x02' - b'\x3D\x01\x00\x00\x00' - b'\x41\x01\x00\x00\x00\x00\x00\x00\x00' - b'\x4D\x01\x00\x00\x00' - b'\x51\x01\x00\x00\x00\x00\x00\x00\x00' - b'\x5D\x00\x00\x80\x3f' - b'\x61\x00\x00\x00\x00\x00\x00\xf0\x3f' - b'\x68\x01' - b'\x70\x01') - self.assertEqual(golden_data, message.SerializeToString()) - - -@unittest.skipIf(api_implementation.Type() == 'python', - 'explicit tests of the C++ implementation') -@testing_refleaks.TestCase -class OversizeProtosTest(unittest.TestCase): - - def GenerateNestedProto(self, n): - msg = unittest_pb2.TestRecursiveMessage() - sub = msg - for _ in range(n): - sub = sub.a - sub.i = 0 - return msg.SerializeToString() - - def testSucceedOkSizedProto(self): - msg = unittest_pb2.TestRecursiveMessage() - msg.ParseFromString(self.GenerateNestedProto(100)) - - def testAssertOversizeProto(self): - api_implementation._c_module.SetAllowOversizeProtos(False) - msg = unittest_pb2.TestRecursiveMessage() - with self.assertRaises(message.DecodeError) as context: - msg.ParseFromString(self.GenerateNestedProto(101)) - self.assertIn('Error parsing message', str(context.exception)) - - def testSucceedOversizeProto(self): - api_implementation._c_module.SetAllowOversizeProtos(True) - msg = unittest_pb2.TestRecursiveMessage() - msg.ParseFromString(self.GenerateNestedProto(101)) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/proto_builder_test.py b/ext/protobuf/Python/google/protobuf/internal/proto_builder_test.py deleted file mode 100644 index 48077b0a4..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/proto_builder_test.py +++ /dev/null @@ -1,106 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.proto_builder.""" - -import collections -import unittest - -from google.protobuf import descriptor_pb2 # pylint: disable=g-import-not-at-top -from google.protobuf import descriptor -from google.protobuf import descriptor_pool -from google.protobuf import proto_builder -from google.protobuf import text_format - - -class ProtoBuilderTest(unittest.TestCase): - - def setUp(self): - self.ordered_fields = collections.OrderedDict([ - ('foo', descriptor_pb2.FieldDescriptorProto.TYPE_INT64), - ('bar', descriptor_pb2.FieldDescriptorProto.TYPE_STRING), - ]) - self._fields = dict(self.ordered_fields) - - def testMakeSimpleProtoClass(self): - """Test that we can create a proto class.""" - proto_cls = proto_builder.MakeSimpleProtoClass( - self._fields, - full_name='net.proto2.python.public.proto_builder_test.Test') - proto = proto_cls() - proto.foo = 12345 - proto.bar = 'asdf' - self.assertMultiLineEqual( - 'bar: "asdf"\nfoo: 12345\n', text_format.MessageToString(proto)) - - def testOrderedFields(self): - """Test that the field order is maintained when given an OrderedDict.""" - proto_cls = proto_builder.MakeSimpleProtoClass( - self.ordered_fields, - full_name='net.proto2.python.public.proto_builder_test.OrderedTest') - proto = proto_cls() - proto.foo = 12345 - proto.bar = 'asdf' - self.assertMultiLineEqual( - 'foo: 12345\nbar: "asdf"\n', text_format.MessageToString(proto)) - - def testMakeSameProtoClassTwice(self): - """Test that the DescriptorPool is used.""" - pool = descriptor_pool.DescriptorPool() - proto_cls1 = proto_builder.MakeSimpleProtoClass( - self._fields, - full_name='net.proto2.python.public.proto_builder_test.Test', - pool=pool) - proto_cls2 = proto_builder.MakeSimpleProtoClass( - self._fields, - full_name='net.proto2.python.public.proto_builder_test.Test', - pool=pool) - self.assertIs(proto_cls1.DESCRIPTOR, proto_cls2.DESCRIPTOR) - - def testMakeLargeProtoClass(self): - """Test that large created protos don't use reserved field numbers.""" - num_fields = 123456 - fields = { - 'foo%d' % i: descriptor_pb2.FieldDescriptorProto.TYPE_INT64 - for i in range(num_fields) - } - proto_cls = proto_builder.MakeSimpleProtoClass( - fields, - full_name='net.proto2.python.public.proto_builder_test.LargeProtoTest') - - reserved_field_numbers = set( - range(descriptor.FieldDescriptor.FIRST_RESERVED_FIELD_NUMBER, - descriptor.FieldDescriptor.LAST_RESERVED_FIELD_NUMBER + 1)) - proto_field_numbers = set(proto_cls.DESCRIPTOR.fields_by_number) - self.assertFalse(reserved_field_numbers.intersection(proto_field_numbers)) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/python_message.py b/ext/protobuf/Python/google/protobuf/internal/python_message.py index 5550b425c..bf9acefd2 100644 --- a/ext/protobuf/Python/google/protobuf/internal/python_message.py +++ b/ext/protobuf/Python/google/protobuf/internal/python_message.py @@ -283,20 +283,8 @@ def _IsMessageMapField(field): def _AttachFieldHelpers(cls, field_descriptor): is_repeated = (field_descriptor.label == _FieldDescriptor.LABEL_REPEATED) - is_packable = (is_repeated and - wire_format.IsTypePackable(field_descriptor.type)) - is_proto3 = field_descriptor.containing_type.syntax == 'proto3' - if not is_packable: - is_packed = False - elif field_descriptor.containing_type.syntax == 'proto2': - is_packed = (field_descriptor.has_options and - field_descriptor.GetOptions().packed) - else: - has_packed_false = (field_descriptor.has_options and - field_descriptor.GetOptions().HasField('packed') and - field_descriptor.GetOptions().packed == False) - is_packed = not has_packed_false is_map_entry = _IsMapField(field_descriptor) + is_packed = field_descriptor.is_packed if is_map_entry: field_encoder = encoder.MapEncoder(field_descriptor) @@ -320,16 +308,12 @@ def AddDecoder(wiretype, is_packed): tag_bytes = encoder.TagBytes(field_descriptor.number, wiretype) decode_type = field_descriptor.type if (decode_type == _FieldDescriptor.TYPE_ENUM and - type_checkers.SupportsOpenEnums(field_descriptor)): + not field_descriptor.enum_type.is_closed): decode_type = _FieldDescriptor.TYPE_INT32 oneof_descriptor = None - clear_if_default = False if field_descriptor.containing_oneof is not None: oneof_descriptor = field_descriptor - elif (is_proto3 and not is_repeated and - field_descriptor.cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE): - clear_if_default = True if is_map_entry: is_message_map = _IsMessageMapField(field_descriptor) @@ -341,7 +325,7 @@ def AddDecoder(wiretype, is_packed): field_decoder = decoder.StringDecoder( field_descriptor.number, is_repeated, is_packed, field_descriptor, field_descriptor._default_constructor, - clear_if_default) + not field_descriptor.has_presence) elif field_descriptor.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: field_decoder = type_checkers.TYPE_TO_DECODER[decode_type]( field_descriptor.number, is_repeated, is_packed, @@ -351,7 +335,7 @@ def AddDecoder(wiretype, is_packed): field_descriptor.number, is_repeated, is_packed, # pylint: disable=protected-access field_descriptor, field_descriptor._default_constructor, - clear_if_default) + not field_descriptor.has_presence) cls._decoders_by_tag[tag_bytes] = (field_decoder, oneof_descriptor) @@ -683,7 +667,6 @@ def _AddPropertiesForNonRepeatedScalarField(field, cls): property_name = _PropertyName(proto_field_name) type_checker = type_checkers.GetTypeChecker(field) default_value = field.default_value - is_proto3 = field.containing_type.syntax == 'proto3' def getter(self): # TODO(protobuf-team): This may be broken since there may not be @@ -692,8 +675,6 @@ def getter(self): getter.__module__ = None getter.__doc__ = 'Getter for %s.' % proto_field_name - clear_when_set_to_default = is_proto3 and not field.containing_oneof - def field_setter(self, new_value): # pylint: disable=protected-access # Testing the value for truthiness captures all of the proto3 defaults @@ -703,7 +684,7 @@ def field_setter(self, new_value): except TypeError as e: raise TypeError( 'Cannot set %s to %.1024r: %s' % (field.full_name, new_value, e)) - if clear_when_set_to_default and not new_value: + if not field.has_presence and not new_value: self._fields.pop(field, None) else: self._fields[field] = new_value @@ -788,12 +769,12 @@ def _AddPropertiesForExtensions(descriptor, cls): def _AddStaticMethods(cls): # TODO(robinson): This probably needs to be thread-safe(?) - def RegisterExtension(extension_handle): - extension_handle.containing_type = cls.DESCRIPTOR + def RegisterExtension(field_descriptor): + field_descriptor.containing_type = cls.DESCRIPTOR # TODO(amauryfa): Use cls.MESSAGE_FACTORY.pool when available. # pylint: disable=protected-access - cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(extension_handle) - _AttachFieldHelpers(cls, extension_handle) + cls.DESCRIPTOR.file.pool._AddExtensionDescriptor(field_descriptor) + _AttachFieldHelpers(cls, field_descriptor) cls.RegisterExtension = staticmethod(RegisterExtension) def FromString(s): @@ -825,24 +806,16 @@ def ListFields(self): cls.ListFields = ListFields -_PROTO3_ERROR_TEMPLATE = \ - ('Protocol message %s has no non-repeated submessage field "%s" ' - 'nor marked as optional') -_PROTO2_ERROR_TEMPLATE = 'Protocol message %s has no non-repeated field "%s"' def _AddHasFieldMethod(message_descriptor, cls): """Helper for _AddMessageMethods().""" - is_proto3 = (message_descriptor.syntax == "proto3") - error_msg = _PROTO3_ERROR_TEMPLATE if is_proto3 else _PROTO2_ERROR_TEMPLATE - hassable_fields = {} for field in message_descriptor.fields: if field.label == _FieldDescriptor.LABEL_REPEATED: continue # For proto3, only submessages and fields inside a oneof have presence. - if (is_proto3 and field.cpp_type != _FieldDescriptor.CPPTYPE_MESSAGE and - not field.containing_oneof): + if not field.has_presence: continue hassable_fields[field.name] = field @@ -853,8 +826,10 @@ def _AddHasFieldMethod(message_descriptor, cls): def HasField(self, field_name): try: field = hassable_fields[field_name] - except KeyError: - raise ValueError(error_msg % (message_descriptor.full_name, field_name)) + except KeyError as exc: + raise ValueError('Protocol message %s has no non-repeated field "%s" ' + 'nor has presence is not available for this field.' % ( + message_descriptor.full_name, field_name)) from exc if isinstance(field, descriptor_mod.OneofDescriptor): try: @@ -911,28 +886,28 @@ def ClearField(self, field_name): def _AddClearExtensionMethod(cls): """Helper for _AddMessageMethods().""" - def ClearExtension(self, extension_handle): - extension_dict._VerifyExtensionHandle(self, extension_handle) + def ClearExtension(self, field_descriptor): + extension_dict._VerifyExtensionHandle(self, field_descriptor) # Similar to ClearField(), above. - if extension_handle in self._fields: - del self._fields[extension_handle] + if field_descriptor in self._fields: + del self._fields[field_descriptor] self._Modified() cls.ClearExtension = ClearExtension def _AddHasExtensionMethod(cls): """Helper for _AddMessageMethods().""" - def HasExtension(self, extension_handle): - extension_dict._VerifyExtensionHandle(self, extension_handle) - if extension_handle.label == _FieldDescriptor.LABEL_REPEATED: - raise KeyError('"%s" is repeated.' % extension_handle.full_name) + def HasExtension(self, field_descriptor): + extension_dict._VerifyExtensionHandle(self, field_descriptor) + if field_descriptor.label == _FieldDescriptor.LABEL_REPEATED: + raise KeyError('"%s" is repeated.' % field_descriptor.full_name) - if extension_handle.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: - value = self._fields.get(extension_handle) + if field_descriptor.cpp_type == _FieldDescriptor.CPPTYPE_MESSAGE: + value = self._fields.get(field_descriptor) return value is not None and value._is_present_in_parent else: - return extension_handle in self._fields + return field_descriptor in self._fields cls.HasExtension = HasExtension def _InternalUnpackAny(msg): diff --git a/ext/protobuf/Python/google/protobuf/internal/reflection_test.py b/ext/protobuf/Python/google/protobuf/internal/reflection_test.py deleted file mode 100644 index 62957d3fd..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/reflection_test.py +++ /dev/null @@ -1,3381 +0,0 @@ -# -*- coding: utf-8 -*- -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Unittest for reflection.py, which also indirectly tests the output of the -pure-Python protocol compiler. -""" - -import copy -import gc -import operator -import struct -import sys -import warnings -import unittest - -from google.protobuf import unittest_import_pb2 -from google.protobuf import unittest_mset_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import unittest_proto3_arena_pb2 -from google.protobuf import descriptor_pb2 -from google.protobuf import descriptor -from google.protobuf import message -from google.protobuf import reflection -from google.protobuf import text_format -from google.protobuf.internal import api_implementation -from google.protobuf.internal import more_extensions_pb2 -from google.protobuf.internal import more_messages_pb2 -from google.protobuf.internal import message_set_extensions_pb2 -from google.protobuf.internal import wire_format -from google.protobuf.internal import test_util -from google.protobuf.internal import testing_refleaks -from google.protobuf.internal import decoder -from google.protobuf.internal import _parameterized - - -warnings.simplefilter('error', DeprecationWarning) - - -class _MiniDecoder(object): - """Decodes a stream of values from a string. - - Once upon a time we actually had a class called decoder.Decoder. Then we - got rid of it during a redesign that made decoding much, much faster overall. - But a couple tests in this file used it to check that the serialized form of - a message was correct. So, this class implements just the methods that were - used by said tests, so that we don't have to rewrite the tests. - """ - - def __init__(self, bytes): - self._bytes = bytes - self._pos = 0 - - def ReadVarint(self): - result, self._pos = decoder._DecodeVarint(self._bytes, self._pos) - return result - - ReadInt32 = ReadVarint - ReadInt64 = ReadVarint - ReadUInt32 = ReadVarint - ReadUInt64 = ReadVarint - - def ReadSInt64(self): - return wire_format.ZigZagDecode(self.ReadVarint()) - - ReadSInt32 = ReadSInt64 - - def ReadFieldNumberAndWireType(self): - return wire_format.UnpackTag(self.ReadVarint()) - - def ReadFloat(self): - result = struct.unpack('= (3, 10)): - self.assertRaises(TypeError, setattr, proto, 'optional_bool', 1.1) - else: - proto.optional_bool = 1.1 - - def assertIntegerTypes(self, integer_fn, message_module): - """Verifies setting of scalar integers. - - Args: - integer_fn: A function to wrap the integers that will be assigned. - message_module: unittest_pb2 or unittest_proto3_arena_pb2 - """ - def TestGetAndDeserialize(field_name, value, expected_type): - proto = message_module.TestAllTypes() - value = integer_fn(value) - setattr(proto, field_name, value) - self.assertIsInstance(getattr(proto, field_name), expected_type) - proto2 = message_module.TestAllTypes() - proto2.ParseFromString(proto.SerializeToString()) - self.assertIsInstance(getattr(proto2, field_name), expected_type) - - TestGetAndDeserialize('optional_int32', 1, int) - TestGetAndDeserialize('optional_int32', 1 << 30, int) - TestGetAndDeserialize('optional_uint32', 1 << 30, int) - integer_64 = int - if struct.calcsize('L') == 4: - # Python only has signed ints, so 32-bit python can't fit an uint32 - # in an int. - TestGetAndDeserialize('optional_uint32', 1 << 31, integer_64) - else: - # 64-bit python can fit uint32 inside an int - TestGetAndDeserialize('optional_uint32', 1 << 31, int) - TestGetAndDeserialize('optional_int64', 1 << 30, integer_64) - TestGetAndDeserialize('optional_int64', 1 << 60, integer_64) - TestGetAndDeserialize('optional_uint64', 1 << 30, integer_64) - TestGetAndDeserialize('optional_uint64', 1 << 60, integer_64) - - def testIntegerTypes(self, message_module): - self.assertIntegerTypes(lambda x: x, message_module) - - def testNonStandardIntegerTypes(self, message_module): - self.assertIntegerTypes(test_util.NonStandardInteger, message_module) - - def testIllegalValuesForIntegers(self, message_module): - pb = message_module.TestAllTypes() - - # Strings are illegal, even when the represent an integer. - with self.assertRaises(TypeError): - pb.optional_uint64 = '2' - - # The exact error should propagate with a poorly written custom integer. - with self.assertRaisesRegex(RuntimeError, 'my_error'): - pb.optional_uint64 = test_util.NonStandardInteger(5, 'my_error') - - def assetIntegerBoundsChecking(self, integer_fn, message_module): - """Verifies bounds checking for scalar integer fields. - - Args: - integer_fn: A function to wrap the integers that will be assigned. - message_module: unittest_pb2 or unittest_proto3_arena_pb2 - """ - def TestMinAndMaxIntegers(field_name, expected_min, expected_max): - pb = message_module.TestAllTypes() - expected_min = integer_fn(expected_min) - expected_max = integer_fn(expected_max) - setattr(pb, field_name, expected_min) - self.assertEqual(expected_min, getattr(pb, field_name)) - setattr(pb, field_name, expected_max) - self.assertEqual(expected_max, getattr(pb, field_name)) - self.assertRaises((ValueError, TypeError), setattr, pb, field_name, - expected_min - 1) - self.assertRaises((ValueError, TypeError), setattr, pb, field_name, - expected_max + 1) - - TestMinAndMaxIntegers('optional_int32', -(1 << 31), (1 << 31) - 1) - TestMinAndMaxIntegers('optional_uint32', 0, 0xffffffff) - TestMinAndMaxIntegers('optional_int64', -(1 << 63), (1 << 63) - 1) - TestMinAndMaxIntegers('optional_uint64', 0, 0xffffffffffffffff) - # A bit of white-box testing since -1 is an int and not a long in C++ and - # so goes down a different path. - pb = message_module.TestAllTypes() - with self.assertRaises((ValueError, TypeError)): - pb.optional_uint64 = integer_fn(-(1 << 63)) - - pb = message_module.TestAllTypes() - pb.optional_nested_enum = integer_fn(1) - self.assertEqual(1, pb.optional_nested_enum) - - def testSingleScalarBoundsChecking(self, message_module): - self.assetIntegerBoundsChecking(lambda x: x, message_module) - - def testNonStandardSingleScalarBoundsChecking(self, message_module): - self.assetIntegerBoundsChecking( - test_util.NonStandardInteger, message_module) - - def testRepeatedScalarTypeSafety(self, message_module): - proto = message_module.TestAllTypes() - self.assertRaises(TypeError, proto.repeated_int32.append, 1.1) - self.assertRaises(TypeError, proto.repeated_int32.append, 'foo') - self.assertRaises(TypeError, proto.repeated_string, 10) - self.assertRaises(TypeError, proto.repeated_bytes, 10) - - proto.repeated_int32.append(10) - proto.repeated_int32[0] = 23 - self.assertRaises(IndexError, proto.repeated_int32.__setitem__, 500, 23) - self.assertRaises(TypeError, proto.repeated_int32.__setitem__, 0, 'abc') - self.assertRaises(TypeError, proto.repeated_int32.__setitem__, 0, []) - self.assertRaises(TypeError, proto.repeated_int32.__setitem__, - 'index', 23) - - proto.repeated_string.append('2') - self.assertRaises(TypeError, proto.repeated_string.__setitem__, 0, 10) - - # Repeated enums tests. - #proto.repeated_nested_enum.append(0) - - def testSingleScalarGettersAndSetters(self, message_module): - proto = message_module.TestAllTypes() - self.assertEqual(0, proto.optional_int32) - proto.optional_int32 = 1 - self.assertEqual(1, proto.optional_int32) - - proto.optional_uint64 = 0xffffffffffff - self.assertEqual(0xffffffffffff, proto.optional_uint64) - proto.optional_uint64 = 0xffffffffffffffff - self.assertEqual(0xffffffffffffffff, proto.optional_uint64) - # TODO(robinson): Test all other scalar field types. - - def testEnums(self, message_module): - proto = message_module.TestAllTypes() - self.assertEqual(1, proto.FOO) - self.assertEqual(1, message_module.TestAllTypes.FOO) - self.assertEqual(2, proto.BAR) - self.assertEqual(2, message_module.TestAllTypes.BAR) - self.assertEqual(3, proto.BAZ) - self.assertEqual(3, message_module.TestAllTypes.BAZ) - - def testEnum_Name(self, message_module): - self.assertEqual( - 'FOREIGN_FOO', - message_module.ForeignEnum.Name(message_module.FOREIGN_FOO)) - self.assertEqual( - 'FOREIGN_BAR', - message_module.ForeignEnum.Name(message_module.FOREIGN_BAR)) - self.assertEqual( - 'FOREIGN_BAZ', - message_module.ForeignEnum.Name(message_module.FOREIGN_BAZ)) - self.assertRaises(ValueError, - message_module.ForeignEnum.Name, 11312) - - proto = message_module.TestAllTypes() - self.assertEqual('FOO', - proto.NestedEnum.Name(proto.FOO)) - self.assertEqual('FOO', - message_module.TestAllTypes.NestedEnum.Name(proto.FOO)) - self.assertEqual('BAR', - proto.NestedEnum.Name(proto.BAR)) - self.assertEqual('BAR', - message_module.TestAllTypes.NestedEnum.Name(proto.BAR)) - self.assertEqual('BAZ', - proto.NestedEnum.Name(proto.BAZ)) - self.assertEqual('BAZ', - message_module.TestAllTypes.NestedEnum.Name(proto.BAZ)) - self.assertRaises(ValueError, - proto.NestedEnum.Name, 11312) - self.assertRaises(ValueError, - message_module.TestAllTypes.NestedEnum.Name, 11312) - - # Check some coercion cases. - self.assertRaises(TypeError, message_module.TestAllTypes.NestedEnum.Name, - 11312.0) - self.assertRaises(TypeError, message_module.TestAllTypes.NestedEnum.Name, - None) - self.assertEqual('FOO', message_module.TestAllTypes.NestedEnum.Name(True)) - - def testEnum_Value(self, message_module): - self.assertEqual(message_module.FOREIGN_FOO, - message_module.ForeignEnum.Value('FOREIGN_FOO')) - self.assertEqual(message_module.FOREIGN_FOO, - message_module.ForeignEnum.FOREIGN_FOO) - - self.assertEqual(message_module.FOREIGN_BAR, - message_module.ForeignEnum.Value('FOREIGN_BAR')) - self.assertEqual(message_module.FOREIGN_BAR, - message_module.ForeignEnum.FOREIGN_BAR) - - self.assertEqual(message_module.FOREIGN_BAZ, - message_module.ForeignEnum.Value('FOREIGN_BAZ')) - self.assertEqual(message_module.FOREIGN_BAZ, - message_module.ForeignEnum.FOREIGN_BAZ) - - self.assertRaises(ValueError, - message_module.ForeignEnum.Value, 'FO') - with self.assertRaises(AttributeError): - message_module.ForeignEnum.FO - - proto = message_module.TestAllTypes() - self.assertEqual(proto.FOO, - proto.NestedEnum.Value('FOO')) - self.assertEqual(proto.FOO, - proto.NestedEnum.FOO) - - self.assertEqual(proto.FOO, - message_module.TestAllTypes.NestedEnum.Value('FOO')) - self.assertEqual(proto.FOO, - message_module.TestAllTypes.NestedEnum.FOO) - - self.assertEqual(proto.BAR, - proto.NestedEnum.Value('BAR')) - self.assertEqual(proto.BAR, - proto.NestedEnum.BAR) - - self.assertEqual(proto.BAR, - message_module.TestAllTypes.NestedEnum.Value('BAR')) - self.assertEqual(proto.BAR, - message_module.TestAllTypes.NestedEnum.BAR) - - self.assertEqual(proto.BAZ, - proto.NestedEnum.Value('BAZ')) - self.assertEqual(proto.BAZ, - proto.NestedEnum.BAZ) - - self.assertEqual(proto.BAZ, - message_module.TestAllTypes.NestedEnum.Value('BAZ')) - self.assertEqual(proto.BAZ, - message_module.TestAllTypes.NestedEnum.BAZ) - - self.assertRaises(ValueError, - proto.NestedEnum.Value, 'Foo') - with self.assertRaises(AttributeError): - proto.NestedEnum.Value.Foo - - self.assertRaises(ValueError, - message_module.TestAllTypes.NestedEnum.Value, 'Foo') - with self.assertRaises(AttributeError): - message_module.TestAllTypes.NestedEnum.Value.Foo - - def testEnum_KeysAndValues(self, message_module): - if message_module == unittest_pb2: - keys = ['FOREIGN_FOO', 'FOREIGN_BAR', 'FOREIGN_BAZ'] - values = [4, 5, 6] - items = [('FOREIGN_FOO', 4), ('FOREIGN_BAR', 5), ('FOREIGN_BAZ', 6)] - else: - keys = ['FOREIGN_ZERO', 'FOREIGN_FOO', 'FOREIGN_BAR', 'FOREIGN_BAZ'] - values = [0, 4, 5, 6] - items = [('FOREIGN_ZERO', 0), ('FOREIGN_FOO', 4), - ('FOREIGN_BAR', 5), ('FOREIGN_BAZ', 6)] - self.assertEqual(keys, - list(message_module.ForeignEnum.keys())) - self.assertEqual(values, - list(message_module.ForeignEnum.values())) - self.assertEqual(items, - list(message_module.ForeignEnum.items())) - - proto = message_module.TestAllTypes() - if message_module == unittest_pb2: - keys = ['FOO', 'BAR', 'BAZ', 'NEG'] - values = [1, 2, 3, -1] - items = [('FOO', 1), ('BAR', 2), ('BAZ', 3), ('NEG', -1)] - else: - keys = ['ZERO', 'FOO', 'BAR', 'BAZ', 'NEG'] - values = [0, 1, 2, 3, -1] - items = [('ZERO', 0), ('FOO', 1), ('BAR', 2), ('BAZ', 3), ('NEG', -1)] - self.assertEqual(keys, list(proto.NestedEnum.keys())) - self.assertEqual(values, list(proto.NestedEnum.values())) - self.assertEqual(items, - list(proto.NestedEnum.items())) - - def testStaticParseFrom(self, message_module): - proto1 = message_module.TestAllTypes() - test_util.SetAllFields(proto1) - - string1 = proto1.SerializeToString() - proto2 = message_module.TestAllTypes.FromString(string1) - - # Messages should be equal. - self.assertEqual(proto2, proto1) - - def testMergeFromSingularField(self, message_module): - # Test merge with just a singular field. - proto1 = message_module.TestAllTypes() - proto1.optional_int32 = 1 - - proto2 = message_module.TestAllTypes() - # This shouldn't get overwritten. - proto2.optional_string = 'value' - - proto2.MergeFrom(proto1) - self.assertEqual(1, proto2.optional_int32) - self.assertEqual('value', proto2.optional_string) - - def testMergeFromRepeatedField(self, message_module): - # Test merge with just a repeated field. - proto1 = message_module.TestAllTypes() - proto1.repeated_int32.append(1) - proto1.repeated_int32.append(2) - - proto2 = message_module.TestAllTypes() - proto2.repeated_int32.append(0) - proto2.MergeFrom(proto1) - - self.assertEqual(0, proto2.repeated_int32[0]) - self.assertEqual(1, proto2.repeated_int32[1]) - self.assertEqual(2, proto2.repeated_int32[2]) - - def testMergeFromRepeatedNestedMessage(self, message_module): - # Test merge with a repeated nested message. - proto1 = message_module.TestAllTypes() - m = proto1.repeated_nested_message.add() - m.bb = 123 - m = proto1.repeated_nested_message.add() - m.bb = 321 - - proto2 = message_module.TestAllTypes() - m = proto2.repeated_nested_message.add() - m.bb = 999 - proto2.MergeFrom(proto1) - self.assertEqual(999, proto2.repeated_nested_message[0].bb) - self.assertEqual(123, proto2.repeated_nested_message[1].bb) - self.assertEqual(321, proto2.repeated_nested_message[2].bb) - - proto3 = message_module.TestAllTypes() - proto3.repeated_nested_message.MergeFrom(proto2.repeated_nested_message) - self.assertEqual(999, proto3.repeated_nested_message[0].bb) - self.assertEqual(123, proto3.repeated_nested_message[1].bb) - self.assertEqual(321, proto3.repeated_nested_message[2].bb) - - def testMergeFromAllFields(self, message_module): - # With all fields set. - proto1 = message_module.TestAllTypes() - test_util.SetAllFields(proto1) - proto2 = message_module.TestAllTypes() - proto2.MergeFrom(proto1) - - # Messages should be equal. - self.assertEqual(proto2, proto1) - - # Serialized string should be equal too. - string1 = proto1.SerializeToString() - string2 = proto2.SerializeToString() - self.assertEqual(string1, string2) - - def testMergeFromBug(self, message_module): - message1 = message_module.TestAllTypes() - message2 = message_module.TestAllTypes() - - # Cause optional_nested_message to be instantiated within message1, even - # though it is not considered to be "present". - message1.optional_nested_message - self.assertFalse(message1.HasField('optional_nested_message')) - - # Merge into message2. This should not instantiate the field is message2. - message2.MergeFrom(message1) - self.assertFalse(message2.HasField('optional_nested_message')) - - def testCopyFromSingularField(self, message_module): - # Test copy with just a singular field. - proto1 = message_module.TestAllTypes() - proto1.optional_int32 = 1 - proto1.optional_string = 'important-text' - - proto2 = message_module.TestAllTypes() - proto2.optional_string = 'value' - - proto2.CopyFrom(proto1) - self.assertEqual(1, proto2.optional_int32) - self.assertEqual('important-text', proto2.optional_string) - - def testCopyFromRepeatedField(self, message_module): - # Test copy with a repeated field. - proto1 = message_module.TestAllTypes() - proto1.repeated_int32.append(1) - proto1.repeated_int32.append(2) - - proto2 = message_module.TestAllTypes() - proto2.repeated_int32.append(0) - proto2.CopyFrom(proto1) - - self.assertEqual(1, proto2.repeated_int32[0]) - self.assertEqual(2, proto2.repeated_int32[1]) - - def testCopyFromAllFields(self, message_module): - # With all fields set. - proto1 = message_module.TestAllTypes() - test_util.SetAllFields(proto1) - proto2 = message_module.TestAllTypes() - proto2.CopyFrom(proto1) - - # Messages should be equal. - self.assertEqual(proto2, proto1) - - # Serialized string should be equal too. - string1 = proto1.SerializeToString() - string2 = proto2.SerializeToString() - self.assertEqual(string1, string2) - - def testCopyFromSelf(self, message_module): - proto1 = message_module.TestAllTypes() - proto1.repeated_int32.append(1) - proto1.optional_int32 = 2 - proto1.optional_string = 'important-text' - - proto1.CopyFrom(proto1) - self.assertEqual(1, proto1.repeated_int32[0]) - self.assertEqual(2, proto1.optional_int32) - self.assertEqual('important-text', proto1.optional_string) - - def testDeepCopy(self, message_module): - proto1 = message_module.TestAllTypes() - proto1.optional_int32 = 1 - proto2 = copy.deepcopy(proto1) - self.assertEqual(1, proto2.optional_int32) - - proto1.repeated_int32.append(2) - proto1.repeated_int32.append(3) - container = copy.deepcopy(proto1.repeated_int32) - self.assertEqual([2, 3], container) - container.remove(container[0]) - self.assertEqual([3], container) - - message1 = proto1.repeated_nested_message.add() - message1.bb = 1 - messages = copy.deepcopy(proto1.repeated_nested_message) - self.assertEqual(proto1.repeated_nested_message, messages) - message1.bb = 2 - self.assertNotEqual(proto1.repeated_nested_message, messages) - messages.remove(messages[0]) - self.assertEqual(len(messages), 0) - - # TODO(anuraag): Implement deepcopy for extension dict - - def testDisconnectingBeforeClear(self, message_module): - proto = message_module.TestAllTypes() - nested = proto.optional_nested_message - proto.Clear() - self.assertIsNot(nested, proto.optional_nested_message) - nested.bb = 23 - self.assertFalse(proto.HasField('optional_nested_message')) - self.assertEqual(0, proto.optional_nested_message.bb) - - proto = message_module.TestAllTypes() - nested = proto.optional_nested_message - nested.bb = 5 - foreign = proto.optional_foreign_message - foreign.c = 6 - proto.Clear() - self.assertIsNot(nested, proto.optional_nested_message) - self.assertIsNot(foreign, proto.optional_foreign_message) - self.assertEqual(5, nested.bb) - self.assertEqual(6, foreign.c) - nested.bb = 15 - foreign.c = 16 - self.assertFalse(proto.HasField('optional_nested_message')) - self.assertEqual(0, proto.optional_nested_message.bb) - self.assertFalse(proto.HasField('optional_foreign_message')) - self.assertEqual(0, proto.optional_foreign_message.c) - - def testStringUTF8Encoding(self, message_module): - proto = message_module.TestAllTypes() - - # Assignment of a unicode object to a field of type 'bytes' is not allowed. - self.assertRaises(TypeError, - setattr, proto, 'optional_bytes', u'unicode object') - - # Check that the default value is of python's 'unicode' type. - self.assertEqual(type(proto.optional_string), str) - - proto.optional_string = str('Testing') - self.assertEqual(proto.optional_string, str('Testing')) - - # Assign a value of type 'str' which can be encoded in UTF-8. - proto.optional_string = str('Testing') - self.assertEqual(proto.optional_string, str('Testing')) - - # Try to assign a 'bytes' object which contains non-UTF-8. - self.assertRaises(ValueError, - setattr, proto, 'optional_string', b'a\x80a') - # No exception: Assign already encoded UTF-8 bytes to a string field. - utf8_bytes = u'Тест'.encode('utf-8') - proto.optional_string = utf8_bytes - # No exception: Assign the a non-ascii unicode object. - proto.optional_string = u'Тест' - # No exception thrown (normal str assignment containing ASCII). - proto.optional_string = 'abc' - - def testBytesInTextFormat(self, message_module): - proto = message_module.TestAllTypes(optional_bytes=b'\x00\x7f\x80\xff') - self.assertEqual(u'optional_bytes: "\\000\\177\\200\\377"\n', str(proto)) - - def testEmptyNestedMessage(self, message_module): - proto = message_module.TestAllTypes() - proto.optional_nested_message.MergeFrom( - message_module.TestAllTypes.NestedMessage()) - self.assertTrue(proto.HasField('optional_nested_message')) - - proto = message_module.TestAllTypes() - proto.optional_nested_message.CopyFrom( - message_module.TestAllTypes.NestedMessage()) - self.assertTrue(proto.HasField('optional_nested_message')) - - proto = message_module.TestAllTypes() - bytes_read = proto.optional_nested_message.MergeFromString(b'') - self.assertEqual(0, bytes_read) - self.assertTrue(proto.HasField('optional_nested_message')) - - proto = message_module.TestAllTypes() - proto.optional_nested_message.ParseFromString(b'') - self.assertTrue(proto.HasField('optional_nested_message')) - - serialized = proto.SerializeToString() - proto2 = message_module.TestAllTypes() - self.assertEqual( - len(serialized), - proto2.MergeFromString(serialized)) - self.assertTrue(proto2.HasField('optional_nested_message')) - - -# Class to test proto2-only features (required, extensions, etc.) -@testing_refleaks.TestCase -class Proto2ReflectionTest(unittest.TestCase): - - def testRepeatedCompositeConstructor(self): - # Constructor with only repeated composite types should succeed. - proto = unittest_pb2.TestAllTypes( - repeated_nested_message=[ - unittest_pb2.TestAllTypes.NestedMessage( - bb=unittest_pb2.TestAllTypes.FOO), - unittest_pb2.TestAllTypes.NestedMessage( - bb=unittest_pb2.TestAllTypes.BAR)], - repeated_foreign_message=[ - unittest_pb2.ForeignMessage(c=-43), - unittest_pb2.ForeignMessage(c=45324), - unittest_pb2.ForeignMessage(c=12)], - repeatedgroup=[ - unittest_pb2.TestAllTypes.RepeatedGroup(), - unittest_pb2.TestAllTypes.RepeatedGroup(a=1), - unittest_pb2.TestAllTypes.RepeatedGroup(a=2)]) - - self.assertEqual( - [unittest_pb2.TestAllTypes.NestedMessage( - bb=unittest_pb2.TestAllTypes.FOO), - unittest_pb2.TestAllTypes.NestedMessage( - bb=unittest_pb2.TestAllTypes.BAR)], - list(proto.repeated_nested_message)) - self.assertEqual( - [unittest_pb2.ForeignMessage(c=-43), - unittest_pb2.ForeignMessage(c=45324), - unittest_pb2.ForeignMessage(c=12)], - list(proto.repeated_foreign_message)) - self.assertEqual( - [unittest_pb2.TestAllTypes.RepeatedGroup(), - unittest_pb2.TestAllTypes.RepeatedGroup(a=1), - unittest_pb2.TestAllTypes.RepeatedGroup(a=2)], - list(proto.repeatedgroup)) - - def assertListsEqual(self, values, others): - self.assertEqual(len(values), len(others)) - for i in range(len(values)): - self.assertEqual(values[i], others[i]) - - def testSimpleHasBits(self): - # Test a scalar. - proto = unittest_pb2.TestAllTypes() - self.assertFalse(proto.HasField('optional_int32')) - self.assertEqual(0, proto.optional_int32) - # HasField() shouldn't be true if all we've done is - # read the default value. - self.assertFalse(proto.HasField('optional_int32')) - proto.optional_int32 = 1 - # Setting a value however *should* set the "has" bit. - self.assertTrue(proto.HasField('optional_int32')) - proto.ClearField('optional_int32') - # And clearing that value should unset the "has" bit. - self.assertFalse(proto.HasField('optional_int32')) - - def testHasBitsWithSinglyNestedScalar(self): - # Helper used to test foreign messages and groups. - # - # composite_field_name should be the name of a non-repeated - # composite (i.e., foreign or group) field in TestAllTypes, - # and scalar_field_name should be the name of an integer-valued - # scalar field within that composite. - # - # I never thought I'd miss C++ macros and templates so much. :( - # This helper is semantically just: - # - # assert proto.composite_field.scalar_field == 0 - # assert not proto.composite_field.HasField('scalar_field') - # assert not proto.HasField('composite_field') - # - # proto.composite_field.scalar_field = 10 - # old_composite_field = proto.composite_field - # - # assert proto.composite_field.scalar_field == 10 - # assert proto.composite_field.HasField('scalar_field') - # assert proto.HasField('composite_field') - # - # proto.ClearField('composite_field') - # - # assert not proto.composite_field.HasField('scalar_field') - # assert not proto.HasField('composite_field') - # assert proto.composite_field.scalar_field == 0 - # - # # Now ensure that ClearField('composite_field') disconnected - # # the old field object from the object tree... - # assert old_composite_field is not proto.composite_field - # old_composite_field.scalar_field = 20 - # assert not proto.composite_field.HasField('scalar_field') - # assert not proto.HasField('composite_field') - def TestCompositeHasBits(composite_field_name, scalar_field_name): - proto = unittest_pb2.TestAllTypes() - # First, check that we can get the scalar value, and see that it's the - # default (0), but that proto.HasField('omposite') and - # proto.composite.HasField('scalar') will still return False. - composite_field = getattr(proto, composite_field_name) - original_scalar_value = getattr(composite_field, scalar_field_name) - self.assertEqual(0, original_scalar_value) - # Assert that the composite object does not "have" the scalar. - self.assertFalse(composite_field.HasField(scalar_field_name)) - # Assert that proto does not "have" the composite field. - self.assertFalse(proto.HasField(composite_field_name)) - - # Now set the scalar within the composite field. Ensure that the setting - # is reflected, and that proto.HasField('composite') and - # proto.composite.HasField('scalar') now both return True. - new_val = 20 - setattr(composite_field, scalar_field_name, new_val) - self.assertEqual(new_val, getattr(composite_field, scalar_field_name)) - # Hold on to a reference to the current composite_field object. - old_composite_field = composite_field - # Assert that the has methods now return true. - self.assertTrue(composite_field.HasField(scalar_field_name)) - self.assertTrue(proto.HasField(composite_field_name)) - - # Now call the clear method... - proto.ClearField(composite_field_name) - - # ...and ensure that the "has" bits are all back to False... - composite_field = getattr(proto, composite_field_name) - self.assertFalse(composite_field.HasField(scalar_field_name)) - self.assertFalse(proto.HasField(composite_field_name)) - # ...and ensure that the scalar field has returned to its default. - self.assertEqual(0, getattr(composite_field, scalar_field_name)) - - self.assertIsNot(old_composite_field, composite_field) - setattr(old_composite_field, scalar_field_name, new_val) - self.assertFalse(composite_field.HasField(scalar_field_name)) - self.assertFalse(proto.HasField(composite_field_name)) - self.assertEqual(0, getattr(composite_field, scalar_field_name)) - - # Test simple, single-level nesting when we set a scalar. - TestCompositeHasBits('optionalgroup', 'a') - TestCompositeHasBits('optional_nested_message', 'bb') - TestCompositeHasBits('optional_foreign_message', 'c') - TestCompositeHasBits('optional_import_message', 'd') - - def testHasBitsWhenModifyingRepeatedFields(self): - # Test nesting when we add an element to a repeated field in a submessage. - proto = unittest_pb2.TestNestedMessageHasBits() - proto.optional_nested_message.nestedmessage_repeated_int32.append(5) - self.assertEqual( - [5], proto.optional_nested_message.nestedmessage_repeated_int32) - self.assertTrue(proto.HasField('optional_nested_message')) - - # Do the same test, but with a repeated composite field within the - # submessage. - proto.ClearField('optional_nested_message') - self.assertFalse(proto.HasField('optional_nested_message')) - proto.optional_nested_message.nestedmessage_repeated_foreignmessage.add() - self.assertTrue(proto.HasField('optional_nested_message')) - - def testHasBitsForManyLevelsOfNesting(self): - # Test nesting many levels deep. - recursive_proto = unittest_pb2.TestMutualRecursionA() - self.assertFalse(recursive_proto.HasField('bb')) - self.assertEqual(0, recursive_proto.bb.a.bb.a.bb.optional_int32) - self.assertFalse(recursive_proto.HasField('bb')) - recursive_proto.bb.a.bb.a.bb.optional_int32 = 5 - self.assertEqual(5, recursive_proto.bb.a.bb.a.bb.optional_int32) - self.assertTrue(recursive_proto.HasField('bb')) - self.assertTrue(recursive_proto.bb.HasField('a')) - self.assertTrue(recursive_proto.bb.a.HasField('bb')) - self.assertTrue(recursive_proto.bb.a.bb.HasField('a')) - self.assertTrue(recursive_proto.bb.a.bb.a.HasField('bb')) - self.assertFalse(recursive_proto.bb.a.bb.a.bb.HasField('a')) - self.assertTrue(recursive_proto.bb.a.bb.a.bb.HasField('optional_int32')) - - def testSingularListExtensions(self): - proto = unittest_pb2.TestAllExtensions() - proto.Extensions[unittest_pb2.optional_fixed32_extension] = 1 - proto.Extensions[unittest_pb2.optional_int32_extension ] = 5 - proto.Extensions[unittest_pb2.optional_string_extension ] = 'foo' - self.assertEqual( - [ (unittest_pb2.optional_int32_extension , 5), - (unittest_pb2.optional_fixed32_extension, 1), - (unittest_pb2.optional_string_extension , 'foo') ], - proto.ListFields()) - del proto.Extensions[unittest_pb2.optional_fixed32_extension] - self.assertEqual( - [(unittest_pb2.optional_int32_extension, 5), - (unittest_pb2.optional_string_extension, 'foo')], - proto.ListFields()) - - def testRepeatedListExtensions(self): - proto = unittest_pb2.TestAllExtensions() - proto.Extensions[unittest_pb2.repeated_fixed32_extension].append(1) - proto.Extensions[unittest_pb2.repeated_int32_extension ].append(5) - proto.Extensions[unittest_pb2.repeated_int32_extension ].append(11) - proto.Extensions[unittest_pb2.repeated_string_extension ].append('foo') - proto.Extensions[unittest_pb2.repeated_string_extension ].append('bar') - proto.Extensions[unittest_pb2.repeated_string_extension ].append('baz') - proto.Extensions[unittest_pb2.optional_int32_extension ] = 21 - self.assertEqual( - [ (unittest_pb2.optional_int32_extension , 21), - (unittest_pb2.repeated_int32_extension , [5, 11]), - (unittest_pb2.repeated_fixed32_extension, [1]), - (unittest_pb2.repeated_string_extension , ['foo', 'bar', 'baz']) ], - proto.ListFields()) - del proto.Extensions[unittest_pb2.repeated_int32_extension] - del proto.Extensions[unittest_pb2.repeated_string_extension] - self.assertEqual( - [(unittest_pb2.optional_int32_extension, 21), - (unittest_pb2.repeated_fixed32_extension, [1])], - proto.ListFields()) - - def testListFieldsAndExtensions(self): - proto = unittest_pb2.TestFieldOrderings() - test_util.SetAllFieldsAndExtensions(proto) - unittest_pb2.my_extension_int - self.assertEqual( - [ (proto.DESCRIPTOR.fields_by_name['my_int' ], 1), - (unittest_pb2.my_extension_int , 23), - (proto.DESCRIPTOR.fields_by_name['my_string'], 'foo'), - (unittest_pb2.my_extension_string , 'bar'), - (proto.DESCRIPTOR.fields_by_name['my_float' ], 1.0) ], - proto.ListFields()) - - def testDefaultValues(self): - proto = unittest_pb2.TestAllTypes() - self.assertEqual(0, proto.optional_int32) - self.assertEqual(0, proto.optional_int64) - self.assertEqual(0, proto.optional_uint32) - self.assertEqual(0, proto.optional_uint64) - self.assertEqual(0, proto.optional_sint32) - self.assertEqual(0, proto.optional_sint64) - self.assertEqual(0, proto.optional_fixed32) - self.assertEqual(0, proto.optional_fixed64) - self.assertEqual(0, proto.optional_sfixed32) - self.assertEqual(0, proto.optional_sfixed64) - self.assertEqual(0.0, proto.optional_float) - self.assertEqual(0.0, proto.optional_double) - self.assertEqual(False, proto.optional_bool) - self.assertEqual('', proto.optional_string) - self.assertEqual(b'', proto.optional_bytes) - - self.assertEqual(41, proto.default_int32) - self.assertEqual(42, proto.default_int64) - self.assertEqual(43, proto.default_uint32) - self.assertEqual(44, proto.default_uint64) - self.assertEqual(-45, proto.default_sint32) - self.assertEqual(46, proto.default_sint64) - self.assertEqual(47, proto.default_fixed32) - self.assertEqual(48, proto.default_fixed64) - self.assertEqual(49, proto.default_sfixed32) - self.assertEqual(-50, proto.default_sfixed64) - self.assertEqual(51.5, proto.default_float) - self.assertEqual(52e3, proto.default_double) - self.assertEqual(True, proto.default_bool) - self.assertEqual('hello', proto.default_string) - self.assertEqual(b'world', proto.default_bytes) - self.assertEqual(unittest_pb2.TestAllTypes.BAR, proto.default_nested_enum) - self.assertEqual(unittest_pb2.FOREIGN_BAR, proto.default_foreign_enum) - self.assertEqual(unittest_import_pb2.IMPORT_BAR, - proto.default_import_enum) - - proto = unittest_pb2.TestExtremeDefaultValues() - self.assertEqual(u'\u1234', proto.utf8_string) - - def testHasFieldWithUnknownFieldName(self): - proto = unittest_pb2.TestAllTypes() - self.assertRaises(ValueError, proto.HasField, 'nonexistent_field') - - def testClearRemovesChildren(self): - # Make sure there aren't any implementation bugs that are only partially - # clearing the message (which can happen in the more complex C++ - # implementation which has parallel message lists). - proto = unittest_pb2.TestRequiredForeign() - for i in range(10): - proto.repeated_message.add() - proto2 = unittest_pb2.TestRequiredForeign() - proto.CopyFrom(proto2) - self.assertRaises(IndexError, lambda: proto.repeated_message[5]) - - def testSingleScalarClearField(self): - proto = unittest_pb2.TestAllTypes() - # Should be allowed to clear something that's not there (a no-op). - proto.ClearField('optional_int32') - proto.optional_int32 = 1 - self.assertTrue(proto.HasField('optional_int32')) - proto.ClearField('optional_int32') - self.assertEqual(0, proto.optional_int32) - self.assertFalse(proto.HasField('optional_int32')) - # TODO(robinson): Test all other scalar field types. - - def testRepeatedScalars(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - proto.repeated_int32.append(5) - proto.repeated_int32.append(10) - proto.repeated_int32.append(15) - self.assertTrue(proto.repeated_int32) - self.assertEqual(3, len(proto.repeated_int32)) - - self.assertEqual([5, 10, 15], proto.repeated_int32) - - # Test single retrieval. - self.assertEqual(5, proto.repeated_int32[0]) - self.assertEqual(15, proto.repeated_int32[-1]) - # Test out-of-bounds indices. - self.assertRaises(IndexError, proto.repeated_int32.__getitem__, 1234) - self.assertRaises(IndexError, proto.repeated_int32.__getitem__, -1234) - # Test incorrect types passed to __getitem__. - self.assertRaises(TypeError, proto.repeated_int32.__getitem__, 'foo') - self.assertRaises(TypeError, proto.repeated_int32.__getitem__, None) - - # Test single assignment. - proto.repeated_int32[1] = 20 - self.assertEqual([5, 20, 15], proto.repeated_int32) - - # Test insertion. - proto.repeated_int32.insert(1, 25) - self.assertEqual([5, 25, 20, 15], proto.repeated_int32) - - # Test slice retrieval. - proto.repeated_int32.append(30) - self.assertEqual([25, 20, 15], proto.repeated_int32[1:4]) - self.assertEqual([5, 25, 20, 15, 30], proto.repeated_int32[:]) - - # Test slice assignment with an iterator - proto.repeated_int32[1:4] = (i for i in range(3)) - self.assertEqual([5, 0, 1, 2, 30], proto.repeated_int32) - - # Test slice assignment. - proto.repeated_int32[1:4] = [35, 40, 45] - self.assertEqual([5, 35, 40, 45, 30], proto.repeated_int32) - - # Test that we can use the field as an iterator. - result = [] - for i in proto.repeated_int32: - result.append(i) - self.assertEqual([5, 35, 40, 45, 30], result) - - # Test single deletion. - del proto.repeated_int32[2] - self.assertEqual([5, 35, 45, 30], proto.repeated_int32) - - # Test slice deletion. - del proto.repeated_int32[2:] - self.assertEqual([5, 35], proto.repeated_int32) - - # Test extending. - proto.repeated_int32.extend([3, 13]) - self.assertEqual([5, 35, 3, 13], proto.repeated_int32) - - # Test clearing. - proto.ClearField('repeated_int32') - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - - proto.repeated_int32.append(1) - self.assertEqual(1, proto.repeated_int32[-1]) - # Test assignment to a negative index. - proto.repeated_int32[-1] = 2 - self.assertEqual(2, proto.repeated_int32[-1]) - - # Test deletion at negative indices. - proto.repeated_int32[:] = [0, 1, 2, 3] - del proto.repeated_int32[-1] - self.assertEqual([0, 1, 2], proto.repeated_int32) - - del proto.repeated_int32[-2] - self.assertEqual([0, 2], proto.repeated_int32) - - self.assertRaises(IndexError, proto.repeated_int32.__delitem__, -3) - self.assertRaises(IndexError, proto.repeated_int32.__delitem__, 300) - - del proto.repeated_int32[-2:-1] - self.assertEqual([2], proto.repeated_int32) - - del proto.repeated_int32[100:10000] - self.assertEqual([2], proto.repeated_int32) - - def testRepeatedScalarsRemove(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - proto.repeated_int32.append(5) - proto.repeated_int32.append(10) - proto.repeated_int32.append(5) - proto.repeated_int32.append(5) - - self.assertEqual(4, len(proto.repeated_int32)) - proto.repeated_int32.remove(5) - self.assertEqual(3, len(proto.repeated_int32)) - self.assertEqual(10, proto.repeated_int32[0]) - self.assertEqual(5, proto.repeated_int32[1]) - self.assertEqual(5, proto.repeated_int32[2]) - - proto.repeated_int32.remove(5) - self.assertEqual(2, len(proto.repeated_int32)) - self.assertEqual(10, proto.repeated_int32[0]) - self.assertEqual(5, proto.repeated_int32[1]) - - proto.repeated_int32.remove(10) - self.assertEqual(1, len(proto.repeated_int32)) - self.assertEqual(5, proto.repeated_int32[0]) - - # Remove a non-existent element. - self.assertRaises(ValueError, proto.repeated_int32.remove, 123) - - def testRepeatedScalarsReverse_Empty(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - - self.assertIsNone(proto.repeated_int32.reverse()) - - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - - def testRepeatedScalarsReverse_NonEmpty(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_int32) - self.assertEqual(0, len(proto.repeated_int32)) - - proto.repeated_int32.append(1) - proto.repeated_int32.append(2) - proto.repeated_int32.append(3) - proto.repeated_int32.append(4) - - self.assertEqual(4, len(proto.repeated_int32)) - - self.assertIsNone(proto.repeated_int32.reverse()) - - self.assertEqual(4, len(proto.repeated_int32)) - self.assertEqual(4, proto.repeated_int32[0]) - self.assertEqual(3, proto.repeated_int32[1]) - self.assertEqual(2, proto.repeated_int32[2]) - self.assertEqual(1, proto.repeated_int32[3]) - - def testRepeatedComposites(self): - proto = unittest_pb2.TestAllTypes() - self.assertFalse(proto.repeated_nested_message) - self.assertEqual(0, len(proto.repeated_nested_message)) - m0 = proto.repeated_nested_message.add() - m1 = proto.repeated_nested_message.add() - self.assertTrue(proto.repeated_nested_message) - self.assertEqual(2, len(proto.repeated_nested_message)) - self.assertListsEqual([m0, m1], proto.repeated_nested_message) - self.assertIsInstance(m0, unittest_pb2.TestAllTypes.NestedMessage) - - # Test out-of-bounds indices. - self.assertRaises(IndexError, proto.repeated_nested_message.__getitem__, - 1234) - self.assertRaises(IndexError, proto.repeated_nested_message.__getitem__, - -1234) - - # Test incorrect types passed to __getitem__. - self.assertRaises(TypeError, proto.repeated_nested_message.__getitem__, - 'foo') - self.assertRaises(TypeError, proto.repeated_nested_message.__getitem__, - None) - - # Test slice retrieval. - m2 = proto.repeated_nested_message.add() - m3 = proto.repeated_nested_message.add() - m4 = proto.repeated_nested_message.add() - self.assertListsEqual( - [m1, m2, m3], proto.repeated_nested_message[1:4]) - self.assertListsEqual( - [m0, m1, m2, m3, m4], proto.repeated_nested_message[:]) - self.assertListsEqual( - [m0, m1], proto.repeated_nested_message[:2]) - self.assertListsEqual( - [m2, m3, m4], proto.repeated_nested_message[2:]) - self.assertEqual( - m0, proto.repeated_nested_message[0]) - self.assertListsEqual( - [m0], proto.repeated_nested_message[:1]) - - # Test that we can use the field as an iterator. - result = [] - for i in proto.repeated_nested_message: - result.append(i) - self.assertListsEqual([m0, m1, m2, m3, m4], result) - - # Test single deletion. - del proto.repeated_nested_message[2] - self.assertListsEqual([m0, m1, m3, m4], proto.repeated_nested_message) - - # Test slice deletion. - del proto.repeated_nested_message[2:] - self.assertListsEqual([m0, m1], proto.repeated_nested_message) - - # Test extending. - n1 = unittest_pb2.TestAllTypes.NestedMessage(bb=1) - n2 = unittest_pb2.TestAllTypes.NestedMessage(bb=2) - proto.repeated_nested_message.extend([n1,n2]) - self.assertEqual(4, len(proto.repeated_nested_message)) - self.assertEqual(n1, proto.repeated_nested_message[2]) - self.assertEqual(n2, proto.repeated_nested_message[3]) - self.assertRaises(TypeError, - proto.repeated_nested_message.extend, n1) - self.assertRaises(TypeError, - proto.repeated_nested_message.extend, [0]) - wrong_message_type = unittest_pb2.TestAllTypes() - self.assertRaises(TypeError, - proto.repeated_nested_message.extend, - [wrong_message_type]) - - # Test clearing. - proto.ClearField('repeated_nested_message') - self.assertFalse(proto.repeated_nested_message) - self.assertEqual(0, len(proto.repeated_nested_message)) - - # Test constructing an element while adding it. - proto.repeated_nested_message.add(bb=23) - self.assertEqual(1, len(proto.repeated_nested_message)) - self.assertEqual(23, proto.repeated_nested_message[0].bb) - self.assertRaises(TypeError, proto.repeated_nested_message.add, 23) - with self.assertRaises(Exception): - proto.repeated_nested_message[0] = 23 - - def testRepeatedCompositeRemove(self): - proto = unittest_pb2.TestAllTypes() - - self.assertEqual(0, len(proto.repeated_nested_message)) - m0 = proto.repeated_nested_message.add() - # Need to set some differentiating variable so m0 != m1 != m2: - m0.bb = len(proto.repeated_nested_message) - m1 = proto.repeated_nested_message.add() - m1.bb = len(proto.repeated_nested_message) - self.assertTrue(m0 != m1) - m2 = proto.repeated_nested_message.add() - m2.bb = len(proto.repeated_nested_message) - self.assertListsEqual([m0, m1, m2], proto.repeated_nested_message) - - self.assertEqual(3, len(proto.repeated_nested_message)) - proto.repeated_nested_message.remove(m0) - self.assertEqual(2, len(proto.repeated_nested_message)) - self.assertEqual(m1, proto.repeated_nested_message[0]) - self.assertEqual(m2, proto.repeated_nested_message[1]) - - # Removing m0 again or removing None should raise error - self.assertRaises(ValueError, proto.repeated_nested_message.remove, m0) - self.assertRaises(ValueError, proto.repeated_nested_message.remove, None) - self.assertEqual(2, len(proto.repeated_nested_message)) - - proto.repeated_nested_message.remove(m2) - self.assertEqual(1, len(proto.repeated_nested_message)) - self.assertEqual(m1, proto.repeated_nested_message[0]) - - def testRepeatedCompositeReverse_Empty(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_nested_message) - self.assertEqual(0, len(proto.repeated_nested_message)) - - self.assertIsNone(proto.repeated_nested_message.reverse()) - - self.assertFalse(proto.repeated_nested_message) - self.assertEqual(0, len(proto.repeated_nested_message)) - - def testRepeatedCompositeReverse_NonEmpty(self): - proto = unittest_pb2.TestAllTypes() - - self.assertFalse(proto.repeated_nested_message) - self.assertEqual(0, len(proto.repeated_nested_message)) - - m0 = proto.repeated_nested_message.add() - m0.bb = len(proto.repeated_nested_message) - m1 = proto.repeated_nested_message.add() - m1.bb = len(proto.repeated_nested_message) - m2 = proto.repeated_nested_message.add() - m2.bb = len(proto.repeated_nested_message) - self.assertListsEqual([m0, m1, m2], proto.repeated_nested_message) - - self.assertIsNone(proto.repeated_nested_message.reverse()) - - self.assertListsEqual([m2, m1, m0], proto.repeated_nested_message) - - def testHandWrittenReflection(self): - # Hand written extensions are only supported by the pure-Python - # implementation of the API. - if api_implementation.Type() != 'python': - return - - FieldDescriptor = descriptor.FieldDescriptor - foo_field_descriptor = FieldDescriptor( - name='foo_field', full_name='MyProto.foo_field', - index=0, number=1, type=FieldDescriptor.TYPE_INT64, - cpp_type=FieldDescriptor.CPPTYPE_INT64, - label=FieldDescriptor.LABEL_OPTIONAL, default_value=0, - containing_type=None, message_type=None, enum_type=None, - is_extension=False, extension_scope=None, - options=descriptor_pb2.FieldOptions(), - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - mydescriptor = descriptor.Descriptor( - name='MyProto', full_name='MyProto', filename='ignored', - containing_type=None, nested_types=[], enum_types=[], - fields=[foo_field_descriptor], extensions=[], - options=descriptor_pb2.MessageOptions(), - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - - class MyProtoClass( - message.Message, metaclass=reflection.GeneratedProtocolMessageType): - DESCRIPTOR = mydescriptor - myproto_instance = MyProtoClass() - self.assertEqual(0, myproto_instance.foo_field) - self.assertFalse(myproto_instance.HasField('foo_field')) - myproto_instance.foo_field = 23 - self.assertEqual(23, myproto_instance.foo_field) - self.assertTrue(myproto_instance.HasField('foo_field')) - - @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') - def testDescriptorProtoSupport(self): - # Hand written descriptors/reflection are only supported by the pure-Python - # implementation of the API. - if api_implementation.Type() != 'python': - return - - def AddDescriptorField(proto, field_name, field_type): - AddDescriptorField.field_index += 1 - new_field = proto.field.add() - new_field.name = field_name - new_field.type = field_type - new_field.number = AddDescriptorField.field_index - new_field.label = descriptor_pb2.FieldDescriptorProto.LABEL_OPTIONAL - - AddDescriptorField.field_index = 0 - - desc_proto = descriptor_pb2.DescriptorProto() - desc_proto.name = 'Car' - fdp = descriptor_pb2.FieldDescriptorProto - AddDescriptorField(desc_proto, 'name', fdp.TYPE_STRING) - AddDescriptorField(desc_proto, 'year', fdp.TYPE_INT64) - AddDescriptorField(desc_proto, 'automatic', fdp.TYPE_BOOL) - AddDescriptorField(desc_proto, 'price', fdp.TYPE_DOUBLE) - # Add a repeated field - AddDescriptorField.field_index += 1 - new_field = desc_proto.field.add() - new_field.name = 'owners' - new_field.type = fdp.TYPE_STRING - new_field.number = AddDescriptorField.field_index - new_field.label = descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED - - desc = descriptor.MakeDescriptor(desc_proto) - self.assertTrue('name' in desc.fields_by_name) - self.assertTrue('year' in desc.fields_by_name) - self.assertTrue('automatic' in desc.fields_by_name) - self.assertTrue('price' in desc.fields_by_name) - self.assertTrue('owners' in desc.fields_by_name) - - class CarMessage( - message.Message, metaclass=reflection.GeneratedProtocolMessageType): - DESCRIPTOR = desc - - prius = CarMessage() - prius.name = 'prius' - prius.year = 2010 - prius.automatic = True - prius.price = 25134.75 - prius.owners.extend(['bob', 'susan']) - - serialized_prius = prius.SerializeToString() - new_prius = reflection.ParseMessage(desc, serialized_prius) - self.assertIsNot(new_prius, prius) - self.assertEqual(prius, new_prius) - - # these are unnecessary assuming message equality works as advertised but - # explicitly check to be safe since we're mucking about in metaclass foo - self.assertEqual(prius.name, new_prius.name) - self.assertEqual(prius.year, new_prius.year) - self.assertEqual(prius.automatic, new_prius.automatic) - self.assertEqual(prius.price, new_prius.price) - self.assertEqual(prius.owners, new_prius.owners) - - def testExtensionDelete(self): - extendee_proto = more_extensions_pb2.ExtendedMessage() - - extension_int32 = more_extensions_pb2.optional_int_extension - extendee_proto.Extensions[extension_int32] = 23 - - extension_repeated = more_extensions_pb2.repeated_int_extension - extendee_proto.Extensions[extension_repeated].append(11) - - extension_msg = more_extensions_pb2.optional_message_extension - extendee_proto.Extensions[extension_msg].foreign_message_int = 56 - - self.assertEqual(len(extendee_proto.Extensions), 3) - del extendee_proto.Extensions[extension_msg] - self.assertEqual(len(extendee_proto.Extensions), 2) - del extendee_proto.Extensions[extension_repeated] - self.assertEqual(len(extendee_proto.Extensions), 1) - # Delete a none exist extension. It is OK to "del m.Extensions[ext]" - # even if the extension is not present in the message; we don't - # raise KeyError. This is consistent with "m.Extensions[ext]" - # returning a default value even if we did not set anything. - del extendee_proto.Extensions[extension_repeated] - self.assertEqual(len(extendee_proto.Extensions), 1) - del extendee_proto.Extensions[extension_int32] - self.assertEqual(len(extendee_proto.Extensions), 0) - - def testExtensionIter(self): - extendee_proto = more_extensions_pb2.ExtendedMessage() - - extension_int32 = more_extensions_pb2.optional_int_extension - extendee_proto.Extensions[extension_int32] = 23 - - extension_repeated = more_extensions_pb2.repeated_int_extension - extendee_proto.Extensions[extension_repeated].append(11) - - extension_msg = more_extensions_pb2.optional_message_extension - extendee_proto.Extensions[extension_msg].foreign_message_int = 56 - - # Set some normal fields. - extendee_proto.optional_int32 = 1 - extendee_proto.repeated_string.append('hi') - - expected = (extension_int32, extension_msg, extension_repeated) - count = 0 - for item in extendee_proto.Extensions: - self.assertEqual(item.name, expected[count].name) - self.assertIn(item, extendee_proto.Extensions) - count += 1 - self.assertEqual(count, 3) - - def testExtensionContainsError(self): - extendee_proto = more_extensions_pb2.ExtendedMessage() - self.assertRaises(KeyError, extendee_proto.Extensions.__contains__, 0) - - field = more_extensions_pb2.ExtendedMessage.DESCRIPTOR.fields_by_name[ - 'optional_int32'] - self.assertRaises(KeyError, extendee_proto.Extensions.__contains__, field) - - def testTopLevelExtensionsForOptionalScalar(self): - extendee_proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.optional_int32_extension - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - self.assertEqual(0, extendee_proto.Extensions[extension]) - # As with normal scalar fields, just doing a read doesn't actually set the - # "has" bit. - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - # Actually set the thing. - extendee_proto.Extensions[extension] = 23 - self.assertEqual(23, extendee_proto.Extensions[extension]) - self.assertTrue(extendee_proto.HasExtension(extension)) - self.assertIn(extension, extendee_proto.Extensions) - # Ensure that clearing works as well. - extendee_proto.ClearExtension(extension) - self.assertEqual(0, extendee_proto.Extensions[extension]) - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - - def testTopLevelExtensionsForRepeatedScalar(self): - extendee_proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.repeated_string_extension - self.assertEqual(0, len(extendee_proto.Extensions[extension])) - self.assertNotIn(extension, extendee_proto.Extensions) - extendee_proto.Extensions[extension].append('foo') - self.assertEqual(['foo'], extendee_proto.Extensions[extension]) - self.assertIn(extension, extendee_proto.Extensions) - string_list = extendee_proto.Extensions[extension] - extendee_proto.ClearExtension(extension) - self.assertEqual(0, len(extendee_proto.Extensions[extension])) - self.assertNotIn(extension, extendee_proto.Extensions) - self.assertIsNot(string_list, extendee_proto.Extensions[extension]) - # Shouldn't be allowed to do Extensions[extension] = 'a' - self.assertRaises(TypeError, operator.setitem, extendee_proto.Extensions, - extension, 'a') - - def testTopLevelExtensionsForOptionalMessage(self): - extendee_proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.optional_foreign_message_extension - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - self.assertEqual(0, extendee_proto.Extensions[extension].c) - # As with normal (non-extension) fields, merely reading from the - # thing shouldn't set the "has" bit. - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - extendee_proto.Extensions[extension].c = 23 - self.assertEqual(23, extendee_proto.Extensions[extension].c) - self.assertTrue(extendee_proto.HasExtension(extension)) - self.assertIn(extension, extendee_proto.Extensions) - # Save a reference here. - foreign_message = extendee_proto.Extensions[extension] - extendee_proto.ClearExtension(extension) - self.assertIsNot(foreign_message, extendee_proto.Extensions[extension]) - # Setting a field on foreign_message now shouldn't set - # any "has" bits on extendee_proto. - foreign_message.c = 42 - self.assertEqual(42, foreign_message.c) - self.assertTrue(foreign_message.HasField('c')) - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - # Shouldn't be allowed to do Extensions[extension] = 'a' - self.assertRaises(TypeError, operator.setitem, extendee_proto.Extensions, - extension, 'a') - - def testTopLevelExtensionsForRepeatedMessage(self): - extendee_proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.repeatedgroup_extension - self.assertEqual(0, len(extendee_proto.Extensions[extension])) - group = extendee_proto.Extensions[extension].add() - group.a = 23 - self.assertEqual(23, extendee_proto.Extensions[extension][0].a) - group.a = 42 - self.assertEqual(42, extendee_proto.Extensions[extension][0].a) - group_list = extendee_proto.Extensions[extension] - extendee_proto.ClearExtension(extension) - self.assertEqual(0, len(extendee_proto.Extensions[extension])) - self.assertIsNot(group_list, extendee_proto.Extensions[extension]) - # Shouldn't be allowed to do Extensions[extension] = 'a' - self.assertRaises(TypeError, operator.setitem, extendee_proto.Extensions, - extension, 'a') - - def testNestedExtensions(self): - extendee_proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.TestRequired.single - - # We just test the non-repeated case. - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - required = extendee_proto.Extensions[extension] - self.assertEqual(0, required.a) - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - required.a = 23 - self.assertEqual(23, extendee_proto.Extensions[extension].a) - self.assertTrue(extendee_proto.HasExtension(extension)) - self.assertIn(extension, extendee_proto.Extensions) - extendee_proto.ClearExtension(extension) - self.assertIsNot(required, extendee_proto.Extensions[extension]) - self.assertFalse(extendee_proto.HasExtension(extension)) - self.assertNotIn(extension, extendee_proto.Extensions) - - def testRegisteredExtensions(self): - pool = unittest_pb2.DESCRIPTOR.pool - self.assertTrue( - pool.FindExtensionByNumber( - unittest_pb2.TestAllExtensions.DESCRIPTOR, 1)) - self.assertIs( - pool.FindExtensionByName( - 'protobuf_unittest.optional_int32_extension').containing_type, - unittest_pb2.TestAllExtensions.DESCRIPTOR) - # Make sure extensions haven't been registered into types that shouldn't - # have any. - self.assertEqual(0, len( - pool.FindAllExtensions(unittest_pb2.TestAllTypes.DESCRIPTOR))) - - # If message A directly contains message B, and - # a.HasField('b') is currently False, then mutating any - # extension in B should change a.HasField('b') to True - # (and so on up the object tree). - def testHasBitsForAncestorsOfExtendedMessage(self): - # Optional scalar extension. - toplevel = more_extensions_pb2.TopLevelMessage() - self.assertFalse(toplevel.HasField('submessage')) - self.assertEqual(0, toplevel.submessage.Extensions[ - more_extensions_pb2.optional_int_extension]) - self.assertFalse(toplevel.HasField('submessage')) - toplevel.submessage.Extensions[ - more_extensions_pb2.optional_int_extension] = 23 - self.assertEqual(23, toplevel.submessage.Extensions[ - more_extensions_pb2.optional_int_extension]) - self.assertTrue(toplevel.HasField('submessage')) - - # Repeated scalar extension. - toplevel = more_extensions_pb2.TopLevelMessage() - self.assertFalse(toplevel.HasField('submessage')) - self.assertEqual([], toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_int_extension]) - self.assertFalse(toplevel.HasField('submessage')) - toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_int_extension].append(23) - self.assertEqual([23], toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_int_extension]) - self.assertTrue(toplevel.HasField('submessage')) - - # Optional message extension. - toplevel = more_extensions_pb2.TopLevelMessage() - self.assertFalse(toplevel.HasField('submessage')) - self.assertEqual(0, toplevel.submessage.Extensions[ - more_extensions_pb2.optional_message_extension].foreign_message_int) - self.assertFalse(toplevel.HasField('submessage')) - toplevel.submessage.Extensions[ - more_extensions_pb2.optional_message_extension].foreign_message_int = 23 - self.assertEqual(23, toplevel.submessage.Extensions[ - more_extensions_pb2.optional_message_extension].foreign_message_int) - self.assertTrue(toplevel.HasField('submessage')) - - # Repeated message extension. - toplevel = more_extensions_pb2.TopLevelMessage() - self.assertFalse(toplevel.HasField('submessage')) - self.assertEqual(0, len(toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_message_extension])) - self.assertFalse(toplevel.HasField('submessage')) - foreign = toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_message_extension].add() - self.assertEqual(foreign, toplevel.submessage.Extensions[ - more_extensions_pb2.repeated_message_extension][0]) - self.assertTrue(toplevel.HasField('submessage')) - - def testDisconnectionAfterClearingEmptyMessage(self): - toplevel = more_extensions_pb2.TopLevelMessage() - extendee_proto = toplevel.submessage - extension = more_extensions_pb2.optional_message_extension - extension_proto = extendee_proto.Extensions[extension] - extendee_proto.ClearExtension(extension) - extension_proto.foreign_message_int = 23 - - self.assertIsNot(extension_proto, extendee_proto.Extensions[extension]) - - def testExtensionFailureModes(self): - extendee_proto = unittest_pb2.TestAllExtensions() - - # Try non-extension-handle arguments to HasExtension, - # ClearExtension(), and Extensions[]... - self.assertRaises(KeyError, extendee_proto.HasExtension, 1234) - self.assertRaises(KeyError, extendee_proto.ClearExtension, 1234) - self.assertRaises(KeyError, extendee_proto.Extensions.__getitem__, 1234) - self.assertRaises(KeyError, extendee_proto.Extensions.__setitem__, 1234, 5) - - # Try something that *is* an extension handle, just not for - # this message... - for unknown_handle in (more_extensions_pb2.optional_int_extension, - more_extensions_pb2.optional_message_extension, - more_extensions_pb2.repeated_int_extension, - more_extensions_pb2.repeated_message_extension): - self.assertRaises(KeyError, extendee_proto.HasExtension, - unknown_handle) - self.assertRaises(KeyError, extendee_proto.ClearExtension, - unknown_handle) - self.assertRaises(KeyError, extendee_proto.Extensions.__getitem__, - unknown_handle) - self.assertRaises(KeyError, extendee_proto.Extensions.__setitem__, - unknown_handle, 5) - - # Try call HasExtension() with a valid handle, but for a - # *repeated* field. (Just as with non-extension repeated - # fields, Has*() isn't supported for extension repeated fields). - self.assertRaises(KeyError, extendee_proto.HasExtension, - unittest_pb2.repeated_string_extension) - - def testMergeFromOptionalGroup(self): - # Test merge with an optional group. - proto1 = unittest_pb2.TestAllTypes() - proto1.optionalgroup.a = 12 - proto2 = unittest_pb2.TestAllTypes() - proto2.MergeFrom(proto1) - self.assertEqual(12, proto2.optionalgroup.a) - - def testMergeFromExtensionsSingular(self): - proto1 = unittest_pb2.TestAllExtensions() - proto1.Extensions[unittest_pb2.optional_int32_extension] = 1 - - proto2 = unittest_pb2.TestAllExtensions() - proto2.MergeFrom(proto1) - self.assertEqual( - 1, proto2.Extensions[unittest_pb2.optional_int32_extension]) - - def testMergeFromExtensionsRepeated(self): - proto1 = unittest_pb2.TestAllExtensions() - proto1.Extensions[unittest_pb2.repeated_int32_extension].append(1) - proto1.Extensions[unittest_pb2.repeated_int32_extension].append(2) - - proto2 = unittest_pb2.TestAllExtensions() - proto2.Extensions[unittest_pb2.repeated_int32_extension].append(0) - proto2.MergeFrom(proto1) - self.assertEqual( - 3, len(proto2.Extensions[unittest_pb2.repeated_int32_extension])) - self.assertEqual( - 0, proto2.Extensions[unittest_pb2.repeated_int32_extension][0]) - self.assertEqual( - 1, proto2.Extensions[unittest_pb2.repeated_int32_extension][1]) - self.assertEqual( - 2, proto2.Extensions[unittest_pb2.repeated_int32_extension][2]) - - def testMergeFromExtensionsNestedMessage(self): - proto1 = unittest_pb2.TestAllExtensions() - ext1 = proto1.Extensions[ - unittest_pb2.repeated_nested_message_extension] - m = ext1.add() - m.bb = 222 - m = ext1.add() - m.bb = 333 - - proto2 = unittest_pb2.TestAllExtensions() - ext2 = proto2.Extensions[ - unittest_pb2.repeated_nested_message_extension] - m = ext2.add() - m.bb = 111 - - proto2.MergeFrom(proto1) - ext2 = proto2.Extensions[ - unittest_pb2.repeated_nested_message_extension] - self.assertEqual(3, len(ext2)) - self.assertEqual(111, ext2[0].bb) - self.assertEqual(222, ext2[1].bb) - self.assertEqual(333, ext2[2].bb) - - def testCopyFromBadType(self): - # The python implementation doesn't raise an exception in this - # case. In theory it should. - if api_implementation.Type() == 'python': - return - proto1 = unittest_pb2.TestAllTypes() - proto2 = unittest_pb2.TestAllExtensions() - self.assertRaises(TypeError, proto1.CopyFrom, proto2) - - def testClear(self): - proto = unittest_pb2.TestAllTypes() - # C++ implementation does not support lazy fields right now so leave it - # out for now. - if api_implementation.Type() == 'python': - test_util.SetAllFields(proto) - else: - test_util.SetAllNonLazyFields(proto) - # Clear the message. - proto.Clear() - self.assertEqual(proto.ByteSize(), 0) - empty_proto = unittest_pb2.TestAllTypes() - self.assertEqual(proto, empty_proto) - - # Test if extensions which were set are cleared. - proto = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(proto) - # Clear the message. - proto.Clear() - self.assertEqual(proto.ByteSize(), 0) - empty_proto = unittest_pb2.TestAllExtensions() - self.assertEqual(proto, empty_proto) - - def testDisconnectingInOneof(self): - m = unittest_pb2.TestOneof2() # This message has two messages in a oneof. - m.foo_message.moo_int = 5 - sub_message = m.foo_message - # Accessing another message's field does not clear the first one - self.assertEqual(m.foo_lazy_message.moo_int, 0) - self.assertEqual(m.foo_message.moo_int, 5) - # But mutating another message in the oneof detaches the first one. - m.foo_lazy_message.moo_int = 6 - self.assertEqual(m.foo_message.moo_int, 0) - # The reference we got above was detached and is still valid. - self.assertEqual(sub_message.moo_int, 5) - sub_message.moo_int = 7 - - def assertInitialized(self, proto): - self.assertTrue(proto.IsInitialized()) - # Neither method should raise an exception. - proto.SerializeToString() - proto.SerializePartialToString() - - def assertNotInitialized(self, proto, error_size=None): - errors = [] - self.assertFalse(proto.IsInitialized()) - self.assertFalse(proto.IsInitialized(errors)) - self.assertEqual(error_size, len(errors)) - self.assertRaises(message.EncodeError, proto.SerializeToString) - # "Partial" serialization doesn't care if message is uninitialized. - proto.SerializePartialToString() - - def testIsInitialized(self): - # Trivial cases - all optional fields and extensions. - proto = unittest_pb2.TestAllTypes() - self.assertInitialized(proto) - proto = unittest_pb2.TestAllExtensions() - self.assertInitialized(proto) - - # The case of uninitialized required fields. - proto = unittest_pb2.TestRequired() - self.assertNotInitialized(proto, 3) - proto.a = proto.b = proto.c = 2 - self.assertInitialized(proto) - - # The case of uninitialized submessage. - proto = unittest_pb2.TestRequiredForeign() - self.assertInitialized(proto) - proto.optional_message.a = 1 - self.assertNotInitialized(proto, 2) - proto.optional_message.b = 0 - proto.optional_message.c = 0 - self.assertInitialized(proto) - - # Uninitialized repeated submessage. - message1 = proto.repeated_message.add() - self.assertNotInitialized(proto, 3) - message1.a = message1.b = message1.c = 0 - self.assertInitialized(proto) - - # Uninitialized repeated group in an extension. - proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.TestRequired.multi - message1 = proto.Extensions[extension].add() - message2 = proto.Extensions[extension].add() - self.assertNotInitialized(proto, 6) - message1.a = 1 - message1.b = 1 - message1.c = 1 - self.assertNotInitialized(proto, 3) - message2.a = 2 - message2.b = 2 - message2.c = 2 - self.assertInitialized(proto) - - # Uninitialized nonrepeated message in an extension. - proto = unittest_pb2.TestAllExtensions() - extension = unittest_pb2.TestRequired.single - proto.Extensions[extension].a = 1 - self.assertNotInitialized(proto, 2) - proto.Extensions[extension].b = 2 - proto.Extensions[extension].c = 3 - self.assertInitialized(proto) - - # Try passing an errors list. - errors = [] - proto = unittest_pb2.TestRequired() - self.assertFalse(proto.IsInitialized(errors)) - self.assertEqual(errors, ['a', 'b', 'c']) - self.assertRaises(TypeError, proto.IsInitialized, 1, 2, 3) - - @unittest.skipIf( - api_implementation.Type() == 'python', - 'Errors are only available from the most recent C++ implementation.') - def testFileDescriptorErrors(self): - file_name = 'test_file_descriptor_errors.proto' - package_name = 'test_file_descriptor_errors.proto' - file_descriptor_proto = descriptor_pb2.FileDescriptorProto() - file_descriptor_proto.name = file_name - file_descriptor_proto.package = package_name - m1 = file_descriptor_proto.message_type.add() - m1.name = 'msg1' - # Compiles the proto into the C++ descriptor pool - descriptor.FileDescriptor( - file_name, - package_name, - serialized_pb=file_descriptor_proto.SerializeToString()) - # Add a FileDescriptorProto that has duplicate symbols - another_file_name = 'another_test_file_descriptor_errors.proto' - file_descriptor_proto.name = another_file_name - m2 = file_descriptor_proto.message_type.add() - m2.name = 'msg2' - with self.assertRaises(TypeError) as cm: - descriptor.FileDescriptor( - another_file_name, - package_name, - serialized_pb=file_descriptor_proto.SerializeToString()) - self.assertTrue(hasattr(cm, 'exception'), '%s not raised' % - getattr(cm.expected, '__name__', cm.expected)) - self.assertIn('test_file_descriptor_errors.proto', str(cm.exception)) - # Error message will say something about this definition being a - # duplicate, though we don't check the message exactly to avoid a - # dependency on the C++ logging code. - self.assertIn('test_file_descriptor_errors.msg1', str(cm.exception)) - - def testStringUTF8Serialization(self): - proto = message_set_extensions_pb2.TestMessageSet() - extension_message = message_set_extensions_pb2.TestMessageSetExtension2 - extension = extension_message.message_set_extension - - test_utf8 = u'Тест' - test_utf8_bytes = test_utf8.encode('utf-8') - - # 'Test' in another language, using UTF-8 charset. - proto.Extensions[extension].str = test_utf8 - - # Serialize using the MessageSet wire format (this is specified in the - # .proto file). - serialized = proto.SerializeToString() - - # Check byte size. - self.assertEqual(proto.ByteSize(), len(serialized)) - - raw = unittest_mset_pb2.RawMessageSet() - bytes_read = raw.MergeFromString(serialized) - self.assertEqual(len(serialized), bytes_read) - - message2 = message_set_extensions_pb2.TestMessageSetExtension2() - - self.assertEqual(1, len(raw.item)) - # Check that the type_id is the same as the tag ID in the .proto file. - self.assertEqual(raw.item[0].type_id, 98418634) - - # Check the actual bytes on the wire. - self.assertTrue(raw.item[0].message.endswith(test_utf8_bytes)) - bytes_read = message2.MergeFromString(raw.item[0].message) - self.assertEqual(len(raw.item[0].message), bytes_read) - - self.assertEqual(type(message2.str), str) - self.assertEqual(message2.str, test_utf8) - - # The pure Python API throws an exception on MergeFromString(), - # if any of the string fields of the message can't be UTF-8 decoded. - # The C++ implementation of the API has no way to check that on - # MergeFromString and thus has no way to throw the exception. - # - # The pure Python API always returns objects of type 'unicode' (UTF-8 - # encoded), or 'bytes' (in 7 bit ASCII). - badbytes = raw.item[0].message.replace( - test_utf8_bytes, len(test_utf8_bytes) * b'\xff') - - unicode_decode_failed = False - try: - message2.MergeFromString(badbytes) - except UnicodeDecodeError: - unicode_decode_failed = True - string_field = message2.str - self.assertTrue(unicode_decode_failed or type(string_field) is bytes) - - def testSetInParent(self): - proto = unittest_pb2.TestAllTypes() - self.assertFalse(proto.HasField('optionalgroup')) - proto.optionalgroup.SetInParent() - self.assertTrue(proto.HasField('optionalgroup')) - - def testPackageInitializationImport(self): - """Test that we can import nested messages from their __init__.py. - - Such setup is not trivial since at the time of processing of __init__.py one - can't refer to its submodules by name in code, so expressions like - google.protobuf.internal.import_test_package.inner_pb2 - don't work. They do work in imports, so we have assign an alias at import - and then use that alias in generated code. - """ - # We import here since it's the import that used to fail, and we want - # the failure to have the right context. - # pylint: disable=g-import-not-at-top - from google.protobuf.internal import import_test_package - # pylint: enable=g-import-not-at-top - msg = import_test_package.myproto.Outer() - # Just check the default value. - self.assertEqual(57, msg.inner.value) - -# Since we had so many tests for protocol buffer equality, we broke these out -# into separate TestCase classes. - - -@testing_refleaks.TestCase -class TestAllTypesEqualityTest(unittest.TestCase): - - def setUp(self): - self.first_proto = unittest_pb2.TestAllTypes() - self.second_proto = unittest_pb2.TestAllTypes() - - def testNotHashable(self): - self.assertRaises(TypeError, hash, self.first_proto) - - def testSelfEquality(self): - self.assertEqual(self.first_proto, self.first_proto) - - def testEmptyProtosEqual(self): - self.assertEqual(self.first_proto, self.second_proto) - - -@testing_refleaks.TestCase -class FullProtosEqualityTest(unittest.TestCase): - - """Equality tests using completely-full protos as a starting point.""" - - def setUp(self): - self.first_proto = unittest_pb2.TestAllTypes() - self.second_proto = unittest_pb2.TestAllTypes() - test_util.SetAllFields(self.first_proto) - test_util.SetAllFields(self.second_proto) - - def testNotHashable(self): - self.assertRaises(TypeError, hash, self.first_proto) - - def testNoneNotEqual(self): - self.assertNotEqual(self.first_proto, None) - self.assertNotEqual(None, self.second_proto) - - def testNotEqualToOtherMessage(self): - third_proto = unittest_pb2.TestRequired() - self.assertNotEqual(self.first_proto, third_proto) - self.assertNotEqual(third_proto, self.second_proto) - - def testAllFieldsFilledEquality(self): - self.assertEqual(self.first_proto, self.second_proto) - - def testNonRepeatedScalar(self): - # Nonrepeated scalar field change should cause inequality. - self.first_proto.optional_int32 += 1 - self.assertNotEqual(self.first_proto, self.second_proto) - # ...as should clearing a field. - self.first_proto.ClearField('optional_int32') - self.assertNotEqual(self.first_proto, self.second_proto) - - def testNonRepeatedComposite(self): - # Change a nonrepeated composite field. - self.first_proto.optional_nested_message.bb += 1 - self.assertNotEqual(self.first_proto, self.second_proto) - self.first_proto.optional_nested_message.bb -= 1 - self.assertEqual(self.first_proto, self.second_proto) - # Clear a field in the nested message. - self.first_proto.optional_nested_message.ClearField('bb') - self.assertNotEqual(self.first_proto, self.second_proto) - self.first_proto.optional_nested_message.bb = ( - self.second_proto.optional_nested_message.bb) - self.assertEqual(self.first_proto, self.second_proto) - # Remove the nested message entirely. - self.first_proto.ClearField('optional_nested_message') - self.assertNotEqual(self.first_proto, self.second_proto) - - def testRepeatedScalar(self): - # Change a repeated scalar field. - self.first_proto.repeated_int32.append(5) - self.assertNotEqual(self.first_proto, self.second_proto) - self.first_proto.ClearField('repeated_int32') - self.assertNotEqual(self.first_proto, self.second_proto) - - def testRepeatedComposite(self): - # Change value within a repeated composite field. - self.first_proto.repeated_nested_message[0].bb += 1 - self.assertNotEqual(self.first_proto, self.second_proto) - self.first_proto.repeated_nested_message[0].bb -= 1 - self.assertEqual(self.first_proto, self.second_proto) - # Add a value to a repeated composite field. - self.first_proto.repeated_nested_message.add() - self.assertNotEqual(self.first_proto, self.second_proto) - self.second_proto.repeated_nested_message.add() - self.assertEqual(self.first_proto, self.second_proto) - - def testNonRepeatedScalarHasBits(self): - # Ensure that we test "has" bits as well as value for - # nonrepeated scalar field. - self.first_proto.ClearField('optional_int32') - self.second_proto.optional_int32 = 0 - self.assertNotEqual(self.first_proto, self.second_proto) - - def testNonRepeatedCompositeHasBits(self): - # Ensure that we test "has" bits as well as value for - # nonrepeated composite field. - self.first_proto.ClearField('optional_nested_message') - self.second_proto.optional_nested_message.ClearField('bb') - self.assertNotEqual(self.first_proto, self.second_proto) - self.first_proto.optional_nested_message.bb = 0 - self.first_proto.optional_nested_message.ClearField('bb') - self.assertEqual(self.first_proto, self.second_proto) - - -@testing_refleaks.TestCase -class ExtensionEqualityTest(unittest.TestCase): - - def testExtensionEquality(self): - first_proto = unittest_pb2.TestAllExtensions() - second_proto = unittest_pb2.TestAllExtensions() - self.assertEqual(first_proto, second_proto) - test_util.SetAllExtensions(first_proto) - self.assertNotEqual(first_proto, second_proto) - test_util.SetAllExtensions(second_proto) - self.assertEqual(first_proto, second_proto) - - # Ensure that we check value equality. - first_proto.Extensions[unittest_pb2.optional_int32_extension] += 1 - self.assertNotEqual(first_proto, second_proto) - first_proto.Extensions[unittest_pb2.optional_int32_extension] -= 1 - self.assertEqual(first_proto, second_proto) - - # Ensure that we also look at "has" bits. - first_proto.ClearExtension(unittest_pb2.optional_int32_extension) - second_proto.Extensions[unittest_pb2.optional_int32_extension] = 0 - self.assertNotEqual(first_proto, second_proto) - first_proto.Extensions[unittest_pb2.optional_int32_extension] = 0 - self.assertEqual(first_proto, second_proto) - - # Ensure that differences in cached values - # don't matter if "has" bits are both false. - first_proto = unittest_pb2.TestAllExtensions() - second_proto = unittest_pb2.TestAllExtensions() - self.assertEqual( - 0, first_proto.Extensions[unittest_pb2.optional_int32_extension]) - self.assertEqual(first_proto, second_proto) - - -@testing_refleaks.TestCase -class MutualRecursionEqualityTest(unittest.TestCase): - - def testEqualityWithMutualRecursion(self): - first_proto = unittest_pb2.TestMutualRecursionA() - second_proto = unittest_pb2.TestMutualRecursionA() - self.assertEqual(first_proto, second_proto) - first_proto.bb.a.bb.optional_int32 = 23 - self.assertNotEqual(first_proto, second_proto) - second_proto.bb.a.bb.optional_int32 = 23 - self.assertEqual(first_proto, second_proto) - - -@testing_refleaks.TestCase -class ByteSizeTest(unittest.TestCase): - - def setUp(self): - self.proto = unittest_pb2.TestAllTypes() - self.extended_proto = more_extensions_pb2.ExtendedMessage() - self.packed_proto = unittest_pb2.TestPackedTypes() - self.packed_extended_proto = unittest_pb2.TestPackedExtensions() - - def Size(self): - return self.proto.ByteSize() - - def testEmptyMessage(self): - self.assertEqual(0, self.proto.ByteSize()) - - def testSizedOnKwargs(self): - # Use a separate message to ensure testing right after creation. - proto = unittest_pb2.TestAllTypes() - self.assertEqual(0, proto.ByteSize()) - proto_kwargs = unittest_pb2.TestAllTypes(optional_int64 = 1) - # One byte for the tag, one to encode varint 1. - self.assertEqual(2, proto_kwargs.ByteSize()) - - def testVarints(self): - def Test(i, expected_varint_size): - self.proto.Clear() - self.proto.optional_int64 = i - # Add one to the varint size for the tag info - # for tag 1. - self.assertEqual(expected_varint_size + 1, self.Size()) - Test(0, 1) - Test(1, 1) - for i, num_bytes in zip(range(7, 63, 7), range(1, 10000)): - Test((1 << i) - 1, num_bytes) - Test(-1, 10) - Test(-2, 10) - Test(-(1 << 63), 10) - - def testStrings(self): - self.proto.optional_string = '' - # Need one byte for tag info (tag #14), and one byte for length. - self.assertEqual(2, self.Size()) - - self.proto.optional_string = 'abc' - # Need one byte for tag info (tag #14), and one byte for length. - self.assertEqual(2 + len(self.proto.optional_string), self.Size()) - - self.proto.optional_string = 'x' * 128 - # Need one byte for tag info (tag #14), and TWO bytes for length. - self.assertEqual(3 + len(self.proto.optional_string), self.Size()) - - def testOtherNumerics(self): - self.proto.optional_fixed32 = 1234 - # One byte for tag and 4 bytes for fixed32. - self.assertEqual(5, self.Size()) - self.proto = unittest_pb2.TestAllTypes() - - self.proto.optional_fixed64 = 1234 - # One byte for tag and 8 bytes for fixed64. - self.assertEqual(9, self.Size()) - self.proto = unittest_pb2.TestAllTypes() - - self.proto.optional_float = 1.234 - # One byte for tag and 4 bytes for float. - self.assertEqual(5, self.Size()) - self.proto = unittest_pb2.TestAllTypes() - - self.proto.optional_double = 1.234 - # One byte for tag and 8 bytes for float. - self.assertEqual(9, self.Size()) - self.proto = unittest_pb2.TestAllTypes() - - self.proto.optional_sint32 = 64 - # One byte for tag and 2 bytes for zig-zag-encoded 64. - self.assertEqual(3, self.Size()) - self.proto = unittest_pb2.TestAllTypes() - - def testComposites(self): - # 3 bytes. - self.proto.optional_nested_message.bb = (1 << 14) - # Plus one byte for bb tag. - # Plus 1 byte for optional_nested_message serialized size. - # Plus two bytes for optional_nested_message tag. - self.assertEqual(3 + 1 + 1 + 2, self.Size()) - - def testGroups(self): - # 4 bytes. - self.proto.optionalgroup.a = (1 << 21) - # Plus two bytes for |a| tag. - # Plus 2 * two bytes for START_GROUP and END_GROUP tags. - self.assertEqual(4 + 2 + 2*2, self.Size()) - - def testRepeatedScalars(self): - self.proto.repeated_int32.append(10) # 1 byte. - self.proto.repeated_int32.append(128) # 2 bytes. - # Also need 2 bytes for each entry for tag. - self.assertEqual(1 + 2 + 2*2, self.Size()) - - def testRepeatedScalarsExtend(self): - self.proto.repeated_int32.extend([10, 128]) # 3 bytes. - # Also need 2 bytes for each entry for tag. - self.assertEqual(1 + 2 + 2*2, self.Size()) - - def testRepeatedScalarsRemove(self): - self.proto.repeated_int32.append(10) # 1 byte. - self.proto.repeated_int32.append(128) # 2 bytes. - # Also need 2 bytes for each entry for tag. - self.assertEqual(1 + 2 + 2*2, self.Size()) - self.proto.repeated_int32.remove(128) - self.assertEqual(1 + 2, self.Size()) - - def testRepeatedComposites(self): - # Empty message. 2 bytes tag plus 1 byte length. - foreign_message_0 = self.proto.repeated_nested_message.add() - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - foreign_message_1 = self.proto.repeated_nested_message.add() - foreign_message_1.bb = 7 - self.assertEqual(2 + 1 + 2 + 1 + 1 + 1, self.Size()) - - def testRepeatedCompositesDelete(self): - # Empty message. 2 bytes tag plus 1 byte length. - foreign_message_0 = self.proto.repeated_nested_message.add() - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - foreign_message_1 = self.proto.repeated_nested_message.add() - foreign_message_1.bb = 9 - self.assertEqual(2 + 1 + 2 + 1 + 1 + 1, self.Size()) - repeated_nested_message = copy.deepcopy( - self.proto.repeated_nested_message) - - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - del self.proto.repeated_nested_message[0] - self.assertEqual(2 + 1 + 1 + 1, self.Size()) - - # Now add a new message. - foreign_message_2 = self.proto.repeated_nested_message.add() - foreign_message_2.bb = 12 - - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - self.assertEqual(2 + 1 + 1 + 1 + 2 + 1 + 1 + 1, self.Size()) - - # 2 bytes tag plus 1 byte length plus 1 byte bb tag 1 byte int. - del self.proto.repeated_nested_message[1] - self.assertEqual(2 + 1 + 1 + 1, self.Size()) - - del self.proto.repeated_nested_message[0] - self.assertEqual(0, self.Size()) - - self.assertEqual(2, len(repeated_nested_message)) - del repeated_nested_message[0:1] - # TODO(jieluo): Fix cpp extension bug when delete repeated message. - if api_implementation.Type() == 'python': - self.assertEqual(1, len(repeated_nested_message)) - del repeated_nested_message[-1] - # TODO(jieluo): Fix cpp extension bug when delete repeated message. - if api_implementation.Type() == 'python': - self.assertEqual(0, len(repeated_nested_message)) - - def testRepeatedGroups(self): - # 2-byte START_GROUP plus 2-byte END_GROUP. - group_0 = self.proto.repeatedgroup.add() - # 2-byte START_GROUP plus 2-byte |a| tag + 1-byte |a| - # plus 2-byte END_GROUP. - group_1 = self.proto.repeatedgroup.add() - group_1.a = 7 - self.assertEqual(2 + 2 + 2 + 2 + 1 + 2, self.Size()) - - def testExtensions(self): - proto = unittest_pb2.TestAllExtensions() - self.assertEqual(0, proto.ByteSize()) - extension = unittest_pb2.optional_int32_extension # Field #1, 1 byte. - proto.Extensions[extension] = 23 - # 1 byte for tag, 1 byte for value. - self.assertEqual(2, proto.ByteSize()) - field = unittest_pb2.TestAllTypes.DESCRIPTOR.fields_by_name[ - 'optional_int32'] - with self.assertRaises(KeyError): - proto.Extensions[field] = 23 - - def testCacheInvalidationForNonrepeatedScalar(self): - # Test non-extension. - self.proto.optional_int32 = 1 - self.assertEqual(2, self.proto.ByteSize()) - self.proto.optional_int32 = 128 - self.assertEqual(3, self.proto.ByteSize()) - self.proto.ClearField('optional_int32') - self.assertEqual(0, self.proto.ByteSize()) - - # Test within extension. - extension = more_extensions_pb2.optional_int_extension - self.extended_proto.Extensions[extension] = 1 - self.assertEqual(2, self.extended_proto.ByteSize()) - self.extended_proto.Extensions[extension] = 128 - self.assertEqual(3, self.extended_proto.ByteSize()) - self.extended_proto.ClearExtension(extension) - self.assertEqual(0, self.extended_proto.ByteSize()) - - def testCacheInvalidationForRepeatedScalar(self): - # Test non-extension. - self.proto.repeated_int32.append(1) - self.assertEqual(3, self.proto.ByteSize()) - self.proto.repeated_int32.append(1) - self.assertEqual(6, self.proto.ByteSize()) - self.proto.repeated_int32[1] = 128 - self.assertEqual(7, self.proto.ByteSize()) - self.proto.ClearField('repeated_int32') - self.assertEqual(0, self.proto.ByteSize()) - - # Test within extension. - extension = more_extensions_pb2.repeated_int_extension - repeated = self.extended_proto.Extensions[extension] - repeated.append(1) - self.assertEqual(2, self.extended_proto.ByteSize()) - repeated.append(1) - self.assertEqual(4, self.extended_proto.ByteSize()) - repeated[1] = 128 - self.assertEqual(5, self.extended_proto.ByteSize()) - self.extended_proto.ClearExtension(extension) - self.assertEqual(0, self.extended_proto.ByteSize()) - - def testCacheInvalidationForNonrepeatedMessage(self): - # Test non-extension. - self.proto.optional_foreign_message.c = 1 - self.assertEqual(5, self.proto.ByteSize()) - self.proto.optional_foreign_message.c = 128 - self.assertEqual(6, self.proto.ByteSize()) - self.proto.optional_foreign_message.ClearField('c') - self.assertEqual(3, self.proto.ByteSize()) - self.proto.ClearField('optional_foreign_message') - self.assertEqual(0, self.proto.ByteSize()) - - if api_implementation.Type() == 'python': - # This is only possible in pure-Python implementation of the API. - child = self.proto.optional_foreign_message - self.proto.ClearField('optional_foreign_message') - child.c = 128 - self.assertEqual(0, self.proto.ByteSize()) - - # Test within extension. - extension = more_extensions_pb2.optional_message_extension - child = self.extended_proto.Extensions[extension] - self.assertEqual(0, self.extended_proto.ByteSize()) - child.foreign_message_int = 1 - self.assertEqual(4, self.extended_proto.ByteSize()) - child.foreign_message_int = 128 - self.assertEqual(5, self.extended_proto.ByteSize()) - self.extended_proto.ClearExtension(extension) - self.assertEqual(0, self.extended_proto.ByteSize()) - - def testCacheInvalidationForRepeatedMessage(self): - # Test non-extension. - child0 = self.proto.repeated_foreign_message.add() - self.assertEqual(3, self.proto.ByteSize()) - self.proto.repeated_foreign_message.add() - self.assertEqual(6, self.proto.ByteSize()) - child0.c = 1 - self.assertEqual(8, self.proto.ByteSize()) - self.proto.ClearField('repeated_foreign_message') - self.assertEqual(0, self.proto.ByteSize()) - - # Test within extension. - extension = more_extensions_pb2.repeated_message_extension - child_list = self.extended_proto.Extensions[extension] - child0 = child_list.add() - self.assertEqual(2, self.extended_proto.ByteSize()) - child_list.add() - self.assertEqual(4, self.extended_proto.ByteSize()) - child0.foreign_message_int = 1 - self.assertEqual(6, self.extended_proto.ByteSize()) - child0.ClearField('foreign_message_int') - self.assertEqual(4, self.extended_proto.ByteSize()) - self.extended_proto.ClearExtension(extension) - self.assertEqual(0, self.extended_proto.ByteSize()) - - def testPackedRepeatedScalars(self): - self.assertEqual(0, self.packed_proto.ByteSize()) - - self.packed_proto.packed_int32.append(10) # 1 byte. - self.packed_proto.packed_int32.append(128) # 2 bytes. - # The tag is 2 bytes (the field number is 90), and the varint - # storing the length is 1 byte. - int_size = 1 + 2 + 3 - self.assertEqual(int_size, self.packed_proto.ByteSize()) - - self.packed_proto.packed_double.append(4.2) # 8 bytes - self.packed_proto.packed_double.append(3.25) # 8 bytes - # 2 more tag bytes, 1 more length byte. - double_size = 8 + 8 + 3 - self.assertEqual(int_size+double_size, self.packed_proto.ByteSize()) - - self.packed_proto.ClearField('packed_int32') - self.assertEqual(double_size, self.packed_proto.ByteSize()) - - def testPackedExtensions(self): - self.assertEqual(0, self.packed_extended_proto.ByteSize()) - extension = self.packed_extended_proto.Extensions[ - unittest_pb2.packed_fixed32_extension] - extension.extend([1, 2, 3, 4]) # 16 bytes - # Tag is 3 bytes. - self.assertEqual(19, self.packed_extended_proto.ByteSize()) - - -# Issues to be sure to cover include: -# * Handling of unrecognized tags ("uninterpreted_bytes"). -# * Handling of MessageSets. -# * Consistent ordering of tags in the wire format, -# including ordering between extensions and non-extension -# fields. -# * Consistent serialization of negative numbers, especially -# negative int32s. -# * Handling of empty submessages (with and without "has" -# bits set). - -@testing_refleaks.TestCase -class SerializationTest(unittest.TestCase): - - def testSerializeEmtpyMessage(self): - first_proto = unittest_pb2.TestAllTypes() - second_proto = unittest_pb2.TestAllTypes() - serialized = first_proto.SerializeToString() - self.assertEqual(first_proto.ByteSize(), len(serialized)) - self.assertEqual( - len(serialized), - second_proto.MergeFromString(serialized)) - self.assertEqual(first_proto, second_proto) - - def testSerializeAllFields(self): - first_proto = unittest_pb2.TestAllTypes() - second_proto = unittest_pb2.TestAllTypes() - test_util.SetAllFields(first_proto) - serialized = first_proto.SerializeToString() - self.assertEqual(first_proto.ByteSize(), len(serialized)) - self.assertEqual( - len(serialized), - second_proto.MergeFromString(serialized)) - self.assertEqual(first_proto, second_proto) - - def testSerializeAllExtensions(self): - first_proto = unittest_pb2.TestAllExtensions() - second_proto = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(first_proto) - serialized = first_proto.SerializeToString() - self.assertEqual( - len(serialized), - second_proto.MergeFromString(serialized)) - self.assertEqual(first_proto, second_proto) - - def testSerializeWithOptionalGroup(self): - first_proto = unittest_pb2.TestAllTypes() - second_proto = unittest_pb2.TestAllTypes() - first_proto.optionalgroup.a = 242 - serialized = first_proto.SerializeToString() - self.assertEqual( - len(serialized), - second_proto.MergeFromString(serialized)) - self.assertEqual(first_proto, second_proto) - - def testSerializeNegativeValues(self): - first_proto = unittest_pb2.TestAllTypes() - - first_proto.optional_int32 = -1 - first_proto.optional_int64 = -(2 << 40) - first_proto.optional_sint32 = -3 - first_proto.optional_sint64 = -(4 << 40) - first_proto.optional_sfixed32 = -5 - first_proto.optional_sfixed64 = -(6 << 40) - - second_proto = unittest_pb2.TestAllTypes.FromString( - first_proto.SerializeToString()) - - self.assertEqual(first_proto, second_proto) - - def testParseTruncated(self): - # This test is only applicable for the Python implementation of the API. - if api_implementation.Type() != 'python': - return - - first_proto = unittest_pb2.TestAllTypes() - test_util.SetAllFields(first_proto) - serialized = memoryview(first_proto.SerializeToString()) - - for truncation_point in range(len(serialized) + 1): - try: - second_proto = unittest_pb2.TestAllTypes() - unknown_fields = unittest_pb2.TestEmptyMessage() - pos = second_proto._InternalParse(serialized, 0, truncation_point) - # If we didn't raise an error then we read exactly the amount expected. - self.assertEqual(truncation_point, pos) - - # Parsing to unknown fields should not throw if parsing to known fields - # did not. - try: - pos2 = unknown_fields._InternalParse(serialized, 0, truncation_point) - self.assertEqual(truncation_point, pos2) - except message.DecodeError: - self.fail('Parsing unknown fields failed when parsing known fields ' - 'did not.') - except message.DecodeError: - # Parsing unknown fields should also fail. - self.assertRaises(message.DecodeError, unknown_fields._InternalParse, - serialized, 0, truncation_point) - - def testCanonicalSerializationOrder(self): - proto = more_messages_pb2.OutOfOrderFields() - # These are also their tag numbers. Even though we're setting these in - # reverse-tag order AND they're listed in reverse tag-order in the .proto - # file, they should nonetheless be serialized in tag order. - proto.optional_sint32 = 5 - proto.Extensions[more_messages_pb2.optional_uint64] = 4 - proto.optional_uint32 = 3 - proto.Extensions[more_messages_pb2.optional_int64] = 2 - proto.optional_int32 = 1 - serialized = proto.SerializeToString() - self.assertEqual(proto.ByteSize(), len(serialized)) - d = _MiniDecoder(serialized) - ReadTag = d.ReadFieldNumberAndWireType - self.assertEqual((1, wire_format.WIRETYPE_VARINT), ReadTag()) - self.assertEqual(1, d.ReadInt32()) - self.assertEqual((2, wire_format.WIRETYPE_VARINT), ReadTag()) - self.assertEqual(2, d.ReadInt64()) - self.assertEqual((3, wire_format.WIRETYPE_VARINT), ReadTag()) - self.assertEqual(3, d.ReadUInt32()) - self.assertEqual((4, wire_format.WIRETYPE_VARINT), ReadTag()) - self.assertEqual(4, d.ReadUInt64()) - self.assertEqual((5, wire_format.WIRETYPE_VARINT), ReadTag()) - self.assertEqual(5, d.ReadSInt32()) - - def testCanonicalSerializationOrderSameAsCpp(self): - # Copy of the same test we use for C++. - proto = unittest_pb2.TestFieldOrderings() - test_util.SetAllFieldsAndExtensions(proto) - serialized = proto.SerializeToString() - test_util.ExpectAllFieldsAndExtensionsInOrder(serialized) - - def testMergeFromStringWhenFieldsAlreadySet(self): - first_proto = unittest_pb2.TestAllTypes() - first_proto.repeated_string.append('foobar') - first_proto.optional_int32 = 23 - first_proto.optional_nested_message.bb = 42 - serialized = first_proto.SerializeToString() - - second_proto = unittest_pb2.TestAllTypes() - second_proto.repeated_string.append('baz') - second_proto.optional_int32 = 100 - second_proto.optional_nested_message.bb = 999 - - bytes_parsed = second_proto.MergeFromString(serialized) - self.assertEqual(len(serialized), bytes_parsed) - - # Ensure that we append to repeated fields. - self.assertEqual(['baz', 'foobar'], list(second_proto.repeated_string)) - # Ensure that we overwrite nonrepeatd scalars. - self.assertEqual(23, second_proto.optional_int32) - # Ensure that we recursively call MergeFromString() on - # submessages. - self.assertEqual(42, second_proto.optional_nested_message.bb) - - def testMessageSetWireFormat(self): - proto = message_set_extensions_pb2.TestMessageSet() - extension_message1 = message_set_extensions_pb2.TestMessageSetExtension1 - extension_message2 = message_set_extensions_pb2.TestMessageSetExtension2 - extension1 = extension_message1.message_set_extension - extension2 = extension_message2.message_set_extension - extension3 = message_set_extensions_pb2.message_set_extension3 - proto.Extensions[extension1].i = 123 - proto.Extensions[extension2].str = 'foo' - proto.Extensions[extension3].text = 'bar' - - # Serialize using the MessageSet wire format (this is specified in the - # .proto file). - serialized = proto.SerializeToString() - - raw = unittest_mset_pb2.RawMessageSet() - self.assertEqual(False, - raw.DESCRIPTOR.GetOptions().message_set_wire_format) - self.assertEqual( - len(serialized), - raw.MergeFromString(serialized)) - self.assertEqual(3, len(raw.item)) - - message1 = message_set_extensions_pb2.TestMessageSetExtension1() - self.assertEqual( - len(raw.item[0].message), - message1.MergeFromString(raw.item[0].message)) - self.assertEqual(123, message1.i) - - message2 = message_set_extensions_pb2.TestMessageSetExtension2() - self.assertEqual( - len(raw.item[1].message), - message2.MergeFromString(raw.item[1].message)) - self.assertEqual('foo', message2.str) - - message3 = message_set_extensions_pb2.TestMessageSetExtension3() - self.assertEqual( - len(raw.item[2].message), - message3.MergeFromString(raw.item[2].message)) - self.assertEqual('bar', message3.text) - - # Deserialize using the MessageSet wire format. - proto2 = message_set_extensions_pb2.TestMessageSet() - self.assertEqual( - len(serialized), - proto2.MergeFromString(serialized)) - self.assertEqual(123, proto2.Extensions[extension1].i) - self.assertEqual('foo', proto2.Extensions[extension2].str) - self.assertEqual('bar', proto2.Extensions[extension3].text) - - # Check byte size. - self.assertEqual(proto2.ByteSize(), len(serialized)) - self.assertEqual(proto.ByteSize(), len(serialized)) - - def testMessageSetWireFormatUnknownExtension(self): - # Create a message using the message set wire format with an unknown - # message. - raw = unittest_mset_pb2.RawMessageSet() - - # Add an item. - item = raw.item.add() - item.type_id = 98418603 - extension_message1 = message_set_extensions_pb2.TestMessageSetExtension1 - message1 = message_set_extensions_pb2.TestMessageSetExtension1() - message1.i = 12345 - item.message = message1.SerializeToString() - - # Add a second, unknown extension. - item = raw.item.add() - item.type_id = 98418604 - extension_message1 = message_set_extensions_pb2.TestMessageSetExtension1 - message1 = message_set_extensions_pb2.TestMessageSetExtension1() - message1.i = 12346 - item.message = message1.SerializeToString() - - # Add another unknown extension. - item = raw.item.add() - item.type_id = 98418605 - message1 = message_set_extensions_pb2.TestMessageSetExtension2() - message1.str = 'foo' - item.message = message1.SerializeToString() - - serialized = raw.SerializeToString() - - # Parse message using the message set wire format. - proto = message_set_extensions_pb2.TestMessageSet() - self.assertEqual( - len(serialized), - proto.MergeFromString(serialized)) - - # Check that the message parsed well. - extension_message1 = message_set_extensions_pb2.TestMessageSetExtension1 - extension1 = extension_message1.message_set_extension - self.assertEqual(12345, proto.Extensions[extension1].i) - - def testUnknownFields(self): - proto = unittest_pb2.TestAllTypes() - test_util.SetAllFields(proto) - - serialized = proto.SerializeToString() - - # The empty message should be parsable with all of the fields - # unknown. - proto2 = unittest_pb2.TestEmptyMessage() - - # Parsing this message should succeed. - self.assertEqual( - len(serialized), - proto2.MergeFromString(serialized)) - - # Now test with a int64 field set. - proto = unittest_pb2.TestAllTypes() - proto.optional_int64 = 0x0fffffffffffffff - serialized = proto.SerializeToString() - # The empty message should be parsable with all of the fields - # unknown. - proto2 = unittest_pb2.TestEmptyMessage() - # Parsing this message should succeed. - self.assertEqual( - len(serialized), - proto2.MergeFromString(serialized)) - - def _CheckRaises(self, exc_class, callable_obj, exception): - """This method checks if the exception type and message are as expected.""" - try: - callable_obj() - except exc_class as ex: - # Check if the exception message is the right one. - self.assertEqual(exception, str(ex)) - return - else: - raise self.failureException('%s not raised' % str(exc_class)) - - def testSerializeUninitialized(self): - proto = unittest_pb2.TestRequired() - self._CheckRaises( - message.EncodeError, - proto.SerializeToString, - 'Message protobuf_unittest.TestRequired is missing required fields: ' - 'a,b,c') - # Shouldn't raise exceptions. - partial = proto.SerializePartialToString() - - proto2 = unittest_pb2.TestRequired() - self.assertFalse(proto2.HasField('a')) - # proto2 ParseFromString does not check that required fields are set. - proto2.ParseFromString(partial) - self.assertFalse(proto2.HasField('a')) - - proto.a = 1 - self._CheckRaises( - message.EncodeError, - proto.SerializeToString, - 'Message protobuf_unittest.TestRequired is missing required fields: b,c') - # Shouldn't raise exceptions. - partial = proto.SerializePartialToString() - - proto.b = 2 - self._CheckRaises( - message.EncodeError, - proto.SerializeToString, - 'Message protobuf_unittest.TestRequired is missing required fields: c') - # Shouldn't raise exceptions. - partial = proto.SerializePartialToString() - - proto.c = 3 - serialized = proto.SerializeToString() - # Shouldn't raise exceptions. - partial = proto.SerializePartialToString() - - proto2 = unittest_pb2.TestRequired() - self.assertEqual( - len(serialized), - proto2.MergeFromString(serialized)) - self.assertEqual(1, proto2.a) - self.assertEqual(2, proto2.b) - self.assertEqual(3, proto2.c) - self.assertEqual( - len(partial), - proto2.MergeFromString(partial)) - self.assertEqual(1, proto2.a) - self.assertEqual(2, proto2.b) - self.assertEqual(3, proto2.c) - - def testSerializeUninitializedSubMessage(self): - proto = unittest_pb2.TestRequiredForeign() - - # Sub-message doesn't exist yet, so this succeeds. - proto.SerializeToString() - - proto.optional_message.a = 1 - self._CheckRaises( - message.EncodeError, - proto.SerializeToString, - 'Message protobuf_unittest.TestRequiredForeign ' - 'is missing required fields: ' - 'optional_message.b,optional_message.c') - - proto.optional_message.b = 2 - proto.optional_message.c = 3 - proto.SerializeToString() - - proto.repeated_message.add().a = 1 - proto.repeated_message.add().b = 2 - self._CheckRaises( - message.EncodeError, - proto.SerializeToString, - 'Message protobuf_unittest.TestRequiredForeign is missing required fields: ' - 'repeated_message[0].b,repeated_message[0].c,' - 'repeated_message[1].a,repeated_message[1].c') - - proto.repeated_message[0].b = 2 - proto.repeated_message[0].c = 3 - proto.repeated_message[1].a = 1 - proto.repeated_message[1].c = 3 - proto.SerializeToString() - - def testSerializeAllPackedFields(self): - first_proto = unittest_pb2.TestPackedTypes() - second_proto = unittest_pb2.TestPackedTypes() - test_util.SetAllPackedFields(first_proto) - serialized = first_proto.SerializeToString() - self.assertEqual(first_proto.ByteSize(), len(serialized)) - bytes_read = second_proto.MergeFromString(serialized) - self.assertEqual(second_proto.ByteSize(), bytes_read) - self.assertEqual(first_proto, second_proto) - - def testSerializeAllPackedExtensions(self): - first_proto = unittest_pb2.TestPackedExtensions() - second_proto = unittest_pb2.TestPackedExtensions() - test_util.SetAllPackedExtensions(first_proto) - serialized = first_proto.SerializeToString() - bytes_read = second_proto.MergeFromString(serialized) - self.assertEqual(second_proto.ByteSize(), bytes_read) - self.assertEqual(first_proto, second_proto) - - def testMergePackedFromStringWhenSomeFieldsAlreadySet(self): - first_proto = unittest_pb2.TestPackedTypes() - first_proto.packed_int32.extend([1, 2]) - first_proto.packed_double.append(3.0) - serialized = first_proto.SerializeToString() - - second_proto = unittest_pb2.TestPackedTypes() - second_proto.packed_int32.append(3) - second_proto.packed_double.extend([1.0, 2.0]) - second_proto.packed_sint32.append(4) - - self.assertEqual( - len(serialized), - second_proto.MergeFromString(serialized)) - self.assertEqual([3, 1, 2], second_proto.packed_int32) - self.assertEqual([1.0, 2.0, 3.0], second_proto.packed_double) - self.assertEqual([4], second_proto.packed_sint32) - - def testPackedFieldsWireFormat(self): - proto = unittest_pb2.TestPackedTypes() - proto.packed_int32.extend([1, 2, 150, 3]) # 1 + 1 + 2 + 1 bytes - proto.packed_double.extend([1.0, 1000.0]) # 8 + 8 bytes - proto.packed_float.append(2.0) # 4 bytes, will be before double - serialized = proto.SerializeToString() - self.assertEqual(proto.ByteSize(), len(serialized)) - d = _MiniDecoder(serialized) - ReadTag = d.ReadFieldNumberAndWireType - self.assertEqual((90, wire_format.WIRETYPE_LENGTH_DELIMITED), ReadTag()) - self.assertEqual(1+1+1+2, d.ReadInt32()) - self.assertEqual(1, d.ReadInt32()) - self.assertEqual(2, d.ReadInt32()) - self.assertEqual(150, d.ReadInt32()) - self.assertEqual(3, d.ReadInt32()) - self.assertEqual((100, wire_format.WIRETYPE_LENGTH_DELIMITED), ReadTag()) - self.assertEqual(4, d.ReadInt32()) - self.assertEqual(2.0, d.ReadFloat()) - self.assertEqual((101, wire_format.WIRETYPE_LENGTH_DELIMITED), ReadTag()) - self.assertEqual(8+8, d.ReadInt32()) - self.assertEqual(1.0, d.ReadDouble()) - self.assertEqual(1000.0, d.ReadDouble()) - self.assertTrue(d.EndOfStream()) - - def testParsePackedFromUnpacked(self): - unpacked = unittest_pb2.TestUnpackedTypes() - test_util.SetAllUnpackedFields(unpacked) - packed = unittest_pb2.TestPackedTypes() - serialized = unpacked.SerializeToString() - self.assertEqual( - len(serialized), - packed.MergeFromString(serialized)) - expected = unittest_pb2.TestPackedTypes() - test_util.SetAllPackedFields(expected) - self.assertEqual(expected, packed) - - def testParseUnpackedFromPacked(self): - packed = unittest_pb2.TestPackedTypes() - test_util.SetAllPackedFields(packed) - unpacked = unittest_pb2.TestUnpackedTypes() - serialized = packed.SerializeToString() - self.assertEqual( - len(serialized), - unpacked.MergeFromString(serialized)) - expected = unittest_pb2.TestUnpackedTypes() - test_util.SetAllUnpackedFields(expected) - self.assertEqual(expected, unpacked) - - def testFieldNumbers(self): - proto = unittest_pb2.TestAllTypes() - self.assertEqual(unittest_pb2.TestAllTypes.NestedMessage.BB_FIELD_NUMBER, 1) - self.assertEqual(unittest_pb2.TestAllTypes.OPTIONAL_INT32_FIELD_NUMBER, 1) - self.assertEqual(unittest_pb2.TestAllTypes.OPTIONALGROUP_FIELD_NUMBER, 16) - self.assertEqual( - unittest_pb2.TestAllTypes.OPTIONAL_NESTED_MESSAGE_FIELD_NUMBER, 18) - self.assertEqual( - unittest_pb2.TestAllTypes.OPTIONAL_NESTED_ENUM_FIELD_NUMBER, 21) - self.assertEqual(unittest_pb2.TestAllTypes.REPEATED_INT32_FIELD_NUMBER, 31) - self.assertEqual(unittest_pb2.TestAllTypes.REPEATEDGROUP_FIELD_NUMBER, 46) - self.assertEqual( - unittest_pb2.TestAllTypes.REPEATED_NESTED_MESSAGE_FIELD_NUMBER, 48) - self.assertEqual( - unittest_pb2.TestAllTypes.REPEATED_NESTED_ENUM_FIELD_NUMBER, 51) - - def testExtensionFieldNumbers(self): - self.assertEqual(unittest_pb2.TestRequired.single.number, 1000) - self.assertEqual(unittest_pb2.TestRequired.SINGLE_FIELD_NUMBER, 1000) - self.assertEqual(unittest_pb2.TestRequired.multi.number, 1001) - self.assertEqual(unittest_pb2.TestRequired.MULTI_FIELD_NUMBER, 1001) - self.assertEqual(unittest_pb2.optional_int32_extension.number, 1) - self.assertEqual(unittest_pb2.OPTIONAL_INT32_EXTENSION_FIELD_NUMBER, 1) - self.assertEqual(unittest_pb2.optionalgroup_extension.number, 16) - self.assertEqual(unittest_pb2.OPTIONALGROUP_EXTENSION_FIELD_NUMBER, 16) - self.assertEqual(unittest_pb2.optional_nested_message_extension.number, 18) - self.assertEqual( - unittest_pb2.OPTIONAL_NESTED_MESSAGE_EXTENSION_FIELD_NUMBER, 18) - self.assertEqual(unittest_pb2.optional_nested_enum_extension.number, 21) - self.assertEqual(unittest_pb2.OPTIONAL_NESTED_ENUM_EXTENSION_FIELD_NUMBER, - 21) - self.assertEqual(unittest_pb2.repeated_int32_extension.number, 31) - self.assertEqual(unittest_pb2.REPEATED_INT32_EXTENSION_FIELD_NUMBER, 31) - self.assertEqual(unittest_pb2.repeatedgroup_extension.number, 46) - self.assertEqual(unittest_pb2.REPEATEDGROUP_EXTENSION_FIELD_NUMBER, 46) - self.assertEqual(unittest_pb2.repeated_nested_message_extension.number, 48) - self.assertEqual( - unittest_pb2.REPEATED_NESTED_MESSAGE_EXTENSION_FIELD_NUMBER, 48) - self.assertEqual(unittest_pb2.repeated_nested_enum_extension.number, 51) - self.assertEqual(unittest_pb2.REPEATED_NESTED_ENUM_EXTENSION_FIELD_NUMBER, - 51) - - def testFieldProperties(self): - cls = unittest_pb2.TestAllTypes - self.assertIs(cls.optional_int32.DESCRIPTOR, - cls.DESCRIPTOR.fields_by_name['optional_int32']) - self.assertEqual(cls.OPTIONAL_INT32_FIELD_NUMBER, - cls.optional_int32.DESCRIPTOR.number) - self.assertIs(cls.optional_nested_message.DESCRIPTOR, - cls.DESCRIPTOR.fields_by_name['optional_nested_message']) - self.assertEqual(cls.OPTIONAL_NESTED_MESSAGE_FIELD_NUMBER, - cls.optional_nested_message.DESCRIPTOR.number) - self.assertIs(cls.repeated_int32.DESCRIPTOR, - cls.DESCRIPTOR.fields_by_name['repeated_int32']) - self.assertEqual(cls.REPEATED_INT32_FIELD_NUMBER, - cls.repeated_int32.DESCRIPTOR.number) - - def testFieldDataDescriptor(self): - msg = unittest_pb2.TestAllTypes() - msg.optional_int32 = 42 - self.assertEqual(unittest_pb2.TestAllTypes.optional_int32.__get__(msg), 42) - unittest_pb2.TestAllTypes.optional_int32.__set__(msg, 25) - self.assertEqual(msg.optional_int32, 25) - with self.assertRaises(AttributeError): - del msg.optional_int32 - try: - unittest_pb2.ForeignMessage.c.__get__(msg) - except TypeError: - pass # The cpp implementation cannot mix fields from other messages. - # This test exercises a specific check that avoids a crash. - else: - pass # The python implementation allows fields from other messages. - # This is useless, but works. - - def testInitKwargs(self): - proto = unittest_pb2.TestAllTypes( - optional_int32=1, - optional_string='foo', - optional_bool=True, - optional_bytes=b'bar', - optional_nested_message=unittest_pb2.TestAllTypes.NestedMessage(bb=1), - optional_foreign_message=unittest_pb2.ForeignMessage(c=1), - optional_nested_enum=unittest_pb2.TestAllTypes.FOO, - optional_foreign_enum=unittest_pb2.FOREIGN_FOO, - repeated_int32=[1, 2, 3]) - self.assertTrue(proto.IsInitialized()) - self.assertTrue(proto.HasField('optional_int32')) - self.assertTrue(proto.HasField('optional_string')) - self.assertTrue(proto.HasField('optional_bool')) - self.assertTrue(proto.HasField('optional_bytes')) - self.assertTrue(proto.HasField('optional_nested_message')) - self.assertTrue(proto.HasField('optional_foreign_message')) - self.assertTrue(proto.HasField('optional_nested_enum')) - self.assertTrue(proto.HasField('optional_foreign_enum')) - self.assertEqual(1, proto.optional_int32) - self.assertEqual('foo', proto.optional_string) - self.assertEqual(True, proto.optional_bool) - self.assertEqual(b'bar', proto.optional_bytes) - self.assertEqual(1, proto.optional_nested_message.bb) - self.assertEqual(1, proto.optional_foreign_message.c) - self.assertEqual(unittest_pb2.TestAllTypes.FOO, - proto.optional_nested_enum) - self.assertEqual(unittest_pb2.FOREIGN_FOO, proto.optional_foreign_enum) - self.assertEqual([1, 2, 3], proto.repeated_int32) - - def testInitArgsUnknownFieldName(self): - def InitalizeEmptyMessageWithExtraKeywordArg(): - unused_proto = unittest_pb2.TestEmptyMessage(unknown='unknown') - self._CheckRaises( - ValueError, - InitalizeEmptyMessageWithExtraKeywordArg, - 'Protocol message TestEmptyMessage has no "unknown" field.') - - def testInitRequiredKwargs(self): - proto = unittest_pb2.TestRequired(a=1, b=1, c=1) - self.assertTrue(proto.IsInitialized()) - self.assertTrue(proto.HasField('a')) - self.assertTrue(proto.HasField('b')) - self.assertTrue(proto.HasField('c')) - self.assertFalse(proto.HasField('dummy2')) - self.assertEqual(1, proto.a) - self.assertEqual(1, proto.b) - self.assertEqual(1, proto.c) - - def testInitRequiredForeignKwargs(self): - proto = unittest_pb2.TestRequiredForeign( - optional_message=unittest_pb2.TestRequired(a=1, b=1, c=1)) - self.assertTrue(proto.IsInitialized()) - self.assertTrue(proto.HasField('optional_message')) - self.assertTrue(proto.optional_message.IsInitialized()) - self.assertTrue(proto.optional_message.HasField('a')) - self.assertTrue(proto.optional_message.HasField('b')) - self.assertTrue(proto.optional_message.HasField('c')) - self.assertFalse(proto.optional_message.HasField('dummy2')) - self.assertEqual(unittest_pb2.TestRequired(a=1, b=1, c=1), - proto.optional_message) - self.assertEqual(1, proto.optional_message.a) - self.assertEqual(1, proto.optional_message.b) - self.assertEqual(1, proto.optional_message.c) - - def testInitRepeatedKwargs(self): - proto = unittest_pb2.TestAllTypes(repeated_int32=[1, 2, 3]) - self.assertTrue(proto.IsInitialized()) - self.assertEqual(1, proto.repeated_int32[0]) - self.assertEqual(2, proto.repeated_int32[1]) - self.assertEqual(3, proto.repeated_int32[2]) - - -@testing_refleaks.TestCase -class OptionsTest(unittest.TestCase): - - def testMessageOptions(self): - proto = message_set_extensions_pb2.TestMessageSet() - self.assertEqual(True, - proto.DESCRIPTOR.GetOptions().message_set_wire_format) - proto = unittest_pb2.TestAllTypes() - self.assertEqual(False, - proto.DESCRIPTOR.GetOptions().message_set_wire_format) - - def testPackedOptions(self): - proto = unittest_pb2.TestAllTypes() - proto.optional_int32 = 1 - proto.optional_double = 3.0 - for field_descriptor, _ in proto.ListFields(): - self.assertEqual(False, field_descriptor.GetOptions().packed) - - proto = unittest_pb2.TestPackedTypes() - proto.packed_int32.append(1) - proto.packed_double.append(3.0) - for field_descriptor, _ in proto.ListFields(): - self.assertEqual(True, field_descriptor.GetOptions().packed) - self.assertEqual(descriptor.FieldDescriptor.LABEL_REPEATED, - field_descriptor.label) - - - -@testing_refleaks.TestCase -class ClassAPITest(unittest.TestCase): - - @unittest.skipIf( - api_implementation.Type() != 'python', - 'C++ implementation requires a call to MakeDescriptor()') - @testing_refleaks.SkipReferenceLeakChecker('MakeClass is not repeatable') - def testMakeClassWithNestedDescriptor(self): - leaf_desc = descriptor.Descriptor( - 'leaf', 'package.parent.child.leaf', '', - containing_type=None, fields=[], - nested_types=[], enum_types=[], - extensions=[], - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - child_desc = descriptor.Descriptor( - 'child', 'package.parent.child', '', - containing_type=None, fields=[], - nested_types=[leaf_desc], enum_types=[], - extensions=[], - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - sibling_desc = descriptor.Descriptor( - 'sibling', 'package.parent.sibling', - '', containing_type=None, fields=[], - nested_types=[], enum_types=[], - extensions=[], - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - parent_desc = descriptor.Descriptor( - 'parent', 'package.parent', '', - containing_type=None, fields=[], - nested_types=[child_desc, sibling_desc], - enum_types=[], extensions=[], - # pylint: disable=protected-access - create_key=descriptor._internal_create_key) - reflection.MakeClass(parent_desc) - - def _GetSerializedFileDescriptor(self, name): - """Get a serialized representation of a test FileDescriptorProto. - - Args: - name: All calls to this must use a unique message name, to avoid - collisions in the cpp descriptor pool. - Returns: - A string containing the serialized form of a test FileDescriptorProto. - """ - file_descriptor_str = ( - 'message_type {' - ' name: "' + name + '"' - ' field {' - ' name: "flat"' - ' number: 1' - ' label: LABEL_REPEATED' - ' type: TYPE_UINT32' - ' }' - ' field {' - ' name: "bar"' - ' number: 2' - ' label: LABEL_OPTIONAL' - ' type: TYPE_MESSAGE' - ' type_name: "Bar"' - ' }' - ' nested_type {' - ' name: "Bar"' - ' field {' - ' name: "baz"' - ' number: 3' - ' label: LABEL_OPTIONAL' - ' type: TYPE_MESSAGE' - ' type_name: "Baz"' - ' }' - ' nested_type {' - ' name: "Baz"' - ' enum_type {' - ' name: "deep_enum"' - ' value {' - ' name: "VALUE_A"' - ' number: 0' - ' }' - ' }' - ' field {' - ' name: "deep"' - ' number: 4' - ' label: LABEL_OPTIONAL' - ' type: TYPE_UINT32' - ' }' - ' }' - ' }' - '}') - file_descriptor = descriptor_pb2.FileDescriptorProto() - text_format.Merge(file_descriptor_str, file_descriptor) - return file_descriptor.SerializeToString() - - @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') - # This test can only run once; the second time, it raises errors about - # conflicting message descriptors. - def testParsingFlatClassWithExplicitClassDeclaration(self): - """Test that the generated class can parse a flat message.""" - # TODO(xiaofeng): This test fails with cpp implementation in the call - # of six.with_metaclass(). The other two callsites of with_metaclass - # in this file are both excluded from cpp test, so it might be expected - # to fail. Need someone more familiar with the python code to take a - # look at this. - if api_implementation.Type() != 'python': - return - file_descriptor = descriptor_pb2.FileDescriptorProto() - file_descriptor.ParseFromString(self._GetSerializedFileDescriptor('A')) - msg_descriptor = descriptor.MakeDescriptor( - file_descriptor.message_type[0]) - - class MessageClass( - message.Message, metaclass=reflection.GeneratedProtocolMessageType): - DESCRIPTOR = msg_descriptor - msg = MessageClass() - msg_str = ( - 'flat: 0 ' - 'flat: 1 ' - 'flat: 2 ') - text_format.Merge(msg_str, msg) - self.assertEqual(msg.flat, [0, 1, 2]) - - @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') - def testParsingFlatClass(self): - """Test that the generated class can parse a flat message.""" - file_descriptor = descriptor_pb2.FileDescriptorProto() - file_descriptor.ParseFromString(self._GetSerializedFileDescriptor('B')) - msg_descriptor = descriptor.MakeDescriptor( - file_descriptor.message_type[0]) - msg_class = reflection.MakeClass(msg_descriptor) - msg = msg_class() - msg_str = ( - 'flat: 0 ' - 'flat: 1 ' - 'flat: 2 ') - text_format.Merge(msg_str, msg) - self.assertEqual(msg.flat, [0, 1, 2]) - - @testing_refleaks.SkipReferenceLeakChecker('MakeDescriptor is not repeatable') - def testParsingNestedClass(self): - """Test that the generated class can parse a nested message.""" - file_descriptor = descriptor_pb2.FileDescriptorProto() - file_descriptor.ParseFromString(self._GetSerializedFileDescriptor('C')) - msg_descriptor = descriptor.MakeDescriptor( - file_descriptor.message_type[0]) - msg_class = reflection.MakeClass(msg_descriptor) - msg = msg_class() - msg_str = ( - 'bar {' - ' baz {' - ' deep: 4' - ' }' - '}') - text_format.Merge(msg_str, msg) - self.assertEqual(msg.bar.baz.deep, 4) - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/service_reflection_test.py b/ext/protobuf/Python/google/protobuf/internal/service_reflection_test.py deleted file mode 100644 index 8e72213b5..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/service_reflection_test.py +++ /dev/null @@ -1,139 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.internal.service_reflection.""" - -__author__ = 'petar@google.com (Petar Petrov)' - - -import unittest - -from google.protobuf import unittest_pb2 -from google.protobuf import service_reflection -from google.protobuf import service - - -class FooUnitTest(unittest.TestCase): - - def testService(self): - class MockRpcChannel(service.RpcChannel): - def CallMethod(self, method, controller, request, response, callback): - self.method = method - self.controller = controller - self.request = request - callback(response) - - class MockRpcController(service.RpcController): - def SetFailed(self, msg): - self.failure_message = msg - - self.callback_response = None - - class MyService(unittest_pb2.TestService): - pass - - self.callback_response = None - - def MyCallback(response): - self.callback_response = response - - rpc_controller = MockRpcController() - channel = MockRpcChannel() - srvc = MyService() - srvc.Foo(rpc_controller, unittest_pb2.FooRequest(), MyCallback) - self.assertEqual('Method Foo not implemented.', - rpc_controller.failure_message) - self.assertEqual(None, self.callback_response) - - rpc_controller.failure_message = None - - service_descriptor = unittest_pb2.TestService.GetDescriptor() - srvc.CallMethod(service_descriptor.methods[1], rpc_controller, - unittest_pb2.BarRequest(), MyCallback) - self.assertTrue(srvc.GetRequestClass(service_descriptor.methods[1]) is - unittest_pb2.BarRequest) - self.assertTrue(srvc.GetResponseClass(service_descriptor.methods[1]) is - unittest_pb2.BarResponse) - self.assertEqual('Method Bar not implemented.', - rpc_controller.failure_message) - self.assertEqual(None, self.callback_response) - - class MyServiceImpl(unittest_pb2.TestService): - def Foo(self, rpc_controller, request, done): - self.foo_called = True - def Bar(self, rpc_controller, request, done): - self.bar_called = True - - srvc = MyServiceImpl() - rpc_controller.failure_message = None - srvc.Foo(rpc_controller, unittest_pb2.FooRequest(), MyCallback) - self.assertEqual(None, rpc_controller.failure_message) - self.assertEqual(True, srvc.foo_called) - - rpc_controller.failure_message = None - srvc.CallMethod(service_descriptor.methods[1], rpc_controller, - unittest_pb2.BarRequest(), MyCallback) - self.assertEqual(None, rpc_controller.failure_message) - self.assertEqual(True, srvc.bar_called) - - def testServiceStub(self): - class MockRpcChannel(service.RpcChannel): - def CallMethod(self, method, controller, request, - response_class, callback): - self.method = method - self.controller = controller - self.request = request - callback(response_class()) - - self.callback_response = None - - def MyCallback(response): - self.callback_response = response - - channel = MockRpcChannel() - stub = unittest_pb2.TestService_Stub(channel) - rpc_controller = 'controller' - request = 'request' - - # GetDescriptor now static, still works as instance method for compatibility - self.assertEqual(unittest_pb2.TestService_Stub.GetDescriptor(), - stub.GetDescriptor()) - - # Invoke method. - stub.Foo(rpc_controller, request, MyCallback) - - self.assertIsInstance(self.callback_response, unittest_pb2.FooResponse) - self.assertEqual(request, channel.request) - self.assertEqual(rpc_controller, channel.controller) - self.assertEqual(stub.GetDescriptor().methods[0], channel.method) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/symbol_database_test.py b/ext/protobuf/Python/google/protobuf/internal/symbol_database_test.py deleted file mode 100644 index 4fdc4ee9c..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/symbol_database_test.py +++ /dev/null @@ -1,133 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.symbol_database.""" - -import unittest - -from google.protobuf import unittest_pb2 -from google.protobuf import descriptor -from google.protobuf import descriptor_pool -from google.protobuf import symbol_database - - -class SymbolDatabaseTest(unittest.TestCase): - - def _Database(self): - if descriptor._USE_C_DESCRIPTORS: - # The C++ implementation does not allow mixing descriptors from - # different pools. - db = symbol_database.SymbolDatabase(pool=descriptor_pool.Default()) - else: - db = symbol_database.SymbolDatabase() - # Register representative types from unittest_pb2. - db.RegisterFileDescriptor(unittest_pb2.DESCRIPTOR) - db.RegisterMessage(unittest_pb2.TestAllTypes) - db.RegisterMessage(unittest_pb2.TestAllTypes.NestedMessage) - db.RegisterMessage(unittest_pb2.TestAllTypes.OptionalGroup) - db.RegisterMessage(unittest_pb2.TestAllTypes.RepeatedGroup) - db.RegisterEnumDescriptor(unittest_pb2.ForeignEnum.DESCRIPTOR) - db.RegisterEnumDescriptor(unittest_pb2.TestAllTypes.NestedEnum.DESCRIPTOR) - db.RegisterServiceDescriptor(unittest_pb2._TESTSERVICE) - return db - - def testGetPrototype(self): - instance = self._Database().GetPrototype( - unittest_pb2.TestAllTypes.DESCRIPTOR) - self.assertTrue(instance is unittest_pb2.TestAllTypes) - - def testGetMessages(self): - messages = self._Database().GetMessages( - ['google/protobuf/unittest.proto']) - self.assertTrue( - unittest_pb2.TestAllTypes is - messages['protobuf_unittest.TestAllTypes']) - - def testGetSymbol(self): - self.assertEqual( - unittest_pb2.TestAllTypes, self._Database().GetSymbol( - 'protobuf_unittest.TestAllTypes')) - self.assertEqual( - unittest_pb2.TestAllTypes.NestedMessage, self._Database().GetSymbol( - 'protobuf_unittest.TestAllTypes.NestedMessage')) - self.assertEqual( - unittest_pb2.TestAllTypes.OptionalGroup, self._Database().GetSymbol( - 'protobuf_unittest.TestAllTypes.OptionalGroup')) - self.assertEqual( - unittest_pb2.TestAllTypes.RepeatedGroup, self._Database().GetSymbol( - 'protobuf_unittest.TestAllTypes.RepeatedGroup')) - - def testEnums(self): - # Check registration of types in the pool. - self.assertEqual( - 'protobuf_unittest.ForeignEnum', - self._Database().pool.FindEnumTypeByName( - 'protobuf_unittest.ForeignEnum').full_name) - self.assertEqual( - 'protobuf_unittest.TestAllTypes.NestedEnum', - self._Database().pool.FindEnumTypeByName( - 'protobuf_unittest.TestAllTypes.NestedEnum').full_name) - - def testFindMessageTypeByName(self): - self.assertEqual( - 'protobuf_unittest.TestAllTypes', - self._Database().pool.FindMessageTypeByName( - 'protobuf_unittest.TestAllTypes').full_name) - self.assertEqual( - 'protobuf_unittest.TestAllTypes.NestedMessage', - self._Database().pool.FindMessageTypeByName( - 'protobuf_unittest.TestAllTypes.NestedMessage').full_name) - - def testFindServiceByName(self): - self.assertEqual( - 'protobuf_unittest.TestService', - self._Database().pool.FindServiceByName( - 'protobuf_unittest.TestService').full_name) - - def testFindFileContainingSymbol(self): - # Lookup based on either enum or message. - self.assertEqual( - 'google/protobuf/unittest.proto', - self._Database().pool.FindFileContainingSymbol( - 'protobuf_unittest.TestAllTypes.NestedEnum').name) - self.assertEqual( - 'google/protobuf/unittest.proto', - self._Database().pool.FindFileContainingSymbol( - 'protobuf_unittest.TestAllTypes').name) - - def testFindFileByName(self): - self.assertEqual( - 'google/protobuf/unittest.proto', - self._Database().pool.FindFileByName( - 'google/protobuf/unittest.proto').name) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/test_util.py b/ext/protobuf/Python/google/protobuf/internal/test_util.py deleted file mode 100644 index 32fb8fd27..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/test_util.py +++ /dev/null @@ -1,878 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Utilities for Python proto2 tests. - -This is intentionally modeled on C++ code in -//google/protobuf/test_util.*. -""" - -__author__ = 'robinson@google.com (Will Robinson)' - -import numbers -import operator -import os.path - -from google.protobuf import unittest_import_pb2 -from google.protobuf import unittest_pb2 - -try: - long # Python 2 -except NameError: - long = int # Python 3 - - -# Tests whether the given TestAllTypes message is proto2 or not. -# This is used to gate several fields/features that only exist -# for the proto2 version of the message. -def IsProto2(message): - return message.DESCRIPTOR.syntax == "proto2" - - -def SetAllNonLazyFields(message): - """Sets every non-lazy field in the message to a unique value. - - Args: - message: A TestAllTypes instance. - """ - - # - # Optional fields. - # - - message.optional_int32 = 101 - message.optional_int64 = 102 - message.optional_uint32 = 103 - message.optional_uint64 = 104 - message.optional_sint32 = 105 - message.optional_sint64 = 106 - message.optional_fixed32 = 107 - message.optional_fixed64 = 108 - message.optional_sfixed32 = 109 - message.optional_sfixed64 = 110 - message.optional_float = 111 - message.optional_double = 112 - message.optional_bool = True - message.optional_string = u'115' - message.optional_bytes = b'116' - - if IsProto2(message): - message.optionalgroup.a = 117 - message.optional_nested_message.bb = 118 - message.optional_foreign_message.c = 119 - message.optional_import_message.d = 120 - message.optional_public_import_message.e = 126 - - message.optional_nested_enum = unittest_pb2.TestAllTypes.BAZ - message.optional_foreign_enum = unittest_pb2.FOREIGN_BAZ - if IsProto2(message): - message.optional_import_enum = unittest_import_pb2.IMPORT_BAZ - - message.optional_string_piece = u'124' - message.optional_cord = u'125' - - # - # Repeated fields. - # - - message.repeated_int32.append(201) - message.repeated_int64.append(202) - message.repeated_uint32.append(203) - message.repeated_uint64.append(204) - message.repeated_sint32.append(205) - message.repeated_sint64.append(206) - message.repeated_fixed32.append(207) - message.repeated_fixed64.append(208) - message.repeated_sfixed32.append(209) - message.repeated_sfixed64.append(210) - message.repeated_float.append(211) - message.repeated_double.append(212) - message.repeated_bool.append(True) - message.repeated_string.append(u'215') - message.repeated_bytes.append(b'216') - - if IsProto2(message): - message.repeatedgroup.add().a = 217 - message.repeated_nested_message.add().bb = 218 - message.repeated_foreign_message.add().c = 219 - message.repeated_import_message.add().d = 220 - message.repeated_lazy_message.add().bb = 227 - - message.repeated_nested_enum.append(unittest_pb2.TestAllTypes.BAR) - message.repeated_foreign_enum.append(unittest_pb2.FOREIGN_BAR) - if IsProto2(message): - message.repeated_import_enum.append(unittest_import_pb2.IMPORT_BAR) - - message.repeated_string_piece.append(u'224') - message.repeated_cord.append(u'225') - - # Add a second one of each field and set value by index. - message.repeated_int32.append(0) - message.repeated_int64.append(0) - message.repeated_uint32.append(0) - message.repeated_uint64.append(0) - message.repeated_sint32.append(0) - message.repeated_sint64.append(0) - message.repeated_fixed32.append(0) - message.repeated_fixed64.append(0) - message.repeated_sfixed32.append(0) - message.repeated_sfixed64.append(0) - message.repeated_float.append(0) - message.repeated_double.append(0) - message.repeated_bool.append(True) - message.repeated_string.append(u'0') - message.repeated_bytes.append(b'0') - message.repeated_int32[1] = 301 - message.repeated_int64[1] = 302 - message.repeated_uint32[1] = 303 - message.repeated_uint64[1] = 304 - message.repeated_sint32[1] = 305 - message.repeated_sint64[1] = 306 - message.repeated_fixed32[1] = 307 - message.repeated_fixed64[1] = 308 - message.repeated_sfixed32[1] = 309 - message.repeated_sfixed64[1] = 310 - message.repeated_float[1] = 311 - message.repeated_double[1] = 312 - message.repeated_bool[1] = False - message.repeated_string[1] = u'315' - message.repeated_bytes[1] = b'316' - - if IsProto2(message): - message.repeatedgroup.add().a = 317 - message.repeated_nested_message.add().bb = 318 - message.repeated_foreign_message.add().c = 319 - message.repeated_import_message.add().d = 320 - message.repeated_lazy_message.add().bb = 327 - - message.repeated_nested_enum.append(unittest_pb2.TestAllTypes.BAR) - message.repeated_nested_enum[1] = unittest_pb2.TestAllTypes.BAZ - message.repeated_foreign_enum.append(unittest_pb2.FOREIGN_BAZ) - if IsProto2(message): - message.repeated_import_enum.append(unittest_import_pb2.IMPORT_BAZ) - - message.repeated_string_piece.append(u'324') - message.repeated_cord.append(u'325') - - # - # Fields that have defaults. - # - - if IsProto2(message): - message.default_int32 = 401 - message.default_int64 = 402 - message.default_uint32 = 403 - message.default_uint64 = 404 - message.default_sint32 = 405 - message.default_sint64 = 406 - message.default_fixed32 = 407 - message.default_fixed64 = 408 - message.default_sfixed32 = 409 - message.default_sfixed64 = 410 - message.default_float = 411 - message.default_double = 412 - message.default_bool = False - message.default_string = '415' - message.default_bytes = b'416' - - message.default_nested_enum = unittest_pb2.TestAllTypes.FOO - message.default_foreign_enum = unittest_pb2.FOREIGN_FOO - message.default_import_enum = unittest_import_pb2.IMPORT_FOO - - message.default_string_piece = '424' - message.default_cord = '425' - - message.oneof_uint32 = 601 - message.oneof_nested_message.bb = 602 - message.oneof_string = '603' - message.oneof_bytes = b'604' - - -def SetAllFields(message): - SetAllNonLazyFields(message) - message.optional_lazy_message.bb = 127 - message.optional_unverified_lazy_message.bb = 128 - - -def SetAllExtensions(message): - """Sets every extension in the message to a unique value. - - Args: - message: A unittest_pb2.TestAllExtensions instance. - """ - - extensions = message.Extensions - pb2 = unittest_pb2 - import_pb2 = unittest_import_pb2 - - # - # Optional fields. - # - - extensions[pb2.optional_int32_extension] = 101 - extensions[pb2.optional_int64_extension] = 102 - extensions[pb2.optional_uint32_extension] = 103 - extensions[pb2.optional_uint64_extension] = 104 - extensions[pb2.optional_sint32_extension] = 105 - extensions[pb2.optional_sint64_extension] = 106 - extensions[pb2.optional_fixed32_extension] = 107 - extensions[pb2.optional_fixed64_extension] = 108 - extensions[pb2.optional_sfixed32_extension] = 109 - extensions[pb2.optional_sfixed64_extension] = 110 - extensions[pb2.optional_float_extension] = 111 - extensions[pb2.optional_double_extension] = 112 - extensions[pb2.optional_bool_extension] = True - extensions[pb2.optional_string_extension] = u'115' - extensions[pb2.optional_bytes_extension] = b'116' - - extensions[pb2.optionalgroup_extension].a = 117 - extensions[pb2.optional_nested_message_extension].bb = 118 - extensions[pb2.optional_foreign_message_extension].c = 119 - extensions[pb2.optional_import_message_extension].d = 120 - extensions[pb2.optional_public_import_message_extension].e = 126 - extensions[pb2.optional_lazy_message_extension].bb = 127 - extensions[pb2.optional_unverified_lazy_message_extension].bb = 128 - - extensions[pb2.optional_nested_enum_extension] = pb2.TestAllTypes.BAZ - extensions[pb2.optional_nested_enum_extension] = pb2.TestAllTypes.BAZ - extensions[pb2.optional_foreign_enum_extension] = pb2.FOREIGN_BAZ - extensions[pb2.optional_import_enum_extension] = import_pb2.IMPORT_BAZ - - extensions[pb2.optional_string_piece_extension] = u'124' - extensions[pb2.optional_cord_extension] = u'125' - - # - # Repeated fields. - # - - extensions[pb2.repeated_int32_extension].append(201) - extensions[pb2.repeated_int64_extension].append(202) - extensions[pb2.repeated_uint32_extension].append(203) - extensions[pb2.repeated_uint64_extension].append(204) - extensions[pb2.repeated_sint32_extension].append(205) - extensions[pb2.repeated_sint64_extension].append(206) - extensions[pb2.repeated_fixed32_extension].append(207) - extensions[pb2.repeated_fixed64_extension].append(208) - extensions[pb2.repeated_sfixed32_extension].append(209) - extensions[pb2.repeated_sfixed64_extension].append(210) - extensions[pb2.repeated_float_extension].append(211) - extensions[pb2.repeated_double_extension].append(212) - extensions[pb2.repeated_bool_extension].append(True) - extensions[pb2.repeated_string_extension].append(u'215') - extensions[pb2.repeated_bytes_extension].append(b'216') - - extensions[pb2.repeatedgroup_extension].add().a = 217 - extensions[pb2.repeated_nested_message_extension].add().bb = 218 - extensions[pb2.repeated_foreign_message_extension].add().c = 219 - extensions[pb2.repeated_import_message_extension].add().d = 220 - extensions[pb2.repeated_lazy_message_extension].add().bb = 227 - - extensions[pb2.repeated_nested_enum_extension].append(pb2.TestAllTypes.BAR) - extensions[pb2.repeated_foreign_enum_extension].append(pb2.FOREIGN_BAR) - extensions[pb2.repeated_import_enum_extension].append(import_pb2.IMPORT_BAR) - - extensions[pb2.repeated_string_piece_extension].append(u'224') - extensions[pb2.repeated_cord_extension].append(u'225') - - # Append a second one of each field. - extensions[pb2.repeated_int32_extension].append(301) - extensions[pb2.repeated_int64_extension].append(302) - extensions[pb2.repeated_uint32_extension].append(303) - extensions[pb2.repeated_uint64_extension].append(304) - extensions[pb2.repeated_sint32_extension].append(305) - extensions[pb2.repeated_sint64_extension].append(306) - extensions[pb2.repeated_fixed32_extension].append(307) - extensions[pb2.repeated_fixed64_extension].append(308) - extensions[pb2.repeated_sfixed32_extension].append(309) - extensions[pb2.repeated_sfixed64_extension].append(310) - extensions[pb2.repeated_float_extension].append(311) - extensions[pb2.repeated_double_extension].append(312) - extensions[pb2.repeated_bool_extension].append(False) - extensions[pb2.repeated_string_extension].append(u'315') - extensions[pb2.repeated_bytes_extension].append(b'316') - - extensions[pb2.repeatedgroup_extension].add().a = 317 - extensions[pb2.repeated_nested_message_extension].add().bb = 318 - extensions[pb2.repeated_foreign_message_extension].add().c = 319 - extensions[pb2.repeated_import_message_extension].add().d = 320 - extensions[pb2.repeated_lazy_message_extension].add().bb = 327 - - extensions[pb2.repeated_nested_enum_extension].append(pb2.TestAllTypes.BAZ) - extensions[pb2.repeated_foreign_enum_extension].append(pb2.FOREIGN_BAZ) - extensions[pb2.repeated_import_enum_extension].append(import_pb2.IMPORT_BAZ) - - extensions[pb2.repeated_string_piece_extension].append(u'324') - extensions[pb2.repeated_cord_extension].append(u'325') - - # - # Fields with defaults. - # - - extensions[pb2.default_int32_extension] = 401 - extensions[pb2.default_int64_extension] = 402 - extensions[pb2.default_uint32_extension] = 403 - extensions[pb2.default_uint64_extension] = 404 - extensions[pb2.default_sint32_extension] = 405 - extensions[pb2.default_sint64_extension] = 406 - extensions[pb2.default_fixed32_extension] = 407 - extensions[pb2.default_fixed64_extension] = 408 - extensions[pb2.default_sfixed32_extension] = 409 - extensions[pb2.default_sfixed64_extension] = 410 - extensions[pb2.default_float_extension] = 411 - extensions[pb2.default_double_extension] = 412 - extensions[pb2.default_bool_extension] = False - extensions[pb2.default_string_extension] = u'415' - extensions[pb2.default_bytes_extension] = b'416' - - extensions[pb2.default_nested_enum_extension] = pb2.TestAllTypes.FOO - extensions[pb2.default_foreign_enum_extension] = pb2.FOREIGN_FOO - extensions[pb2.default_import_enum_extension] = import_pb2.IMPORT_FOO - - extensions[pb2.default_string_piece_extension] = u'424' - extensions[pb2.default_cord_extension] = '425' - - extensions[pb2.oneof_uint32_extension] = 601 - extensions[pb2.oneof_nested_message_extension].bb = 602 - extensions[pb2.oneof_string_extension] = u'603' - extensions[pb2.oneof_bytes_extension] = b'604' - - -def SetAllFieldsAndExtensions(message): - """Sets every field and extension in the message to a unique value. - - Args: - message: A unittest_pb2.TestAllExtensions message. - """ - message.my_int = 1 - message.my_string = 'foo' - message.my_float = 1.0 - message.Extensions[unittest_pb2.my_extension_int] = 23 - message.Extensions[unittest_pb2.my_extension_string] = 'bar' - - -def ExpectAllFieldsAndExtensionsInOrder(serialized): - """Ensures that serialized is the serialization we expect for a message - filled with SetAllFieldsAndExtensions(). (Specifically, ensures that the - serialization is in canonical, tag-number order). - """ - my_extension_int = unittest_pb2.my_extension_int - my_extension_string = unittest_pb2.my_extension_string - expected_strings = [] - message = unittest_pb2.TestFieldOrderings() - message.my_int = 1 # Field 1. - expected_strings.append(message.SerializeToString()) - message.Clear() - message.Extensions[my_extension_int] = 23 # Field 5. - expected_strings.append(message.SerializeToString()) - message.Clear() - message.my_string = 'foo' # Field 11. - expected_strings.append(message.SerializeToString()) - message.Clear() - message.Extensions[my_extension_string] = 'bar' # Field 50. - expected_strings.append(message.SerializeToString()) - message.Clear() - message.my_float = 1.0 - expected_strings.append(message.SerializeToString()) - message.Clear() - expected = b''.join(expected_strings) - - if expected != serialized: - raise ValueError('Expected %r, found %r' % (expected, serialized)) - - -def ExpectAllFieldsSet(test_case, message): - """Check all fields for correct values have after Set*Fields() is called.""" - test_case.assertTrue(message.HasField('optional_int32')) - test_case.assertTrue(message.HasField('optional_int64')) - test_case.assertTrue(message.HasField('optional_uint32')) - test_case.assertTrue(message.HasField('optional_uint64')) - test_case.assertTrue(message.HasField('optional_sint32')) - test_case.assertTrue(message.HasField('optional_sint64')) - test_case.assertTrue(message.HasField('optional_fixed32')) - test_case.assertTrue(message.HasField('optional_fixed64')) - test_case.assertTrue(message.HasField('optional_sfixed32')) - test_case.assertTrue(message.HasField('optional_sfixed64')) - test_case.assertTrue(message.HasField('optional_float')) - test_case.assertTrue(message.HasField('optional_double')) - test_case.assertTrue(message.HasField('optional_bool')) - test_case.assertTrue(message.HasField('optional_string')) - test_case.assertTrue(message.HasField('optional_bytes')) - - if IsProto2(message): - test_case.assertTrue(message.HasField('optionalgroup')) - test_case.assertTrue(message.HasField('optional_nested_message')) - test_case.assertTrue(message.HasField('optional_foreign_message')) - test_case.assertTrue(message.HasField('optional_import_message')) - - test_case.assertTrue(message.optionalgroup.HasField('a')) - test_case.assertTrue(message.optional_nested_message.HasField('bb')) - test_case.assertTrue(message.optional_foreign_message.HasField('c')) - test_case.assertTrue(message.optional_import_message.HasField('d')) - - test_case.assertTrue(message.HasField('optional_nested_enum')) - test_case.assertTrue(message.HasField('optional_foreign_enum')) - if IsProto2(message): - test_case.assertTrue(message.HasField('optional_import_enum')) - - test_case.assertTrue(message.HasField('optional_string_piece')) - test_case.assertTrue(message.HasField('optional_cord')) - - test_case.assertEqual(101, message.optional_int32) - test_case.assertEqual(102, message.optional_int64) - test_case.assertEqual(103, message.optional_uint32) - test_case.assertEqual(104, message.optional_uint64) - test_case.assertEqual(105, message.optional_sint32) - test_case.assertEqual(106, message.optional_sint64) - test_case.assertEqual(107, message.optional_fixed32) - test_case.assertEqual(108, message.optional_fixed64) - test_case.assertEqual(109, message.optional_sfixed32) - test_case.assertEqual(110, message.optional_sfixed64) - test_case.assertEqual(111, message.optional_float) - test_case.assertEqual(112, message.optional_double) - test_case.assertEqual(True, message.optional_bool) - test_case.assertEqual('115', message.optional_string) - test_case.assertEqual(b'116', message.optional_bytes) - - if IsProto2(message): - test_case.assertEqual(117, message.optionalgroup.a) - test_case.assertEqual(118, message.optional_nested_message.bb) - test_case.assertEqual(119, message.optional_foreign_message.c) - test_case.assertEqual(120, message.optional_import_message.d) - test_case.assertEqual(126, message.optional_public_import_message.e) - test_case.assertEqual(127, message.optional_lazy_message.bb) - test_case.assertEqual(128, message.optional_unverified_lazy_message.bb) - - test_case.assertEqual(unittest_pb2.TestAllTypes.BAZ, - message.optional_nested_enum) - test_case.assertEqual(unittest_pb2.FOREIGN_BAZ, - message.optional_foreign_enum) - if IsProto2(message): - test_case.assertEqual(unittest_import_pb2.IMPORT_BAZ, - message.optional_import_enum) - - # ----------------------------------------------------------------- - - test_case.assertEqual(2, len(message.repeated_int32)) - test_case.assertEqual(2, len(message.repeated_int64)) - test_case.assertEqual(2, len(message.repeated_uint32)) - test_case.assertEqual(2, len(message.repeated_uint64)) - test_case.assertEqual(2, len(message.repeated_sint32)) - test_case.assertEqual(2, len(message.repeated_sint64)) - test_case.assertEqual(2, len(message.repeated_fixed32)) - test_case.assertEqual(2, len(message.repeated_fixed64)) - test_case.assertEqual(2, len(message.repeated_sfixed32)) - test_case.assertEqual(2, len(message.repeated_sfixed64)) - test_case.assertEqual(2, len(message.repeated_float)) - test_case.assertEqual(2, len(message.repeated_double)) - test_case.assertEqual(2, len(message.repeated_bool)) - test_case.assertEqual(2, len(message.repeated_string)) - test_case.assertEqual(2, len(message.repeated_bytes)) - - if IsProto2(message): - test_case.assertEqual(2, len(message.repeatedgroup)) - test_case.assertEqual(2, len(message.repeated_nested_message)) - test_case.assertEqual(2, len(message.repeated_foreign_message)) - test_case.assertEqual(2, len(message.repeated_import_message)) - test_case.assertEqual(2, len(message.repeated_nested_enum)) - test_case.assertEqual(2, len(message.repeated_foreign_enum)) - if IsProto2(message): - test_case.assertEqual(2, len(message.repeated_import_enum)) - - test_case.assertEqual(2, len(message.repeated_string_piece)) - test_case.assertEqual(2, len(message.repeated_cord)) - - test_case.assertEqual(201, message.repeated_int32[0]) - test_case.assertEqual(202, message.repeated_int64[0]) - test_case.assertEqual(203, message.repeated_uint32[0]) - test_case.assertEqual(204, message.repeated_uint64[0]) - test_case.assertEqual(205, message.repeated_sint32[0]) - test_case.assertEqual(206, message.repeated_sint64[0]) - test_case.assertEqual(207, message.repeated_fixed32[0]) - test_case.assertEqual(208, message.repeated_fixed64[0]) - test_case.assertEqual(209, message.repeated_sfixed32[0]) - test_case.assertEqual(210, message.repeated_sfixed64[0]) - test_case.assertEqual(211, message.repeated_float[0]) - test_case.assertEqual(212, message.repeated_double[0]) - test_case.assertEqual(True, message.repeated_bool[0]) - test_case.assertEqual('215', message.repeated_string[0]) - test_case.assertEqual(b'216', message.repeated_bytes[0]) - - if IsProto2(message): - test_case.assertEqual(217, message.repeatedgroup[0].a) - test_case.assertEqual(218, message.repeated_nested_message[0].bb) - test_case.assertEqual(219, message.repeated_foreign_message[0].c) - test_case.assertEqual(220, message.repeated_import_message[0].d) - test_case.assertEqual(227, message.repeated_lazy_message[0].bb) - - test_case.assertEqual(unittest_pb2.TestAllTypes.BAR, - message.repeated_nested_enum[0]) - test_case.assertEqual(unittest_pb2.FOREIGN_BAR, - message.repeated_foreign_enum[0]) - if IsProto2(message): - test_case.assertEqual(unittest_import_pb2.IMPORT_BAR, - message.repeated_import_enum[0]) - - test_case.assertEqual(301, message.repeated_int32[1]) - test_case.assertEqual(302, message.repeated_int64[1]) - test_case.assertEqual(303, message.repeated_uint32[1]) - test_case.assertEqual(304, message.repeated_uint64[1]) - test_case.assertEqual(305, message.repeated_sint32[1]) - test_case.assertEqual(306, message.repeated_sint64[1]) - test_case.assertEqual(307, message.repeated_fixed32[1]) - test_case.assertEqual(308, message.repeated_fixed64[1]) - test_case.assertEqual(309, message.repeated_sfixed32[1]) - test_case.assertEqual(310, message.repeated_sfixed64[1]) - test_case.assertEqual(311, message.repeated_float[1]) - test_case.assertEqual(312, message.repeated_double[1]) - test_case.assertEqual(False, message.repeated_bool[1]) - test_case.assertEqual('315', message.repeated_string[1]) - test_case.assertEqual(b'316', message.repeated_bytes[1]) - - if IsProto2(message): - test_case.assertEqual(317, message.repeatedgroup[1].a) - test_case.assertEqual(318, message.repeated_nested_message[1].bb) - test_case.assertEqual(319, message.repeated_foreign_message[1].c) - test_case.assertEqual(320, message.repeated_import_message[1].d) - test_case.assertEqual(327, message.repeated_lazy_message[1].bb) - - test_case.assertEqual(unittest_pb2.TestAllTypes.BAZ, - message.repeated_nested_enum[1]) - test_case.assertEqual(unittest_pb2.FOREIGN_BAZ, - message.repeated_foreign_enum[1]) - if IsProto2(message): - test_case.assertEqual(unittest_import_pb2.IMPORT_BAZ, - message.repeated_import_enum[1]) - - # ----------------------------------------------------------------- - - if IsProto2(message): - test_case.assertTrue(message.HasField('default_int32')) - test_case.assertTrue(message.HasField('default_int64')) - test_case.assertTrue(message.HasField('default_uint32')) - test_case.assertTrue(message.HasField('default_uint64')) - test_case.assertTrue(message.HasField('default_sint32')) - test_case.assertTrue(message.HasField('default_sint64')) - test_case.assertTrue(message.HasField('default_fixed32')) - test_case.assertTrue(message.HasField('default_fixed64')) - test_case.assertTrue(message.HasField('default_sfixed32')) - test_case.assertTrue(message.HasField('default_sfixed64')) - test_case.assertTrue(message.HasField('default_float')) - test_case.assertTrue(message.HasField('default_double')) - test_case.assertTrue(message.HasField('default_bool')) - test_case.assertTrue(message.HasField('default_string')) - test_case.assertTrue(message.HasField('default_bytes')) - - test_case.assertTrue(message.HasField('default_nested_enum')) - test_case.assertTrue(message.HasField('default_foreign_enum')) - test_case.assertTrue(message.HasField('default_import_enum')) - - test_case.assertEqual(401, message.default_int32) - test_case.assertEqual(402, message.default_int64) - test_case.assertEqual(403, message.default_uint32) - test_case.assertEqual(404, message.default_uint64) - test_case.assertEqual(405, message.default_sint32) - test_case.assertEqual(406, message.default_sint64) - test_case.assertEqual(407, message.default_fixed32) - test_case.assertEqual(408, message.default_fixed64) - test_case.assertEqual(409, message.default_sfixed32) - test_case.assertEqual(410, message.default_sfixed64) - test_case.assertEqual(411, message.default_float) - test_case.assertEqual(412, message.default_double) - test_case.assertEqual(False, message.default_bool) - test_case.assertEqual('415', message.default_string) - test_case.assertEqual(b'416', message.default_bytes) - - test_case.assertEqual(unittest_pb2.TestAllTypes.FOO, - message.default_nested_enum) - test_case.assertEqual(unittest_pb2.FOREIGN_FOO, - message.default_foreign_enum) - test_case.assertEqual(unittest_import_pb2.IMPORT_FOO, - message.default_import_enum) - - -def GoldenFile(filename): - """Finds the given golden file and returns a file object representing it.""" - - # Search up the directory tree looking for the C++ protobuf source code. - path = '.' - while os.path.exists(path): - if os.path.exists(os.path.join(path, 'src/google/protobuf')): - # Found it. Load the golden file from the testdata directory. - full_path = os.path.join(path, 'src/google/protobuf/testdata', filename) - return open(full_path, 'rb') - path = os.path.join(path, '..') - - # Search internally. - path = '.' - full_path = os.path.join(path, 'third_party/py/google/protobuf/testdata', - filename) - if os.path.exists(full_path): - # Found it. Load the golden file from the testdata directory. - return open(full_path, 'rb') - - # Search for cross-repo path. - full_path = os.path.join('external/com_google_protobuf/src/google/protobuf/testdata', - filename) - if os.path.exists(full_path): - # Found it. Load the golden file from the testdata directory. - return open(full_path, 'rb') - - raise RuntimeError( - 'Could not find golden files. This test must be run from within the ' - 'protobuf source package so that it can read test data files from the ' - 'C++ source tree.') - - -def GoldenFileData(filename): - """Finds the given golden file and returns its contents.""" - with GoldenFile(filename) as f: - return f.read() - - -def SetAllPackedFields(message): - """Sets every field in the message to a unique value. - - Args: - message: A TestPackedTypes instance. - """ - message.packed_int32.extend([601, 701]) - message.packed_int64.extend([602, 702]) - message.packed_uint32.extend([603, 703]) - message.packed_uint64.extend([604, 704]) - message.packed_sint32.extend([605, 705]) - message.packed_sint64.extend([606, 706]) - message.packed_fixed32.extend([607, 707]) - message.packed_fixed64.extend([608, 708]) - message.packed_sfixed32.extend([609, 709]) - message.packed_sfixed64.extend([610, 710]) - message.packed_float.extend([611.0, 711.0]) - message.packed_double.extend([612.0, 712.0]) - message.packed_bool.extend([True, False]) - message.packed_enum.extend([unittest_pb2.FOREIGN_BAR, - unittest_pb2.FOREIGN_BAZ]) - - -def SetAllPackedExtensions(message): - """Sets every extension in the message to a unique value. - - Args: - message: A unittest_pb2.TestPackedExtensions instance. - """ - extensions = message.Extensions - pb2 = unittest_pb2 - - extensions[pb2.packed_int32_extension].extend([601, 701]) - extensions[pb2.packed_int64_extension].extend([602, 702]) - extensions[pb2.packed_uint32_extension].extend([603, 703]) - extensions[pb2.packed_uint64_extension].extend([604, 704]) - extensions[pb2.packed_sint32_extension].extend([605, 705]) - extensions[pb2.packed_sint64_extension].extend([606, 706]) - extensions[pb2.packed_fixed32_extension].extend([607, 707]) - extensions[pb2.packed_fixed64_extension].extend([608, 708]) - extensions[pb2.packed_sfixed32_extension].extend([609, 709]) - extensions[pb2.packed_sfixed64_extension].extend([610, 710]) - extensions[pb2.packed_float_extension].extend([611.0, 711.0]) - extensions[pb2.packed_double_extension].extend([612.0, 712.0]) - extensions[pb2.packed_bool_extension].extend([True, False]) - extensions[pb2.packed_enum_extension].extend([unittest_pb2.FOREIGN_BAR, - unittest_pb2.FOREIGN_BAZ]) - - -def SetAllUnpackedFields(message): - """Sets every field in the message to a unique value. - - Args: - message: A unittest_pb2.TestUnpackedTypes instance. - """ - message.unpacked_int32.extend([601, 701]) - message.unpacked_int64.extend([602, 702]) - message.unpacked_uint32.extend([603, 703]) - message.unpacked_uint64.extend([604, 704]) - message.unpacked_sint32.extend([605, 705]) - message.unpacked_sint64.extend([606, 706]) - message.unpacked_fixed32.extend([607, 707]) - message.unpacked_fixed64.extend([608, 708]) - message.unpacked_sfixed32.extend([609, 709]) - message.unpacked_sfixed64.extend([610, 710]) - message.unpacked_float.extend([611.0, 711.0]) - message.unpacked_double.extend([612.0, 712.0]) - message.unpacked_bool.extend([True, False]) - message.unpacked_enum.extend([unittest_pb2.FOREIGN_BAR, - unittest_pb2.FOREIGN_BAZ]) - - -class NonStandardInteger(numbers.Integral): - """An integer object that does not subclass int. - - This is used to verify that both C++ and regular proto systems can handle - integer others than int and long and that they handle them in predictable - ways. - - NonStandardInteger is the minimal legal specification for a custom Integral. - As such, it does not support 0 < x < 5 and it is not hashable. - - Note: This is added here instead of relying on numpy or a similar library - with custom integers to limit dependencies. - """ - - def __init__(self, val, error_string_on_conversion=None): - assert isinstance(val, numbers.Integral) - if isinstance(val, NonStandardInteger): - val = val.val - self.val = val - self.error_string_on_conversion = error_string_on_conversion - - def __long__(self): - if self.error_string_on_conversion: - raise RuntimeError(self.error_string_on_conversion) - return long(self.val) - - def __abs__(self): - return NonStandardInteger(operator.abs(self.val)) - - def __add__(self, y): - return NonStandardInteger(operator.add(self.val, y)) - - def __div__(self, y): - return NonStandardInteger(operator.div(self.val, y)) - - def __eq__(self, y): - return operator.eq(self.val, y) - - def __floordiv__(self, y): - return NonStandardInteger(operator.floordiv(self.val, y)) - - def __truediv__(self, y): - return NonStandardInteger(operator.truediv(self.val, y)) - - def __invert__(self): - return NonStandardInteger(operator.invert(self.val)) - - def __mod__(self, y): - return NonStandardInteger(operator.mod(self.val, y)) - - def __mul__(self, y): - return NonStandardInteger(operator.mul(self.val, y)) - - def __neg__(self): - return NonStandardInteger(operator.neg(self.val)) - - def __pos__(self): - return NonStandardInteger(operator.pos(self.val)) - - def __pow__(self, y): - return NonStandardInteger(operator.pow(self.val, y)) - - def __trunc__(self): - return int(self.val) - - def __radd__(self, y): - return NonStandardInteger(operator.add(y, self.val)) - - def __rdiv__(self, y): - return NonStandardInteger(operator.div(y, self.val)) - - def __rmod__(self, y): - return NonStandardInteger(operator.mod(y, self.val)) - - def __rmul__(self, y): - return NonStandardInteger(operator.mul(y, self.val)) - - def __rpow__(self, y): - return NonStandardInteger(operator.pow(y, self.val)) - - def __rfloordiv__(self, y): - return NonStandardInteger(operator.floordiv(y, self.val)) - - def __rtruediv__(self, y): - return NonStandardInteger(operator.truediv(y, self.val)) - - def __lshift__(self, y): - return NonStandardInteger(operator.lshift(self.val, y)) - - def __rshift__(self, y): - return NonStandardInteger(operator.rshift(self.val, y)) - - def __rlshift__(self, y): - return NonStandardInteger(operator.lshift(y, self.val)) - - def __rrshift__(self, y): - return NonStandardInteger(operator.rshift(y, self.val)) - - def __le__(self, y): - if isinstance(y, NonStandardInteger): - y = y.val - return operator.le(self.val, y) - - def __lt__(self, y): - if isinstance(y, NonStandardInteger): - y = y.val - return operator.lt(self.val, y) - - def __and__(self, y): - return NonStandardInteger(operator.and_(self.val, y)) - - def __or__(self, y): - return NonStandardInteger(operator.or_(self.val, y)) - - def __xor__(self, y): - return NonStandardInteger(operator.xor(self.val, y)) - - def __rand__(self, y): - return NonStandardInteger(operator.and_(y, self.val)) - - def __ror__(self, y): - return NonStandardInteger(operator.or_(y, self.val)) - - def __rxor__(self, y): - return NonStandardInteger(operator.xor(y, self.val)) - - def __bool__(self): - return self.val - - def __nonzero__(self): - return self.val - - def __ceil__(self): - return self - - def __floor__(self): - return self - - def __int__(self): - if self.error_string_on_conversion: - raise RuntimeError(self.error_string_on_conversion) - return int(self.val) - - def __round__(self): - return self - - def __repr__(self): - return 'NonStandardInteger(%s)' % self.val diff --git a/ext/protobuf/Python/google/protobuf/internal/text_encoding_test.py b/ext/protobuf/Python/google/protobuf/internal/text_encoding_test.py deleted file mode 100644 index f36a2cc5b..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/text_encoding_test.py +++ /dev/null @@ -1,67 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Tests for google.protobuf.text_encoding.""" - -import unittest - -from google.protobuf import text_encoding - -TEST_VALUES = [ - ("foo\\rbar\\nbaz\\t", - "foo\\rbar\\nbaz\\t", - b"foo\rbar\nbaz\t"), - ("\\'full of \\\"sound\\\" and \\\"fury\\\"\\'", - "\\'full of \\\"sound\\\" and \\\"fury\\\"\\'", - b"'full of \"sound\" and \"fury\"'"), - ("signi\\\\fying\\\\ nothing\\\\", - "signi\\\\fying\\\\ nothing\\\\", - b"signi\\fying\\ nothing\\"), - ("\\010\\t\\n\\013\\014\\r", - "\x08\\t\\n\x0b\x0c\\r", - b"\010\011\012\013\014\015")] - - -class TextEncodingTestCase(unittest.TestCase): - def testCEscape(self): - for escaped, escaped_utf8, unescaped in TEST_VALUES: - self.assertEqual(escaped, - text_encoding.CEscape(unescaped, as_utf8=False)) - self.assertEqual(escaped_utf8, - text_encoding.CEscape(unescaped, as_utf8=True)) - - def testCUnescape(self): - for escaped, escaped_utf8, unescaped in TEST_VALUES: - self.assertEqual(unescaped, text_encoding.CUnescape(escaped)) - self.assertEqual(unescaped, text_encoding.CUnescape(escaped_utf8)) - - -if __name__ == "__main__": - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/text_format_test.py b/ext/protobuf/Python/google/protobuf/internal/text_format_test.py deleted file mode 100644 index 18b784e5b..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/text_format_test.py +++ /dev/null @@ -1,2447 +0,0 @@ -# -*- coding: utf-8 -*- -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Test for google.protobuf.text_format.""" - -import io -import math -import re -import string -import textwrap - -import unittest - -from google.protobuf import any_pb2 -from google.protobuf import struct_pb2 -from google.protobuf import any_test_pb2 -from google.protobuf import map_unittest_pb2 -from google.protobuf import unittest_custom_options_pb2 -from google.protobuf import unittest_mset_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import unittest_proto3_arena_pb2 -from google.protobuf import descriptor_pb2 -from google.protobuf.internal import any_test_pb2 as test_extend_any -from google.protobuf.internal import api_implementation -from google.protobuf.internal import message_set_extensions_pb2 -from google.protobuf.internal import test_proto3_optional_pb2 -from google.protobuf.internal import test_util -from google.protobuf import descriptor_pool -from google.protobuf import text_format -from google.protobuf.internal import _parameterized -# pylint: enable=g-import-not-at-top - - -# Low-level nuts-n-bolts tests. -class SimpleTextFormatTests(unittest.TestCase): - - # The members of _QUOTES are formatted into a regexp template that - # expects single characters. Therefore it's an error (in addition to being - # non-sensical in the first place) to try to specify a "quote mark" that is - # more than one character. - def testQuoteMarksAreSingleChars(self): - for quote in text_format._QUOTES: - self.assertEqual(1, len(quote)) - - -# Base class with some common functionality. -class TextFormatBase(unittest.TestCase): - - def ReadGolden(self, golden_filename): - with test_util.GoldenFile(golden_filename) as f: - return (f.readlines() if str is bytes else # PY3 - [golden_line.decode('utf-8') for golden_line in f]) - - def CompareToGoldenFile(self, text, golden_filename): - golden_lines = self.ReadGolden(golden_filename) - self.assertMultiLineEqual(text, ''.join(golden_lines)) - - def CompareToGoldenText(self, text, golden_text): - self.assertEqual(text, golden_text) - - def RemoveRedundantZeros(self, text): - # Some platforms print 1e+5 as 1e+005. This is fine, but we need to remove - # these zeros in order to match the golden file. - text = text.replace('e+0','e+').replace('e+0','e+') \ - .replace('e-0','e-').replace('e-0','e-') - # Floating point fields are printed with .0 suffix even if they are - # actually integer numbers. - text = re.compile(r'\.0$', re.MULTILINE).sub('', text) - return text - - -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) -class TextFormatMessageToStringTests(TextFormatBase): - - def testPrintExotic(self, message_module): - message = message_module.TestAllTypes() - message.repeated_int64.append(-9223372036854775808) - message.repeated_uint64.append(18446744073709551615) - message.repeated_double.append(123.456) - message.repeated_double.append(1.23e22) - message.repeated_double.append(1.23e-18) - message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"') - message.repeated_string.append(u'\u00fc\ua71f') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_format.MessageToString(message)), - 'repeated_int64: -9223372036854775808\n' - 'repeated_uint64: 18446744073709551615\n' - 'repeated_double: 123.456\n' - 'repeated_double: 1.23e+22\n' - 'repeated_double: 1.23e-18\n' - 'repeated_string:' - ' "\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""\n' - 'repeated_string: "\\303\\274\\352\\234\\237"\n') - - def testPrintFloatPrecision(self, message_module): - message = message_module.TestAllTypes() - - message.repeated_float.append(0.0) - message.repeated_float.append(0.8) - message.repeated_float.append(1.0) - message.repeated_float.append(1.2) - message.repeated_float.append(1.23) - message.repeated_float.append(1.234) - message.repeated_float.append(1.2345) - message.repeated_float.append(1.23456) - message.repeated_float.append(1.2e10) - message.repeated_float.append(1.23e10) - message.repeated_float.append(1.234e10) - message.repeated_float.append(1.2345e10) - message.repeated_float.append(1.23456e10) - message.repeated_float.append(float('NaN')) - message.repeated_float.append(float('inf')) - message.repeated_double.append(0.0) - message.repeated_double.append(0.8) - message.repeated_double.append(1.0) - message.repeated_double.append(1.2) - message.repeated_double.append(1.23) - message.repeated_double.append(1.234) - message.repeated_double.append(1.2345) - message.repeated_double.append(1.23456) - message.repeated_double.append(1.234567) - message.repeated_double.append(1.2345678) - message.repeated_double.append(1.23456789) - message.repeated_double.append(1.234567898) - message.repeated_double.append(1.2345678987) - message.repeated_double.append(1.23456789876) - message.repeated_double.append(1.234567898765) - message.repeated_double.append(1.2345678987654) - message.repeated_double.append(1.23456789876543) - message.repeated_double.append(1.2e100) - message.repeated_double.append(1.23e100) - message.repeated_double.append(1.234e100) - message.repeated_double.append(1.2345e100) - message.repeated_double.append(1.23456e100) - message.repeated_double.append(1.234567e100) - message.repeated_double.append(1.2345678e100) - message.repeated_double.append(1.23456789e100) - message.repeated_double.append(1.234567898e100) - message.repeated_double.append(1.2345678987e100) - message.repeated_double.append(1.23456789876e100) - message.repeated_double.append(1.234567898765e100) - message.repeated_double.append(1.2345678987654e100) - message.repeated_double.append(1.23456789876543e100) - # pylint: disable=g-long-ternary - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_format.MessageToString(message)), - 'repeated_float: 0\n' - 'repeated_float: 0.8\n' - 'repeated_float: 1\n' - 'repeated_float: 1.2\n' - 'repeated_float: 1.23\n' - 'repeated_float: 1.234\n' - 'repeated_float: 1.2345\n' - 'repeated_float: 1.23456\n' - # Note that these don't use scientific notation. - 'repeated_float: 12000000000\n' - 'repeated_float: 12300000000\n' - 'repeated_float: 12340000000\n' - 'repeated_float: 12345000000\n' - 'repeated_float: 12345600000\n' - 'repeated_float: nan\n' - 'repeated_float: inf\n' - 'repeated_double: 0\n' - 'repeated_double: 0.8\n' - 'repeated_double: 1\n' - 'repeated_double: 1.2\n' - 'repeated_double: 1.23\n' - 'repeated_double: 1.234\n' - 'repeated_double: 1.2345\n' - 'repeated_double: 1.23456\n' - 'repeated_double: 1.234567\n' - 'repeated_double: 1.2345678\n' - 'repeated_double: 1.23456789\n' - 'repeated_double: 1.234567898\n' - 'repeated_double: 1.2345678987\n' - 'repeated_double: 1.23456789876\n' - 'repeated_double: 1.234567898765\n' - 'repeated_double: 1.2345678987654\n' - 'repeated_double: 1.23456789876543\n' - 'repeated_double: 1.2e+100\n' - 'repeated_double: 1.23e+100\n' - 'repeated_double: 1.234e+100\n' - 'repeated_double: 1.2345e+100\n' - 'repeated_double: 1.23456e+100\n' - 'repeated_double: 1.234567e+100\n' - 'repeated_double: 1.2345678e+100\n' - 'repeated_double: 1.23456789e+100\n' - 'repeated_double: 1.234567898e+100\n' - 'repeated_double: 1.2345678987e+100\n' - 'repeated_double: 1.23456789876e+100\n' - 'repeated_double: 1.234567898765e+100\n' - 'repeated_double: 1.2345678987654e+100\n' - 'repeated_double: 1.23456789876543e+100\n') - - def testPrintExoticUnicodeSubclass(self, message_module): - - class UnicodeSub(str): - pass - - message = message_module.TestAllTypes() - message.repeated_string.append(UnicodeSub(u'\u00fc\ua71f')) - self.CompareToGoldenText( - text_format.MessageToString(message), - 'repeated_string: "\\303\\274\\352\\234\\237"\n') - - def testPrintNestedMessageAsOneLine(self, message_module): - message = message_module.TestAllTypes() - msg = message.repeated_nested_message.add() - msg.bb = 42 - self.CompareToGoldenText( - text_format.MessageToString(message, as_one_line=True), - 'repeated_nested_message { bb: 42 }') - - def testPrintRepeatedFieldsAsOneLine(self, message_module): - message = message_module.TestAllTypes() - message.repeated_int32.append(1) - message.repeated_int32.append(1) - message.repeated_int32.append(3) - message.repeated_string.append('Google') - message.repeated_string.append('Zurich') - self.CompareToGoldenText( - text_format.MessageToString(message, as_one_line=True), - 'repeated_int32: 1 repeated_int32: 1 repeated_int32: 3 ' - 'repeated_string: "Google" repeated_string: "Zurich"') - - def VerifyPrintShortFormatRepeatedFields(self, message_module, as_one_line): - message = message_module.TestAllTypes() - message.repeated_int32.append(1) - message.repeated_string.append('Google') - message.repeated_string.append('Hello,World') - message.repeated_foreign_enum.append(unittest_pb2.FOREIGN_FOO) - message.repeated_foreign_enum.append(unittest_pb2.FOREIGN_BAR) - message.repeated_foreign_enum.append(unittest_pb2.FOREIGN_BAZ) - message.optional_nested_message.bb = 3 - for i in (21, 32): - msg = message.repeated_nested_message.add() - msg.bb = i - expected_ascii = ( - 'optional_nested_message {\n bb: 3\n}\n' - 'repeated_int32: [1]\n' - 'repeated_string: "Google"\n' - 'repeated_string: "Hello,World"\n' - 'repeated_nested_message {\n bb: 21\n}\n' - 'repeated_nested_message {\n bb: 32\n}\n' - 'repeated_foreign_enum: [FOREIGN_FOO, FOREIGN_BAR, FOREIGN_BAZ]\n') - if as_one_line: - expected_ascii = expected_ascii.replace('\n', ' ') - expected_ascii = re.sub(r'\s+', ' ', expected_ascii) - expected_ascii = re.sub(r'\s$', '', expected_ascii) - - actual_ascii = text_format.MessageToString( - message, use_short_repeated_primitives=True, - as_one_line=as_one_line) - self.CompareToGoldenText(actual_ascii, expected_ascii) - parsed_message = message_module.TestAllTypes() - text_format.Parse(actual_ascii, parsed_message) - self.assertEqual(parsed_message, message) - - def testPrintShortFormatRepeatedFields(self, message_module): - self.VerifyPrintShortFormatRepeatedFields(message_module, False) - self.VerifyPrintShortFormatRepeatedFields(message_module, True) - - def testPrintNestedNewLineInStringAsOneLine(self, message_module): - message = message_module.TestAllTypes() - message.optional_string = 'a\nnew\nline' - self.CompareToGoldenText( - text_format.MessageToString(message, as_one_line=True), - 'optional_string: "a\\nnew\\nline"') - - def testPrintExoticAsOneLine(self, message_module): - message = message_module.TestAllTypes() - message.repeated_int64.append(-9223372036854775808) - message.repeated_uint64.append(18446744073709551615) - message.repeated_double.append(123.456) - message.repeated_double.append(1.23e22) - message.repeated_double.append(1.23e-18) - message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"') - message.repeated_string.append(u'\u00fc\ua71f') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_format.MessageToString( - message, as_one_line=True)), - 'repeated_int64: -9223372036854775808' - ' repeated_uint64: 18446744073709551615' - ' repeated_double: 123.456' - ' repeated_double: 1.23e+22' - ' repeated_double: 1.23e-18' - ' repeated_string: ' - '"\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""' - ' repeated_string: "\\303\\274\\352\\234\\237"') - - def testRoundTripExoticAsOneLine(self, message_module): - message = message_module.TestAllTypes() - message.repeated_int64.append(-9223372036854775808) - message.repeated_uint64.append(18446744073709551615) - message.repeated_double.append(123.456) - message.repeated_double.append(1.23e22) - message.repeated_double.append(1.23e-18) - message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"') - message.repeated_string.append(u'\u00fc\ua71f') - - # Test as_utf8 = False. - wire_text = text_format.MessageToString(message, - as_one_line=True, - as_utf8=False) - parsed_message = message_module.TestAllTypes() - r = text_format.Parse(wire_text, parsed_message) - self.assertIs(r, parsed_message) - self.assertEqual(message, parsed_message) - - # Test as_utf8 = True. - wire_text = text_format.MessageToString(message, - as_one_line=True, - as_utf8=True) - parsed_message = message_module.TestAllTypes() - r = text_format.Parse(wire_text, parsed_message) - self.assertIs(r, parsed_message) - self.assertEqual(message, parsed_message, - '\n%s != %s' % (message, parsed_message)) - - def testPrintRawUtf8String(self, message_module): - message = message_module.TestAllTypes() - message.repeated_string.append(u'\u00fc\t\ua71f') - text = text_format.MessageToString(message, as_utf8=True) - golden_unicode = u'repeated_string: "\u00fc\\t\ua71f"\n' - golden_text = golden_unicode - # MessageToString always returns a native str. - self.CompareToGoldenText(text, golden_text) - parsed_message = message_module.TestAllTypes() - text_format.Parse(text, parsed_message) - self.assertEqual( - message, parsed_message, '\n%s != %s (%s != %s)' % - (message, parsed_message, message.repeated_string[0], - parsed_message.repeated_string[0])) - - def testPrintFloatFormat(self, message_module): - # Check that float_format argument is passed to sub-message formatting. - message = message_module.NestedTestAllTypes() - message.payload.optional_float = 1.25 - # Check rounding at 15 significant digits - message.payload.optional_double = -.000003456789012345678 - # Check no decimal point. - message.payload.repeated_float.append(-5642) - # Check no trailing zeros. - message.payload.repeated_double.append(.000078900) - formatted_fields = ['optional_float: 1.25', - 'optional_double: -3.45678901234568e-6', - 'repeated_float: -5642', 'repeated_double: 7.89e-5'] - text_message = text_format.MessageToString(message, float_format='.15g') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_message), - 'payload {{\n {0}\n {1}\n {2}\n {3}\n}}\n'.format( - *formatted_fields)) - # as_one_line=True is a separate code branch where float_format is passed. - text_message = text_format.MessageToString(message, - as_one_line=True, - float_format='.15g') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_message), - 'payload {{ {0} {1} {2} {3} }}'.format(*formatted_fields)) - - # 32-bit 1.2 is noisy when extended to 64-bit: - # >>> struct.unpack('f', struct.pack('f', 1.2))[0] - # 1.2000000476837158 - message.payload.optional_float = 1.2 - formatted_fields = ['optional_float: 1.2', - 'optional_double: -3.45678901234568e-6', - 'repeated_float: -5642', 'repeated_double: 7.89e-5'] - text_message = text_format.MessageToString(message, float_format='.7g', - double_format='.15g') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_message), - 'payload {{\n {0}\n {1}\n {2}\n {3}\n}}\n'.format( - *formatted_fields)) - - # Test only set float_format affect both float and double fields. - formatted_fields = ['optional_float: 1.2', - 'optional_double: -3.456789e-6', - 'repeated_float: -5642', 'repeated_double: 7.89e-5'] - text_message = text_format.MessageToString(message, float_format='.7g') - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_message), - 'payload {{\n {0}\n {1}\n {2}\n {3}\n}}\n'.format( - *formatted_fields)) - - # Test default float_format will automatic print shortest float. - message.payload.optional_float = 1.2345678912 - message.payload.optional_double = 1.2345678912 - formatted_fields = ['optional_float: 1.2345679', - 'optional_double: 1.2345678912', - 'repeated_float: -5642', 'repeated_double: 7.89e-5'] - text_message = text_format.MessageToString(message) - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_message), - 'payload {{\n {0}\n {1}\n {2}\n {3}\n}}\n'.format( - *formatted_fields)) - - message.Clear() - message.payload.optional_float = 1.1000000000011 - self.assertEqual(text_format.MessageToString(message), - 'payload {\n optional_float: 1.1\n}\n') - message.payload.optional_float = 1.00000075e-36 - self.assertEqual(text_format.MessageToString(message), - 'payload {\n optional_float: 1.00000075e-36\n}\n') - message.payload.optional_float = 12345678912345e+11 - self.assertEqual(text_format.MessageToString(message), - 'payload {\n optional_float: 1.234568e+24\n}\n') - - def testMessageToString(self, message_module): - message = message_module.ForeignMessage() - message.c = 123 - self.assertEqual('c: 123\n', str(message)) - - def testMessageToStringUnicode(self, message_module): - golden_unicode = u'Á short desçription and a 🍌.' - golden_bytes = golden_unicode.encode('utf-8') - message = message_module.TestAllTypes() - message.optional_string = golden_unicode - message.optional_bytes = golden_bytes - text = text_format.MessageToString(message, as_utf8=True) - golden_message = textwrap.dedent( - 'optional_string: "Á short desçription and a 🍌."\n' - 'optional_bytes: ' - r'"\303\201 short des\303\247ription and a \360\237\215\214."' - '\n') - self.CompareToGoldenText(text, golden_message) - - def testMessageToStringASCII(self, message_module): - golden_unicode = u'Á short desçription and a 🍌.' - golden_bytes = golden_unicode.encode('utf-8') - message = message_module.TestAllTypes() - message.optional_string = golden_unicode - message.optional_bytes = golden_bytes - text = text_format.MessageToString(message, as_utf8=False) # ASCII - golden_message = ( - 'optional_string: ' - r'"\303\201 short des\303\247ription and a \360\237\215\214."' - '\n' - 'optional_bytes: ' - r'"\303\201 short des\303\247ription and a \360\237\215\214."' - '\n') - self.CompareToGoldenText(text, golden_message) - - def testPrintField(self, message_module): - message = message_module.TestAllTypes() - field = message.DESCRIPTOR.fields_by_name['optional_float'] - value = message.optional_float - out = text_format.TextWriter(False) - text_format.PrintField(field, value, out) - self.assertEqual('optional_float: 0.0\n', out.getvalue()) - out.close() - # Test Printer - out = text_format.TextWriter(False) - printer = text_format._Printer(out) - printer.PrintField(field, value) - self.assertEqual('optional_float: 0.0\n', out.getvalue()) - out.close() - - def testPrintFieldValue(self, message_module): - message = message_module.TestAllTypes() - field = message.DESCRIPTOR.fields_by_name['optional_float'] - value = message.optional_float - out = text_format.TextWriter(False) - text_format.PrintFieldValue(field, value, out) - self.assertEqual('0.0', out.getvalue()) - out.close() - # Test Printer - out = text_format.TextWriter(False) - printer = text_format._Printer(out) - printer.PrintFieldValue(field, value) - self.assertEqual('0.0', out.getvalue()) - out.close() - - def testCustomOptions(self, message_module): - message_descriptor = (unittest_custom_options_pb2. - TestMessageWithCustomOptions.DESCRIPTOR) - message_proto = descriptor_pb2.DescriptorProto() - message_descriptor.CopyToProto(message_proto) - expected_text = ( - 'name: "TestMessageWithCustomOptions"\n' - 'field {\n' - ' name: "field1"\n' - ' number: 1\n' - ' label: LABEL_OPTIONAL\n' - ' type: TYPE_STRING\n' - ' options {\n' - ' ctype: CORD\n' - ' [protobuf_unittest.field_opt1]: 8765432109\n' - ' }\n' - '}\n' - 'field {\n' - ' name: "oneof_field"\n' - ' number: 2\n' - ' label: LABEL_OPTIONAL\n' - ' type: TYPE_INT32\n' - ' oneof_index: 0\n' - '}\n' - 'field {\n' - ' name: "map_field"\n' - ' number: 3\n' - ' label: LABEL_REPEATED\n' - ' type: TYPE_MESSAGE\n' - ' type_name: ".protobuf_unittest.TestMessageWithCustomOptions.' - 'MapFieldEntry"\n' - ' options {\n' - ' [protobuf_unittest.field_opt1]: 12345\n' - ' }\n' - '}\n' - 'nested_type {\n' - ' name: "MapFieldEntry"\n' - ' field {\n' - ' name: "key"\n' - ' number: 1\n' - ' label: LABEL_OPTIONAL\n' - ' type: TYPE_STRING\n' - ' }\n' - ' field {\n' - ' name: "value"\n' - ' number: 2\n' - ' label: LABEL_OPTIONAL\n' - ' type: TYPE_STRING\n' - ' }\n' - ' options {\n' - ' map_entry: true\n' - ' }\n' - '}\n' - 'enum_type {\n' - ' name: "AnEnum"\n' - ' value {\n' - ' name: "ANENUM_VAL1"\n' - ' number: 1\n' - ' }\n' - ' value {\n' - ' name: "ANENUM_VAL2"\n' - ' number: 2\n' - ' options {\n' - ' [protobuf_unittest.enum_value_opt1]: 123\n' - ' }\n' - ' }\n' - ' options {\n' - ' [protobuf_unittest.enum_opt1]: -789\n' - ' }\n' - '}\n' - 'options {\n' - ' message_set_wire_format: false\n' - ' [protobuf_unittest.message_opt1]: -56\n' - '}\n' - 'oneof_decl {\n' - ' name: "AnOneof"\n' - ' options {\n' - ' [protobuf_unittest.oneof_opt1]: -99\n' - ' }\n' - '}\n') - self.assertEqual(expected_text, - text_format.MessageToString(message_proto)) - parsed_proto = descriptor_pb2.DescriptorProto() - text_format.Parse(expected_text, parsed_proto) - self.assertEqual(message_proto, parsed_proto) - - @unittest.skipIf( - api_implementation.Type() == 'upb', - "upb API doesn't support old UnknownField API. The TextFormat library " - "needs to convert to the new API.") - def testPrintUnknownFieldsEmbeddedMessageInBytes(self, message_module): - inner_msg = message_module.TestAllTypes() - inner_msg.optional_int32 = 101 - inner_msg.optional_double = 102.0 - inner_msg.optional_string = u'hello' - inner_msg.optional_bytes = b'103' - inner_msg.optional_nested_message.bb = 105 - inner_data = inner_msg.SerializeToString() - outer_message = message_module.TestAllTypes() - outer_message.optional_int32 = 101 - outer_message.optional_bytes = inner_data - all_data = outer_message.SerializeToString() - empty_message = message_module.TestEmptyMessage() - empty_message.ParseFromString(all_data) - - self.assertEqual(' 1: 101\n' - ' 15 {\n' - ' 1: 101\n' - ' 12: 4636878028842991616\n' - ' 14: "hello"\n' - ' 15: "103"\n' - ' 18 {\n' - ' 1: 105\n' - ' }\n' - ' }\n', - text_format.MessageToString(empty_message, - indent=2, - print_unknown_fields=True)) - self.assertEqual('1: 101 ' - '15 { ' - '1: 101 ' - '12: 4636878028842991616 ' - '14: "hello" ' - '15: "103" ' - '18 { 1: 105 } ' - '}', - text_format.MessageToString(empty_message, - print_unknown_fields=True, - as_one_line=True)) - - -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) -class TextFormatMessageToTextBytesTests(TextFormatBase): - - def testMessageToBytes(self, message_module): - message = message_module.ForeignMessage() - message.c = 123 - self.assertEqual(b'c: 123\n', text_format.MessageToBytes(message)) - - def testRawUtf8RoundTrip(self, message_module): - message = message_module.TestAllTypes() - message.repeated_string.append(u'\u00fc\t\ua71f') - utf8_text = text_format.MessageToBytes(message, as_utf8=True) - golden_bytes = b'repeated_string: "\xc3\xbc\\t\xea\x9c\x9f"\n' - self.CompareToGoldenText(utf8_text, golden_bytes) - parsed_message = message_module.TestAllTypes() - text_format.Parse(utf8_text, parsed_message) - self.assertEqual( - message, parsed_message, '\n%s != %s (%s != %s)' % - (message, parsed_message, message.repeated_string[0], - parsed_message.repeated_string[0])) - - def testEscapedUtf8ASCIIRoundTrip(self, message_module): - message = message_module.TestAllTypes() - message.repeated_string.append(u'\u00fc\t\ua71f') - ascii_text = text_format.MessageToBytes(message) # as_utf8=False default - golden_bytes = b'repeated_string: "\\303\\274\\t\\352\\234\\237"\n' - self.CompareToGoldenText(ascii_text, golden_bytes) - parsed_message = message_module.TestAllTypes() - text_format.Parse(ascii_text, parsed_message) - self.assertEqual( - message, parsed_message, '\n%s != %s (%s != %s)' % - (message, parsed_message, message.repeated_string[0], - parsed_message.repeated_string[0])) - - -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) -class TextFormatParserTests(TextFormatBase): - - def testParseAllFields(self, message_module): - message = message_module.TestAllTypes() - test_util.SetAllFields(message) - ascii_text = text_format.MessageToString(message) - - parsed_message = message_module.TestAllTypes() - text_format.Parse(ascii_text, parsed_message) - self.assertEqual(message, parsed_message) - if message_module is unittest_pb2: - test_util.ExpectAllFieldsSet(self, message) - - def testParseAndMergeUtf8(self, message_module): - message = message_module.TestAllTypes() - test_util.SetAllFields(message) - ascii_text = text_format.MessageToString(message) - ascii_text = ascii_text.encode('utf-8') - - parsed_message = message_module.TestAllTypes() - text_format.Parse(ascii_text, parsed_message) - self.assertEqual(message, parsed_message) - if message_module is unittest_pb2: - test_util.ExpectAllFieldsSet(self, message) - - parsed_message.Clear() - text_format.Merge(ascii_text, parsed_message) - self.assertEqual(message, parsed_message) - if message_module is unittest_pb2: - test_util.ExpectAllFieldsSet(self, message) - - msg2 = message_module.TestAllTypes() - text = (u'optional_string: "café"') - text_format.Merge(text, msg2) - self.assertEqual(msg2.optional_string, u'café') - msg2.Clear() - self.assertEqual(msg2.optional_string, u'') - text_format.Parse(text, msg2) - self.assertEqual(msg2.optional_string, u'café') - - def testParseDoubleToFloat(self, message_module): - message = message_module.TestAllTypes() - text = ('repeated_float: 3.4028235e+39\n' - 'repeated_float: 1.4028235e-39\n') - text_format.Parse(text, message) - self.assertEqual(message.repeated_float[0], float('inf')) - self.assertAlmostEqual(message.repeated_float[1], 1.4028235e-39) - - def testParseExotic(self, message_module): - message = message_module.TestAllTypes() - text = ('repeated_int64: -9223372036854775808\n' - 'repeated_uint64: 18446744073709551615\n' - 'repeated_double: 123.456\n' - 'repeated_double: 1.23e+22\n' - 'repeated_double: 1.23e-18\n' - 'repeated_string: \n' - '"\\000\\001\\007\\010\\014\\n\\r\\t\\013\\\\\\\'\\""\n' - 'repeated_string: "foo" \'corge\' "grault"\n' - 'repeated_string: "\\303\\274\\352\\234\\237"\n' - 'repeated_string: "\\xc3\\xbc"\n' - 'repeated_string: "\xc3\xbc"\n') - text_format.Parse(text, message) - - self.assertEqual(-9223372036854775808, message.repeated_int64[0]) - self.assertEqual(18446744073709551615, message.repeated_uint64[0]) - self.assertEqual(123.456, message.repeated_double[0]) - self.assertEqual(1.23e22, message.repeated_double[1]) - self.assertEqual(1.23e-18, message.repeated_double[2]) - self.assertEqual('\000\001\a\b\f\n\r\t\v\\\'"', message.repeated_string[0]) - self.assertEqual('foocorgegrault', message.repeated_string[1]) - self.assertEqual(u'\u00fc\ua71f', message.repeated_string[2]) - self.assertEqual(u'\u00fc', message.repeated_string[3]) - - def testParseTrailingCommas(self, message_module): - message = message_module.TestAllTypes() - text = ('repeated_int64: 100;\n' - 'repeated_int64: 200;\n' - 'repeated_int64: 300,\n' - 'repeated_string: "one",\n' - 'repeated_string: "two";\n') - text_format.Parse(text, message) - - self.assertEqual(100, message.repeated_int64[0]) - self.assertEqual(200, message.repeated_int64[1]) - self.assertEqual(300, message.repeated_int64[2]) - self.assertEqual(u'one', message.repeated_string[0]) - self.assertEqual(u'two', message.repeated_string[1]) - - def testParseRepeatedScalarShortFormat(self, message_module): - message = message_module.TestAllTypes() - text = ('repeated_int64: [100, 200];\n' - 'repeated_int64: []\n' - 'repeated_int64: 300,\n' - 'repeated_string: ["one", "two"];\n') - text_format.Parse(text, message) - - self.assertEqual(100, message.repeated_int64[0]) - self.assertEqual(200, message.repeated_int64[1]) - self.assertEqual(300, message.repeated_int64[2]) - self.assertEqual(u'one', message.repeated_string[0]) - self.assertEqual(u'two', message.repeated_string[1]) - - def testParseRepeatedMessageShortFormat(self, message_module): - message = message_module.TestAllTypes() - text = ('repeated_nested_message: [{bb: 100}, {bb: 200}],\n' - 'repeated_nested_message: {bb: 300}\n' - 'repeated_nested_message [{bb: 400}];\n') - text_format.Parse(text, message) - - self.assertEqual(100, message.repeated_nested_message[0].bb) - self.assertEqual(200, message.repeated_nested_message[1].bb) - self.assertEqual(300, message.repeated_nested_message[2].bb) - self.assertEqual(400, message.repeated_nested_message[3].bb) - - def testParseEmptyText(self, message_module): - message = message_module.TestAllTypes() - text = '' - text_format.Parse(text, message) - self.assertEqual(message_module.TestAllTypes(), message) - - def testParseInvalidUtf8(self, message_module): - message = message_module.TestAllTypes() - text = 'repeated_string: "\\xc3\\xc3"' - with self.assertRaises(text_format.ParseError) as e: - text_format.Parse(text, message) - self.assertEqual(e.exception.GetLine(), 1) - self.assertEqual(e.exception.GetColumn(), 28) - - def testParseSingleWord(self, message_module): - message = message_module.TestAllTypes() - text = 'foo' - self.assertRaisesRegex( - text_format.ParseError, - (r'1:1 : Message type "\w+.TestAllTypes" has no field named ' - r'"foo".'), text_format.Parse, text, message) - - def testParseUnknownField(self, message_module): - message = message_module.TestAllTypes() - text = 'unknown_field: 8\n' - self.assertRaisesRegex( - text_format.ParseError, - (r'1:1 : Message type "\w+.TestAllTypes" has no field named ' - r'"unknown_field".'), text_format.Parse, text, message) - text = ('optional_int32: 123\n' - 'unknown_field: 8\n' - 'optional_nested_message { bb: 45 }') - text_format.Parse(text, message, allow_unknown_field=True) - self.assertEqual(message.optional_nested_message.bb, 45) - self.assertEqual(message.optional_int32, 123) - - def testParseBadEnumValue(self, message_module): - message = message_module.TestAllTypes() - text = 'optional_nested_enum: BARR' - self.assertRaisesRegex(text_format.ParseError, - (r'1:23 : \'optional_nested_enum: BARR\': ' - r'Enum type "\w+.TestAllTypes.NestedEnum" ' - r'has no value named BARR.'), text_format.Parse, - text, message) - - def testParseBadIntValue(self, message_module): - message = message_module.TestAllTypes() - text = 'optional_int32: bork' - self.assertRaisesRegex(text_format.ParseError, - ('1:17 : \'optional_int32: bork\': ' - 'Couldn\'t parse integer: bork'), text_format.Parse, - text, message) - - def testParseStringFieldUnescape(self, message_module): - message = message_module.TestAllTypes() - text = r'''repeated_string: "\xf\x62" - repeated_string: "\\xf\\x62" - repeated_string: "\\\xf\\\x62" - repeated_string: "\\\\xf\\\\x62" - repeated_string: "\\\\\xf\\\\\x62" - repeated_string: "\x5cx20"''' - - text_format.Parse(text, message) - - SLASH = '\\' - self.assertEqual('\x0fb', message.repeated_string[0]) - self.assertEqual(SLASH + 'xf' + SLASH + 'x62', message.repeated_string[1]) - self.assertEqual(SLASH + '\x0f' + SLASH + 'b', message.repeated_string[2]) - self.assertEqual(SLASH + SLASH + 'xf' + SLASH + SLASH + 'x62', - message.repeated_string[3]) - self.assertEqual(SLASH + SLASH + '\x0f' + SLASH + SLASH + 'b', - message.repeated_string[4]) - self.assertEqual(SLASH + 'x20', message.repeated_string[5]) - - def testParseOneof(self, message_module): - m = message_module.TestAllTypes() - m.oneof_uint32 = 11 - m2 = message_module.TestAllTypes() - text_format.Parse(text_format.MessageToString(m), m2) - self.assertEqual('oneof_uint32', m2.WhichOneof('oneof_field')) - - def testParseMultipleOneof(self, message_module): - m_string = '\n'.join(['oneof_uint32: 11', 'oneof_string: "foo"']) - m2 = message_module.TestAllTypes() - with self.assertRaisesRegex(text_format.ParseError, - ' is specified along with field '): - text_format.Parse(m_string, m2) - - # This example contains non-ASCII codepoint unicode data as literals - # which should come through as utf-8 for bytes, and as the unicode - # itself for string fields. It also demonstrates escaped binary data. - # The ur"" string prefix is unfortunately missing from Python 3 - # so we resort to double escaping our \s so that they come through. - _UNICODE_SAMPLE = u""" - optional_bytes: 'Á short desçription' - optional_string: 'Á short desçription' - repeated_bytes: '\\303\\201 short des\\303\\247ription' - repeated_bytes: '\\x12\\x34\\x56\\x78\\x90\\xab\\xcd\\xef' - repeated_string: '\\xd0\\x9f\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82' - """ - _BYTES_SAMPLE = _UNICODE_SAMPLE.encode('utf-8') - _GOLDEN_UNICODE = u'Á short desçription' - _GOLDEN_BYTES = _GOLDEN_UNICODE.encode('utf-8') - _GOLDEN_BYTES_1 = b'\x12\x34\x56\x78\x90\xab\xcd\xef' - _GOLDEN_STR_0 = u'Привет' - - def testParseUnicode(self, message_module): - m = message_module.TestAllTypes() - text_format.Parse(self._UNICODE_SAMPLE, m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - # repeated_bytes[1] contained simple \ escaped non-UTF-8 raw binary data. - self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES_1) - # repeated_string[0] contained \ escaped data representing the UTF-8 - # representation of _GOLDEN_STR_0 - it needs to decode as such. - self.assertEqual(m.repeated_string[0], self._GOLDEN_STR_0) - - def testParseBytes(self, message_module): - m = message_module.TestAllTypes() - text_format.Parse(self._BYTES_SAMPLE, m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - # repeated_bytes[1] contained simple \ escaped non-UTF-8 raw binary data. - self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES_1) - # repeated_string[0] contained \ escaped data representing the UTF-8 - # representation of _GOLDEN_STR_0 - it needs to decode as such. - self.assertEqual(m.repeated_string[0], self._GOLDEN_STR_0) - - def testFromBytesFile(self, message_module): - m = message_module.TestAllTypes() - f = io.BytesIO(self._BYTES_SAMPLE) - text_format.ParseLines(f, m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - - def testFromUnicodeFile(self, message_module): - m = message_module.TestAllTypes() - f = io.StringIO(self._UNICODE_SAMPLE) - text_format.ParseLines(f, m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - - def testFromBytesLines(self, message_module): - m = message_module.TestAllTypes() - text_format.ParseLines(self._BYTES_SAMPLE.split(b'\n'), m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - - def testFromUnicodeLines(self, message_module): - m = message_module.TestAllTypes() - text_format.ParseLines(self._UNICODE_SAMPLE.split(u'\n'), m) - self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES) - self.assertEqual(m.optional_string, self._GOLDEN_UNICODE) - self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES) - - def testParseDuplicateMessages(self, message_module): - message = message_module.TestAllTypes() - text = ('optional_nested_message { bb: 1 } ' - 'optional_nested_message { bb: 2 }') - self.assertRaisesRegex( - text_format.ParseError, - (r'1:59 : Message type "\w+.TestAllTypes" ' - r'should not have multiple "optional_nested_message" fields.'), - text_format.Parse, text, message) - - def testParseDuplicateScalars(self, message_module): - message = message_module.TestAllTypes() - text = ('optional_int32: 42 ' 'optional_int32: 67') - self.assertRaisesRegex( - text_format.ParseError, - (r'1:36 : Message type "\w+.TestAllTypes" should not ' - r'have multiple "optional_int32" fields.'), text_format.Parse, text, - message) - - def testParseExistingScalarInMessage(self, message_module): - message = message_module.TestAllTypes(optional_int32=42) - text = 'optional_int32: 67' - self.assertRaisesRegex(text_format.ParseError, - (r'Message type "\w+.TestAllTypes" should not ' - r'have multiple "optional_int32" fields.'), - text_format.Parse, text, message) - - -@_parameterized.parameters(unittest_pb2, unittest_proto3_arena_pb2) -class TextFormatMergeTests(TextFormatBase): - - def testMergeDuplicateScalarsInText(self, message_module): - message = message_module.TestAllTypes() - text = ('optional_int32: 42 ' 'optional_int32: 67') - r = text_format.Merge(text, message) - self.assertIs(r, message) - self.assertEqual(67, message.optional_int32) - - def testMergeDuplicateNestedMessageScalars(self, message_module): - message = message_module.TestAllTypes() - text = ('optional_nested_message { bb: 1 } ' - 'optional_nested_message { bb: 2 }') - r = text_format.Merge(text, message) - self.assertTrue(r is message) - self.assertEqual(2, message.optional_nested_message.bb) - - def testReplaceScalarInMessage(self, message_module): - message = message_module.TestAllTypes(optional_int32=42) - text = 'optional_int32: 67' - r = text_format.Merge(text, message) - self.assertIs(r, message) - self.assertEqual(67, message.optional_int32) - - def testReplaceMessageInMessage(self, message_module): - message = message_module.TestAllTypes( - optional_int32=42, optional_nested_message=dict()) - self.assertTrue(message.HasField('optional_nested_message')) - text = 'optional_nested_message{ bb: 3 }' - r = text_format.Merge(text, message) - self.assertIs(r, message) - self.assertEqual(3, message.optional_nested_message.bb) - - def testMergeMultipleOneof(self, message_module): - m_string = '\n'.join(['oneof_uint32: 11', 'oneof_string: "foo"']) - m2 = message_module.TestAllTypes() - text_format.Merge(m_string, m2) - self.assertEqual('oneof_string', m2.WhichOneof('oneof_field')) - - -# These are tests that aren't fundamentally specific to proto2, but are at -# the moment because of differences between the proto2 and proto3 test schemas. -# Ideally the schemas would be made more similar so these tests could pass. -class OnlyWorksWithProto2RightNowTests(TextFormatBase): - - def testPrintAllFieldsPointy(self): - message = unittest_pb2.TestAllTypes() - test_util.SetAllFields(message) - self.CompareToGoldenFile( - self.RemoveRedundantZeros(text_format.MessageToString( - message, pointy_brackets=True)), - 'text_format_unittest_data_pointy_oneof.txt') - - def testParseGolden(self): - golden_text = '\n'.join(self.ReadGolden( - 'text_format_unittest_data_oneof_implemented.txt')) - parsed_message = unittest_pb2.TestAllTypes() - r = text_format.Parse(golden_text, parsed_message) - self.assertIs(r, parsed_message) - - message = unittest_pb2.TestAllTypes() - test_util.SetAllFields(message) - self.assertEqual(message, parsed_message) - - def testPrintAllFields(self): - message = unittest_pb2.TestAllTypes() - test_util.SetAllFields(message) - self.CompareToGoldenFile( - self.RemoveRedundantZeros(text_format.MessageToString(message)), - 'text_format_unittest_data_oneof_implemented.txt') - - def testPrintUnknownFields(self): - message = unittest_pb2.TestAllTypes() - message.optional_int32 = 101 - message.optional_double = 102.0 - message.optional_string = u'hello' - message.optional_bytes = b'103' - message.optionalgroup.a = 104 - message.optional_nested_message.bb = 105 - all_data = message.SerializeToString() - empty_message = unittest_pb2.TestEmptyMessage() - empty_message.ParseFromString(all_data) - self.assertEqual(' 1: 101\n' - ' 12: 4636878028842991616\n' - ' 14: "hello"\n' - ' 15: "103"\n' - ' 16 {\n' - ' 17: 104\n' - ' }\n' - ' 18 {\n' - ' 1: 105\n' - ' }\n', - text_format.MessageToString(empty_message, - indent=2, - print_unknown_fields=True)) - self.assertEqual('1: 101 ' - '12: 4636878028842991616 ' - '14: "hello" ' - '15: "103" ' - '16 { 17: 104 } ' - '18 { 1: 105 }', - text_format.MessageToString(empty_message, - print_unknown_fields=True, - as_one_line=True)) - - def testPrintInIndexOrder(self): - message = unittest_pb2.TestFieldOrderings() - # Fields are listed in index order instead of field number. - message.my_string = 'str' - message.my_int = 101 - message.my_float = 111 - message.optional_nested_message.oo = 0 - message.optional_nested_message.bb = 1 - message.Extensions[unittest_pb2.my_extension_string] = 'ext_str0' - # Extensions are listed based on the order of extension number. - # Extension number 12. - message.Extensions[unittest_pb2.TestExtensionOrderings2. - test_ext_orderings2].my_string = 'ext_str2' - # Extension number 13. - message.Extensions[unittest_pb2.TestExtensionOrderings1. - test_ext_orderings1].my_string = 'ext_str1' - # Extension number 14. - message.Extensions[ - unittest_pb2.TestExtensionOrderings2.TestExtensionOrderings3. - test_ext_orderings3].my_string = 'ext_str3' - - # Print in index order. - self.CompareToGoldenText( - self.RemoveRedundantZeros( - text_format.MessageToString(message, use_index_order=True)), - 'my_string: "str"\n' - 'my_int: 101\n' - 'my_float: 111\n' - 'optional_nested_message {\n' - ' oo: 0\n' - ' bb: 1\n' - '}\n' - '[protobuf_unittest.TestExtensionOrderings2.test_ext_orderings2] {\n' - ' my_string: "ext_str2"\n' - '}\n' - '[protobuf_unittest.TestExtensionOrderings1.test_ext_orderings1] {\n' - ' my_string: "ext_str1"\n' - '}\n' - '[protobuf_unittest.TestExtensionOrderings2.TestExtensionOrderings3' - '.test_ext_orderings3] {\n' - ' my_string: "ext_str3"\n' - '}\n' - '[protobuf_unittest.my_extension_string]: "ext_str0"\n') - # By default, print in field number order. - self.CompareToGoldenText( - self.RemoveRedundantZeros(text_format.MessageToString(message)), - 'my_int: 101\n' - 'my_string: "str"\n' - '[protobuf_unittest.TestExtensionOrderings2.test_ext_orderings2] {\n' - ' my_string: "ext_str2"\n' - '}\n' - '[protobuf_unittest.TestExtensionOrderings1.test_ext_orderings1] {\n' - ' my_string: "ext_str1"\n' - '}\n' - '[protobuf_unittest.TestExtensionOrderings2.TestExtensionOrderings3' - '.test_ext_orderings3] {\n' - ' my_string: "ext_str3"\n' - '}\n' - '[protobuf_unittest.my_extension_string]: "ext_str0"\n' - 'my_float: 111\n' - 'optional_nested_message {\n' - ' bb: 1\n' - ' oo: 0\n' - '}\n') - - def testMergeLinesGolden(self): - opened = self.ReadGolden('text_format_unittest_data_oneof_implemented.txt') - parsed_message = unittest_pb2.TestAllTypes() - r = text_format.MergeLines(opened, parsed_message) - self.assertIs(r, parsed_message) - - message = unittest_pb2.TestAllTypes() - test_util.SetAllFields(message) - self.assertEqual(message, parsed_message) - - def testParseLinesGolden(self): - opened = self.ReadGolden('text_format_unittest_data_oneof_implemented.txt') - parsed_message = unittest_pb2.TestAllTypes() - r = text_format.ParseLines(opened, parsed_message) - self.assertIs(r, parsed_message) - - message = unittest_pb2.TestAllTypes() - test_util.SetAllFields(message) - self.assertEqual(message, parsed_message) - - def testPrintMap(self): - message = map_unittest_pb2.TestMap() - - message.map_int32_int32[-123] = -456 - message.map_int64_int64[-2**33] = -2**34 - message.map_uint32_uint32[123] = 456 - message.map_uint64_uint64[2**33] = 2**34 - message.map_string_string['abc'] = '123' - message.map_int32_foreign_message[111].c = 5 - - # Maps are serialized to text format using their underlying repeated - # representation. - self.CompareToGoldenText( - text_format.MessageToString(message), 'map_int32_int32 {\n' - ' key: -123\n' - ' value: -456\n' - '}\n' - 'map_int64_int64 {\n' - ' key: -8589934592\n' - ' value: -17179869184\n' - '}\n' - 'map_uint32_uint32 {\n' - ' key: 123\n' - ' value: 456\n' - '}\n' - 'map_uint64_uint64 {\n' - ' key: 8589934592\n' - ' value: 17179869184\n' - '}\n' - 'map_string_string {\n' - ' key: "abc"\n' - ' value: "123"\n' - '}\n' - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' c: 5\n' - ' }\n' - '}\n') - - def testDuplicateMapKey(self): - message = map_unittest_pb2.TestMap() - text = ( - 'map_uint64_uint64 {\n' - ' key: 123\n' - ' value: 17179869184\n' - '}\n' - 'map_string_string {\n' - ' key: "abc"\n' - ' value: "first"\n' - '}\n' - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' c: 5\n' - ' }\n' - '}\n' - 'map_uint64_uint64 {\n' - ' key: 123\n' - ' value: 321\n' - '}\n' - 'map_string_string {\n' - ' key: "abc"\n' - ' value: "second"\n' - '}\n' - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' d: 5\n' - ' }\n' - '}\n') - text_format.Parse(text, message) - self.CompareToGoldenText( - text_format.MessageToString(message), 'map_uint64_uint64 {\n' - ' key: 123\n' - ' value: 321\n' - '}\n' - 'map_string_string {\n' - ' key: "abc"\n' - ' value: "second"\n' - '}\n' - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' d: 5\n' - ' }\n' - '}\n') - - # In cpp implementation, __str__ calls the cpp implementation of text format. - def testPrintMapUsingCppImplementation(self): - message = map_unittest_pb2.TestMap() - inner_msg = message.map_int32_foreign_message[111] - inner_msg.c = 1 - self.assertEqual( - str(message), - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' c: 1\n' - ' }\n' - '}\n') - inner_msg.c = 2 - self.assertEqual( - str(message), - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' c: 2\n' - ' }\n' - '}\n') - - def testMapOrderEnforcement(self): - message = map_unittest_pb2.TestMap() - for letter in string.ascii_uppercase[13:26]: - message.map_string_string[letter] = 'dummy' - for letter in reversed(string.ascii_uppercase[0:13]): - message.map_string_string[letter] = 'dummy' - golden = ''.join(('map_string_string {\n key: "%c"\n value: "dummy"\n}\n' - % (letter,) for letter in string.ascii_uppercase)) - self.CompareToGoldenText(text_format.MessageToString(message), golden) - - # TODO(teboring): In c/137553523, not serializing default value for map entry - # message has been fixed. This test needs to be disabled in order to submit - # that cl. Add this back when c/137553523 has been submitted. - # def testMapOrderSemantics(self): - # golden_lines = self.ReadGolden('map_test_data.txt') - - # message = map_unittest_pb2.TestMap() - # text_format.ParseLines(golden_lines, message) - # candidate = text_format.MessageToString(message) - # # The Python implementation emits "1.0" for the double value that the C++ - # # implementation emits as "1". - # candidate = candidate.replace('1.0', '1', 2) - # candidate = candidate.replace('0.0', '0', 2) - # self.assertMultiLineEqual(candidate, ''.join(golden_lines)) - - -# Tests of proto2-only features (MessageSet, extensions, etc.). -class Proto2Tests(TextFormatBase): - - def testPrintMessageSet(self): - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - self.CompareToGoldenText( - text_format.MessageToString(message), 'message_set {\n' - ' [protobuf_unittest.TestMessageSetExtension1] {\n' - ' i: 23\n' - ' }\n' - ' [protobuf_unittest.TestMessageSetExtension2] {\n' - ' str: \"foo\"\n' - ' }\n' - '}\n') - - message = message_set_extensions_pb2.TestMessageSet() - ext = message_set_extensions_pb2.message_set_extension3 - message.Extensions[ext].text = 'bar' - self.CompareToGoldenText( - text_format.MessageToString(message), - '[google.protobuf.internal.TestMessageSetExtension3] {\n' - ' text: \"bar\"\n' - '}\n') - - def testPrintMessageSetByFieldNumber(self): - out = text_format.TextWriter(False) - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - text_format.PrintMessage(message, out, use_field_number=True) - self.CompareToGoldenText(out.getvalue(), '1 {\n' - ' 1545008 {\n' - ' 15: 23\n' - ' }\n' - ' 1547769 {\n' - ' 25: \"foo\"\n' - ' }\n' - '}\n') - out.close() - - def testPrintMessageSetAsOneLine(self): - message = unittest_mset_pb2.TestMessageSetContainer() - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - message.message_set.Extensions[ext1].i = 23 - message.message_set.Extensions[ext2].str = 'foo' - self.CompareToGoldenText( - text_format.MessageToString(message, as_one_line=True), - 'message_set {' - ' [protobuf_unittest.TestMessageSetExtension1] {' - ' i: 23' - ' }' - ' [protobuf_unittest.TestMessageSetExtension2] {' - ' str: \"foo\"' - ' }' - ' }') - - def testParseMessageSet(self): - message = unittest_pb2.TestAllTypes() - text = ('repeated_uint64: 1\n' 'repeated_uint64: 2\n') - text_format.Parse(text, message) - self.assertEqual(1, message.repeated_uint64[0]) - self.assertEqual(2, message.repeated_uint64[1]) - - message = unittest_mset_pb2.TestMessageSetContainer() - text = ('message_set {\n' - ' [protobuf_unittest.TestMessageSetExtension1] {\n' - ' i: 23\n' - ' }\n' - ' [protobuf_unittest.TestMessageSetExtension2] {\n' - ' str: \"foo\"\n' - ' }\n' - '}\n') - text_format.Parse(text, message) - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - self.assertEqual(23, message.message_set.Extensions[ext1].i) - self.assertEqual('foo', message.message_set.Extensions[ext2].str) - - def testExtensionInsideAnyMessage(self): - message = test_extend_any.TestAny() - text = ('value {\n' - ' [type.googleapis.com/google.protobuf.internal.TestAny] {\n' - ' [google.protobuf.internal.TestAnyExtension1.extension1] {\n' - ' i: 10\n' - ' }\n' - ' }\n' - '}\n') - text_format.Merge(text, message, descriptor_pool=descriptor_pool.Default()) - self.CompareToGoldenText( - text_format.MessageToString( - message, descriptor_pool=descriptor_pool.Default()), - text) - - def testParseMessageByFieldNumber(self): - message = unittest_pb2.TestAllTypes() - text = ('34: 1\n' 'repeated_uint64: 2\n') - text_format.Parse(text, message, allow_field_number=True) - self.assertEqual(1, message.repeated_uint64[0]) - self.assertEqual(2, message.repeated_uint64[1]) - - message = unittest_mset_pb2.TestMessageSetContainer() - text = ('1 {\n' - ' 1545008 {\n' - ' 15: 23\n' - ' }\n' - ' 1547769 {\n' - ' 25: \"foo\"\n' - ' }\n' - '}\n') - text_format.Parse(text, message, allow_field_number=True) - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - self.assertEqual(23, message.message_set.Extensions[ext1].i) - self.assertEqual('foo', message.message_set.Extensions[ext2].str) - - # Can't parse field number without set allow_field_number=True. - message = unittest_pb2.TestAllTypes() - text = '34:1\n' - self.assertRaisesRegex( - text_format.ParseError, - (r'1:1 : Message type "\w+.TestAllTypes" has no field named ' - r'"34".'), text_format.Parse, text, message) - - # Can't parse if field number is not found. - text = '1234:1\n' - self.assertRaisesRegex( - text_format.ParseError, - (r'1:1 : Message type "\w+.TestAllTypes" has no field named ' - r'"1234".'), - text_format.Parse, - text, - message, - allow_field_number=True) - - def testPrintAllExtensions(self): - message = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(message) - self.CompareToGoldenFile( - self.RemoveRedundantZeros(text_format.MessageToString(message)), - 'text_format_unittest_extensions_data.txt') - - def testPrintAllExtensionsPointy(self): - message = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(message) - self.CompareToGoldenFile( - self.RemoveRedundantZeros(text_format.MessageToString( - message, pointy_brackets=True)), - 'text_format_unittest_extensions_data_pointy.txt') - - def testParseGoldenExtensions(self): - golden_text = '\n'.join(self.ReadGolden( - 'text_format_unittest_extensions_data.txt')) - parsed_message = unittest_pb2.TestAllExtensions() - text_format.Parse(golden_text, parsed_message) - - message = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(message) - self.assertEqual(message, parsed_message) - - def testParseAllExtensions(self): - message = unittest_pb2.TestAllExtensions() - test_util.SetAllExtensions(message) - ascii_text = text_format.MessageToString(message) - - parsed_message = unittest_pb2.TestAllExtensions() - text_format.Parse(ascii_text, parsed_message) - self.assertEqual(message, parsed_message) - - def testParseAllowedUnknownExtension(self): - # Skip over unknown extension correctly. - message = unittest_mset_pb2.TestMessageSetContainer() - text = ('message_set {\n' - ' [unknown_extension] {\n' - ' i: 23\n' - ' repeated_i: []\n' - ' bin: "\xe0"\n' - ' [nested_unknown_ext]: {\n' - ' i: 23\n' - ' repeated_i: [1, 2]\n' - ' x: x\n' - ' test: "test_string"\n' - ' floaty_float: -0.315\n' - ' num: -inf\n' - ' multiline_str: "abc"\n' - ' "def"\n' - ' "xyz."\n' - ' [nested_unknown_ext.ext]: <\n' - ' i: 23\n' - ' i: 24\n' - ' pointfloat: .3\n' - ' test: "test_string"\n' - ' repeated_test: ["test_string1", "test_string2"]\n' - ' floaty_float: -0.315\n' - ' num: -inf\n' - ' long_string: "test" "test2" \n' - ' >\n' - ' }\n' - ' }\n' - ' [unknown_extension]: 5\n' - ' [unknown_extension_with_number_field] {\n' - ' 1: "some_field"\n' - ' 2: -0.451\n' - ' }\n' - '}\n') - text_format.Parse(text, message, allow_unknown_extension=True) - golden = 'message_set {\n}\n' - self.CompareToGoldenText(text_format.MessageToString(message), golden) - - # Catch parse errors in unknown extension. - message = unittest_mset_pb2.TestMessageSetContainer() - malformed = ('message_set {\n' - ' [unknown_extension] {\n' - ' i:\n' # Missing value. - ' }\n' - '}\n') - self.assertRaisesRegex( - text_format.ParseError, - 'Invalid field value: }', - text_format.Parse, - malformed, - message, - allow_unknown_extension=True) - - message = unittest_mset_pb2.TestMessageSetContainer() - malformed = ('message_set {\n' - ' [unknown_extension] {\n' - ' str: "malformed string\n' # Missing closing quote. - ' }\n' - '}\n') - self.assertRaisesRegex( - text_format.ParseError, - 'Invalid field value: "', - text_format.Parse, - malformed, - message, - allow_unknown_extension=True) - - message = unittest_mset_pb2.TestMessageSetContainer() - malformed = ('message_set {\n' - ' [unknown_extension] {\n' - ' str: "malformed\n multiline\n string\n' - ' }\n' - '}\n') - self.assertRaisesRegex( - text_format.ParseError, - 'Invalid field value: "', - text_format.Parse, - malformed, - message, - allow_unknown_extension=True) - - message = unittest_mset_pb2.TestMessageSetContainer() - malformed = ('message_set {\n' - ' [malformed_extension] <\n' - ' i: -5\n' - ' \n' # Missing '>' here. - '}\n') - self.assertRaisesRegex( - text_format.ParseError, - '5:1 : \'}\': Expected ">".', - text_format.Parse, - malformed, - message, - allow_unknown_extension=True) - - # Don't allow unknown fields with allow_unknown_extension=True. - message = unittest_mset_pb2.TestMessageSetContainer() - malformed = ('message_set {\n' - ' unknown_field: true\n' - '}\n') - self.assertRaisesRegex( - text_format.ParseError, - ('2:3 : Message type ' - '"proto2_wireformat_unittest.TestMessageSet" has no' - ' field named "unknown_field".'), - text_format.Parse, - malformed, - message, - allow_unknown_extension=True) - - # Parse known extension correctly. - message = unittest_mset_pb2.TestMessageSetContainer() - text = ('message_set {\n' - ' [protobuf_unittest.TestMessageSetExtension1] {\n' - ' i: 23\n' - ' }\n' - ' [protobuf_unittest.TestMessageSetExtension2] {\n' - ' str: \"foo\"\n' - ' }\n' - '}\n') - text_format.Parse(text, message, allow_unknown_extension=True) - ext1 = unittest_mset_pb2.TestMessageSetExtension1.message_set_extension - ext2 = unittest_mset_pb2.TestMessageSetExtension2.message_set_extension - self.assertEqual(23, message.message_set.Extensions[ext1].i) - self.assertEqual('foo', message.message_set.Extensions[ext2].str) - - def testParseBadIdentifier(self): - message = unittest_pb2.TestAllTypes() - text = ('optional_nested_message { "bb": 1 }') - with self.assertRaises(text_format.ParseError) as e: - text_format.Parse(text, message) - self.assertEqual(str(e.exception), - '1:27 : \'optional_nested_message { "bb": 1 }\': ' - 'Expected identifier or number, got "bb".') - - def testParseBadExtension(self): - message = unittest_pb2.TestAllExtensions() - text = '[unknown_extension]: 8\n' - self.assertRaisesRegex( - text_format.ParseError, - '1:2 : Extension "unknown_extension" not registered.', - text_format.Parse, text, message) - message = unittest_pb2.TestAllTypes() - self.assertRaisesRegex( - text_format.ParseError, - ('1:2 : Message type "protobuf_unittest.TestAllTypes" does not have ' - 'extensions.'), text_format.Parse, text, message) - - def testParseNumericUnknownEnum(self): - message = unittest_pb2.TestAllTypes() - text = 'optional_nested_enum: 100' - self.assertRaisesRegex(text_format.ParseError, - (r'1:23 : \'optional_nested_enum: 100\': ' - r'Enum type "\w+.TestAllTypes.NestedEnum" ' - r'has no value with number 100.'), - text_format.Parse, text, message) - - def testMergeDuplicateExtensionScalars(self): - message = unittest_pb2.TestAllExtensions() - text = ('[protobuf_unittest.optional_int32_extension]: 42 ' - '[protobuf_unittest.optional_int32_extension]: 67') - text_format.Merge(text, message) - self.assertEqual(67, - message.Extensions[unittest_pb2.optional_int32_extension]) - - def testParseDuplicateExtensionScalars(self): - message = unittest_pb2.TestAllExtensions() - text = ('[protobuf_unittest.optional_int32_extension]: 42 ' - '[protobuf_unittest.optional_int32_extension]: 67') - self.assertRaisesRegex( - text_format.ParseError, - ('1:96 : Message type "protobuf_unittest.TestAllExtensions" ' - 'should not have multiple ' - '"protobuf_unittest.optional_int32_extension" extensions.'), - text_format.Parse, text, message) - - def testParseDuplicateExtensionMessages(self): - message = unittest_pb2.TestAllExtensions() - text = ('[protobuf_unittest.optional_nested_message_extension]: {} ' - '[protobuf_unittest.optional_nested_message_extension]: {}') - self.assertRaisesRegex( - text_format.ParseError, - ('1:114 : Message type "protobuf_unittest.TestAllExtensions" ' - 'should not have multiple ' - '"protobuf_unittest.optional_nested_message_extension" extensions.'), - text_format.Parse, text, message) - - def testParseGroupNotClosed(self): - message = unittest_pb2.TestAllTypes() - text = 'RepeatedGroup: <' - self.assertRaisesRegex(text_format.ParseError, '1:16 : Expected ">".', - text_format.Parse, text, message) - text = 'RepeatedGroup: {' - self.assertRaisesRegex(text_format.ParseError, '1:16 : Expected "}".', - text_format.Parse, text, message) - - def testParseEmptyGroup(self): - message = unittest_pb2.TestAllTypes() - text = 'OptionalGroup: {}' - text_format.Parse(text, message) - self.assertTrue(message.HasField('optionalgroup')) - - message.Clear() - - message = unittest_pb2.TestAllTypes() - text = 'OptionalGroup: <>' - text_format.Parse(text, message) - self.assertTrue(message.HasField('optionalgroup')) - - # Maps aren't really proto2-only, but our test schema only has maps for - # proto2. - def testParseMap(self): - text = ('map_int32_int32 {\n' - ' key: -123\n' - ' value: -456\n' - '}\n' - 'map_int64_int64 {\n' - ' key: -8589934592\n' - ' value: -17179869184\n' - '}\n' - 'map_uint32_uint32 {\n' - ' key: 123\n' - ' value: 456\n' - '}\n' - 'map_uint64_uint64 {\n' - ' key: 8589934592\n' - ' value: 17179869184\n' - '}\n' - 'map_string_string {\n' - ' key: "abc"\n' - ' value: "123"\n' - '}\n' - 'map_int32_foreign_message {\n' - ' key: 111\n' - ' value {\n' - ' c: 5\n' - ' }\n' - '}\n') - message = map_unittest_pb2.TestMap() - text_format.Parse(text, message) - - self.assertEqual(-456, message.map_int32_int32[-123]) - self.assertEqual(-2**34, message.map_int64_int64[-2**33]) - self.assertEqual(456, message.map_uint32_uint32[123]) - self.assertEqual(2**34, message.map_uint64_uint64[2**33]) - self.assertEqual('123', message.map_string_string['abc']) - self.assertEqual(5, message.map_int32_foreign_message[111].c) - - -class Proto3Tests(unittest.TestCase): - - def testPrintMessageExpandAny(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - self.assertEqual( - text_format.MessageToString(message, - descriptor_pool=descriptor_pool.Default()), - 'any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string"\n' - ' }\n' - '}\n') - - def testTopAnyMessage(self): - packed_msg = unittest_pb2.OneString() - msg = any_pb2.Any() - msg.Pack(packed_msg) - text = text_format.MessageToString(msg) - other_msg = text_format.Parse(text, any_pb2.Any()) - self.assertEqual(msg, other_msg) - - def testPrintMessageExpandAnyRepeated(self): - packed_message = unittest_pb2.OneString() - message = any_test_pb2.TestAny() - packed_message.data = 'string0' - message.repeated_any_value.add().Pack(packed_message) - packed_message.data = 'string1' - message.repeated_any_value.add().Pack(packed_message) - self.assertEqual( - text_format.MessageToString(message), - 'repeated_any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string0"\n' - ' }\n' - '}\n' - 'repeated_any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string1"\n' - ' }\n' - '}\n') - - def testPrintMessageExpandAnyDescriptorPoolMissingType(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - empty_pool = descriptor_pool.DescriptorPool() - self.assertEqual( - text_format.MessageToString(message, descriptor_pool=empty_pool), - 'any_value {\n' - ' type_url: "type.googleapis.com/protobuf_unittest.OneString"\n' - ' value: "\\n\\006string"\n' - '}\n') - - def testPrintMessageExpandAnyPointyBrackets(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - self.assertEqual( - text_format.MessageToString(message, - pointy_brackets=True), - 'any_value <\n' - ' [type.googleapis.com/protobuf_unittest.OneString] <\n' - ' data: "string"\n' - ' >\n' - '>\n') - - def testPrintMessageExpandAnyAsOneLine(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - self.assertEqual( - text_format.MessageToString(message, - as_one_line=True), - 'any_value {' - ' [type.googleapis.com/protobuf_unittest.OneString]' - ' { data: "string" } ' - '}') - - def testPrintMessageExpandAnyAsOneLinePointyBrackets(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - self.assertEqual( - text_format.MessageToString(message, - as_one_line=True, - pointy_brackets=True, - descriptor_pool=descriptor_pool.Default()), - 'any_value <' - ' [type.googleapis.com/protobuf_unittest.OneString]' - ' < data: "string" > ' - '>') - - def testPrintAndParseMessageInvalidAny(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - # Only include string after last '/' in type_url. - message.any_value.type_url = message.any_value.TypeName() - text = text_format.MessageToString(message) - self.assertEqual( - text, 'any_value {\n' - ' type_url: "protobuf_unittest.OneString"\n' - ' value: "\\n\\006string"\n' - '}\n') - - parsed_message = any_test_pb2.TestAny() - text_format.Parse(text, parsed_message) - self.assertEqual(message, parsed_message) - - def testUnknownEnums(self): - message = unittest_proto3_arena_pb2.TestAllTypes() - message2 = unittest_proto3_arena_pb2.TestAllTypes() - message.optional_nested_enum = 999 - text_string = text_format.MessageToString(message) - text_format.Parse(text_string, message2) - self.assertEqual(999, message2.optional_nested_enum) - - def testMergeExpandedAny(self): - message = any_test_pb2.TestAny() - text = ('any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string"\n' - ' }\n' - '}\n') - text_format.Merge(text, message) - packed_message = unittest_pb2.OneString() - message.any_value.Unpack(packed_message) - self.assertEqual('string', packed_message.data) - message.Clear() - text_format.Parse(text, message) - packed_message = unittest_pb2.OneString() - message.any_value.Unpack(packed_message) - self.assertEqual('string', packed_message.data) - - def testMergeExpandedAnyRepeated(self): - message = any_test_pb2.TestAny() - text = ('repeated_any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string0"\n' - ' }\n' - '}\n' - 'repeated_any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string1"\n' - ' }\n' - '}\n') - text_format.Merge(text, message) - packed_message = unittest_pb2.OneString() - message.repeated_any_value[0].Unpack(packed_message) - self.assertEqual('string0', packed_message.data) - message.repeated_any_value[1].Unpack(packed_message) - self.assertEqual('string1', packed_message.data) - - def testMergeExpandedAnyPointyBrackets(self): - message = any_test_pb2.TestAny() - text = ('any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] <\n' - ' data: "string"\n' - ' >\n' - '}\n') - text_format.Merge(text, message) - packed_message = unittest_pb2.OneString() - message.any_value.Unpack(packed_message) - self.assertEqual('string', packed_message.data) - - def testMergeAlternativeUrl(self): - message = any_test_pb2.TestAny() - text = ('any_value {\n' - ' [type.otherapi.com/protobuf_unittest.OneString] {\n' - ' data: "string"\n' - ' }\n' - '}\n') - text_format.Merge(text, message) - packed_message = unittest_pb2.OneString() - self.assertEqual('type.otherapi.com/protobuf_unittest.OneString', - message.any_value.type_url) - - def testMergeExpandedAnyDescriptorPoolMissingType(self): - message = any_test_pb2.TestAny() - text = ('any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string"\n' - ' }\n' - '}\n') - with self.assertRaises(text_format.ParseError) as e: - empty_pool = descriptor_pool.DescriptorPool() - text_format.Merge(text, message, descriptor_pool=empty_pool) - self.assertEqual( - str(e.exception), - 'Type protobuf_unittest.OneString not found in descriptor pool') - - def testMergeUnexpandedAny(self): - text = ('any_value {\n' - ' type_url: "type.googleapis.com/protobuf_unittest.OneString"\n' - ' value: "\\n\\006string"\n' - '}\n') - message = any_test_pb2.TestAny() - text_format.Merge(text, message) - packed_message = unittest_pb2.OneString() - message.any_value.Unpack(packed_message) - self.assertEqual('string', packed_message.data) - - def testMergeMissingAnyEndToken(self): - message = any_test_pb2.TestAny() - text = ('any_value {\n' - ' [type.googleapis.com/protobuf_unittest.OneString] {\n' - ' data: "string"\n') - with self.assertRaises(text_format.ParseError) as e: - text_format.Merge(text, message) - self.assertEqual(str(e.exception), '3:11 : Expected "}".') - - def testParseExpandedAnyListValue(self): - any_msg = any_pb2.Any() - any_msg.Pack(struct_pb2.ListValue()) - msg = any_test_pb2.TestAny(any_value=any_msg) - text = ('any_value {\n' - ' [type.googleapis.com/google.protobuf.ListValue] {}\n' - '}\n') - parsed_msg = text_format.Parse(text, any_test_pb2.TestAny()) - self.assertEqual(msg, parsed_msg) - - def testProto3Optional(self): - msg = test_proto3_optional_pb2.TestProto3Optional() - self.assertEqual(text_format.MessageToString(msg), '') - msg.optional_int32 = 0 - msg.optional_float = 0.0 - msg.optional_string = '' - msg.optional_nested_message.bb = 0 - text = ('optional_int32: 0\n' - 'optional_float: 0.0\n' - 'optional_string: ""\n' - 'optional_nested_message {\n' - ' bb: 0\n' - '}\n') - self.assertEqual(text_format.MessageToString(msg), text) - msg2 = test_proto3_optional_pb2.TestProto3Optional() - text_format.Parse(text, msg2) - self.assertEqual(text_format.MessageToString(msg2), text) - - -class TokenizerTest(unittest.TestCase): - - def testSimpleTokenCases(self): - text = ('identifier1:"string1"\n \n\n' - 'identifier2 : \n \n123 \n identifier3 :\'string\'\n' - 'identifiER_4 : 1.1e+2 ID5:-0.23 ID6:\'aaaa\\\'bbbb\'\n' - 'ID7 : "aa\\"bb"\n\n\n\n ID8: {A:inf B:-inf C:true D:false}\n' - 'ID9: 22 ID10: -111111111111111111 ID11: -22\n' - 'ID12: 2222222222222222222 ID13: 1.23456f ID14: 1.2e+2f ' - 'false_bool: 0 true_BOOL:t \n true_bool1: 1 false_BOOL1:f ' - 'False_bool: False True_bool: True X:iNf Y:-inF Z:nAN') - tokenizer = text_format.Tokenizer(text.splitlines()) - methods = [(tokenizer.ConsumeIdentifier, 'identifier1'), ':', - (tokenizer.ConsumeString, 'string1'), - (tokenizer.ConsumeIdentifier, 'identifier2'), ':', - (tokenizer.ConsumeInteger, 123), - (tokenizer.ConsumeIdentifier, 'identifier3'), ':', - (tokenizer.ConsumeString, 'string'), - (tokenizer.ConsumeIdentifier, 'identifiER_4'), ':', - (tokenizer.ConsumeFloat, 1.1e+2), - (tokenizer.ConsumeIdentifier, 'ID5'), ':', - (tokenizer.ConsumeFloat, -0.23), - (tokenizer.ConsumeIdentifier, 'ID6'), ':', - (tokenizer.ConsumeString, 'aaaa\'bbbb'), - (tokenizer.ConsumeIdentifier, 'ID7'), ':', - (tokenizer.ConsumeString, 'aa\"bb'), - (tokenizer.ConsumeIdentifier, 'ID8'), ':', '{', - (tokenizer.ConsumeIdentifier, 'A'), ':', - (tokenizer.ConsumeFloat, float('inf')), - (tokenizer.ConsumeIdentifier, 'B'), ':', - (tokenizer.ConsumeFloat, -float('inf')), - (tokenizer.ConsumeIdentifier, 'C'), ':', - (tokenizer.ConsumeBool, True), - (tokenizer.ConsumeIdentifier, 'D'), ':', - (tokenizer.ConsumeBool, False), '}', - (tokenizer.ConsumeIdentifier, 'ID9'), ':', - (tokenizer.ConsumeInteger, 22), - (tokenizer.ConsumeIdentifier, 'ID10'), ':', - (tokenizer.ConsumeInteger, -111111111111111111), - (tokenizer.ConsumeIdentifier, 'ID11'), ':', - (tokenizer.ConsumeInteger, -22), - (tokenizer.ConsumeIdentifier, 'ID12'), ':', - (tokenizer.ConsumeInteger, 2222222222222222222), - (tokenizer.ConsumeIdentifier, 'ID13'), ':', - (tokenizer.ConsumeFloat, 1.23456), - (tokenizer.ConsumeIdentifier, 'ID14'), ':', - (tokenizer.ConsumeFloat, 1.2e+2), - (tokenizer.ConsumeIdentifier, 'false_bool'), ':', - (tokenizer.ConsumeBool, False), - (tokenizer.ConsumeIdentifier, 'true_BOOL'), ':', - (tokenizer.ConsumeBool, True), - (tokenizer.ConsumeIdentifier, 'true_bool1'), ':', - (tokenizer.ConsumeBool, True), - (tokenizer.ConsumeIdentifier, 'false_BOOL1'), ':', - (tokenizer.ConsumeBool, False), - (tokenizer.ConsumeIdentifier, 'False_bool'), ':', - (tokenizer.ConsumeBool, False), - (tokenizer.ConsumeIdentifier, 'True_bool'), ':', - (tokenizer.ConsumeBool, True), - (tokenizer.ConsumeIdentifier, 'X'), ':', - (tokenizer.ConsumeFloat, float('inf')), - (tokenizer.ConsumeIdentifier, 'Y'), ':', - (tokenizer.ConsumeFloat, float('-inf')), - (tokenizer.ConsumeIdentifier, 'Z'), ':', - (tokenizer.ConsumeFloat, float('nan'))] - - i = 0 - while not tokenizer.AtEnd(): - m = methods[i] - if isinstance(m, str): - token = tokenizer.token - self.assertEqual(token, m) - tokenizer.NextToken() - elif isinstance(m[1], float) and math.isnan(m[1]): - self.assertTrue(math.isnan(m[0]())) - else: - self.assertEqual(m[1], m[0]()) - i += 1 - - def testConsumeAbstractIntegers(self): - # This test only tests the failures in the integer parsing methods as well - # as the '0' special cases. - int64_max = (1 << 63) - 1 - uint32_max = (1 << 32) - 1 - text = '-1 %d %d' % (uint32_max + 1, int64_max + 1) - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertEqual(-1, tokenizer.ConsumeInteger()) - - self.assertEqual(uint32_max + 1, tokenizer.ConsumeInteger()) - - self.assertEqual(int64_max + 1, tokenizer.ConsumeInteger()) - self.assertTrue(tokenizer.AtEnd()) - - text = '-0 0 0 1.2' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertEqual(0, tokenizer.ConsumeInteger()) - self.assertEqual(0, tokenizer.ConsumeInteger()) - self.assertEqual(True, tokenizer.TryConsumeInteger()) - self.assertEqual(False, tokenizer.TryConsumeInteger()) - with self.assertRaises(text_format.ParseError): - tokenizer.ConsumeInteger() - self.assertEqual(1.2, tokenizer.ConsumeFloat()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeIntegers(self): - # This test only tests the failures in the integer parsing methods as well - # as the '0' special cases. - int64_max = (1 << 63) - 1 - uint32_max = (1 << 32) - 1 - text = '-1 %d %d' % (uint32_max + 1, int64_max + 1) - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, - text_format._ConsumeUint32, tokenizer) - self.assertRaises(text_format.ParseError, - text_format._ConsumeUint64, tokenizer) - self.assertEqual(-1, text_format._ConsumeInt32(tokenizer)) - - self.assertRaises(text_format.ParseError, - text_format._ConsumeUint32, tokenizer) - self.assertRaises(text_format.ParseError, - text_format._ConsumeInt32, tokenizer) - self.assertEqual(uint32_max + 1, text_format._ConsumeInt64(tokenizer)) - - self.assertRaises(text_format.ParseError, - text_format._ConsumeInt64, tokenizer) - self.assertEqual(int64_max + 1, text_format._ConsumeUint64(tokenizer)) - self.assertTrue(tokenizer.AtEnd()) - - text = '-0 -0 0 0' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertEqual(0, text_format._ConsumeUint32(tokenizer)) - self.assertEqual(0, text_format._ConsumeUint64(tokenizer)) - self.assertEqual(0, text_format._ConsumeUint32(tokenizer)) - self.assertEqual(0, text_format._ConsumeUint64(tokenizer)) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeOctalIntegers(self): - """Test support for C style octal integers.""" - text = '00 -00 04 0755 -010 007 -0033 08 -09 01' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertEqual(0, tokenizer.ConsumeInteger()) - self.assertEqual(0, tokenizer.ConsumeInteger()) - self.assertEqual(4, tokenizer.ConsumeInteger()) - self.assertEqual(0o755, tokenizer.ConsumeInteger()) - self.assertEqual(-0o10, tokenizer.ConsumeInteger()) - self.assertEqual(7, tokenizer.ConsumeInteger()) - self.assertEqual(-0o033, tokenizer.ConsumeInteger()) - with self.assertRaises(text_format.ParseError): - tokenizer.ConsumeInteger() # 08 - tokenizer.NextToken() - with self.assertRaises(text_format.ParseError): - tokenizer.ConsumeInteger() # -09 - tokenizer.NextToken() - self.assertEqual(1, tokenizer.ConsumeInteger()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeByteString(self): - text = '"string1\'' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString) - - text = 'string1"' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString) - - text = '\n"\\xt"' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString) - - text = '\n"\\"' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString) - - text = '\n"\\x"' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString) - - def testConsumeBool(self): - text = 'not-a-bool' - tokenizer = text_format.Tokenizer(text.splitlines()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeBool) - - def testSkipComment(self): - tokenizer = text_format.Tokenizer('# some comment'.splitlines()) - self.assertTrue(tokenizer.AtEnd()) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment) - - def testConsumeComment(self): - tokenizer = text_format.Tokenizer('# some comment'.splitlines(), - skip_comments=False) - self.assertFalse(tokenizer.AtEnd()) - self.assertEqual('# some comment', tokenizer.ConsumeComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeTwoComments(self): - text = '# some comment\n# another comment' - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - self.assertEqual('# some comment', tokenizer.ConsumeComment()) - self.assertFalse(tokenizer.AtEnd()) - self.assertEqual('# another comment', tokenizer.ConsumeComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeTrailingComment(self): - text = 'some_number: 4\n# some comment' - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment) - - self.assertEqual('some_number', tokenizer.ConsumeIdentifier()) - self.assertEqual(tokenizer.token, ':') - tokenizer.NextToken() - self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment) - self.assertEqual(4, tokenizer.ConsumeInteger()) - self.assertFalse(tokenizer.AtEnd()) - - self.assertEqual('# some comment', tokenizer.ConsumeComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeLineComment(self): - tokenizer = text_format.Tokenizer('# some comment'.splitlines(), - skip_comments=False) - self.assertFalse(tokenizer.AtEnd()) - self.assertEqual((False, '# some comment'), - tokenizer.ConsumeCommentOrTrailingComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeTwoLineComments(self): - text = '# some comment\n# another comment' - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - self.assertEqual((False, '# some comment'), - tokenizer.ConsumeCommentOrTrailingComment()) - self.assertFalse(tokenizer.AtEnd()) - self.assertEqual((False, '# another comment'), - tokenizer.ConsumeCommentOrTrailingComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testConsumeAndCheckTrailingComment(self): - text = 'some_number: 4 # some comment' # trailing comment on the same line - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - self.assertRaises(text_format.ParseError, - tokenizer.ConsumeCommentOrTrailingComment) - - self.assertEqual('some_number', tokenizer.ConsumeIdentifier()) - self.assertEqual(tokenizer.token, ':') - tokenizer.NextToken() - self.assertRaises(text_format.ParseError, - tokenizer.ConsumeCommentOrTrailingComment) - self.assertEqual(4, tokenizer.ConsumeInteger()) - self.assertFalse(tokenizer.AtEnd()) - - self.assertEqual((True, '# some comment'), - tokenizer.ConsumeCommentOrTrailingComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testHashinComment(self): - text = 'some_number: 4 # some comment # not a new comment' - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - self.assertEqual('some_number', tokenizer.ConsumeIdentifier()) - self.assertEqual(tokenizer.token, ':') - tokenizer.NextToken() - self.assertEqual(4, tokenizer.ConsumeInteger()) - self.assertEqual((True, '# some comment # not a new comment'), - tokenizer.ConsumeCommentOrTrailingComment()) - self.assertTrue(tokenizer.AtEnd()) - - def testHugeString(self): - # With pathologic backtracking, fails with Forge OOM. - text = '"' + 'a' * (10 * 1024 * 1024) + '"' - tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False) - tokenizer.ConsumeString() - - -# Tests for pretty printer functionality. -@_parameterized.parameters((unittest_pb2), (unittest_proto3_arena_pb2)) -class PrettyPrinterTest(TextFormatBase): - - def testPrettyPrintNoMatch(self, message_module): - - def printer(message, indent, as_one_line): - del message, indent, as_one_line - return None - - message = message_module.TestAllTypes() - msg = message.repeated_nested_message.add() - msg.bb = 42 - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=True, message_formatter=printer), - 'repeated_nested_message { bb: 42 }') - - def testPrettyPrintOneLine(self, message_module): - - def printer(m, indent, as_one_line): - del indent, as_one_line - if m.DESCRIPTOR == message_module.TestAllTypes.NestedMessage.DESCRIPTOR: - return 'My lucky number is %s' % m.bb - - message = message_module.TestAllTypes() - msg = message.repeated_nested_message.add() - msg.bb = 42 - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=True, message_formatter=printer), - 'repeated_nested_message { My lucky number is 42 }') - - def testPrettyPrintMultiLine(self, message_module): - - def printer(m, indent, as_one_line): - if m.DESCRIPTOR == message_module.TestAllTypes.NestedMessage.DESCRIPTOR: - line_deliminator = (' ' if as_one_line else '\n') + ' ' * indent - return 'My lucky number is:%s%s' % (line_deliminator, m.bb) - return None - - message = message_module.TestAllTypes() - msg = message.repeated_nested_message.add() - msg.bb = 42 - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=True, message_formatter=printer), - 'repeated_nested_message { My lucky number is: 42 }') - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=False, message_formatter=printer), - 'repeated_nested_message {\n My lucky number is:\n 42\n}\n') - - def testPrettyPrintEntireMessage(self, message_module): - - def printer(m, indent, as_one_line): - del indent, as_one_line - if m.DESCRIPTOR == message_module.TestAllTypes.DESCRIPTOR: - return 'The is the message!' - return None - - message = message_module.TestAllTypes() - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=False, message_formatter=printer), - 'The is the message!\n') - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=True, message_formatter=printer), - 'The is the message!') - - def testPrettyPrintMultipleParts(self, message_module): - - def printer(m, indent, as_one_line): - del indent, as_one_line - if m.DESCRIPTOR == message_module.TestAllTypes.NestedMessage.DESCRIPTOR: - return 'My lucky number is %s' % m.bb - return None - - message = message_module.TestAllTypes() - message.optional_int32 = 61 - msg = message.repeated_nested_message.add() - msg.bb = 42 - msg = message.repeated_nested_message.add() - msg.bb = 99 - msg = message.optional_nested_message - msg.bb = 1 - self.CompareToGoldenText( - text_format.MessageToString( - message, as_one_line=True, message_formatter=printer), - ('optional_int32: 61 ' - 'optional_nested_message { My lucky number is 1 } ' - 'repeated_nested_message { My lucky number is 42 } ' - 'repeated_nested_message { My lucky number is 99 }')) - - out = text_format.TextWriter(False) - text_format.PrintField( - message_module.TestAllTypes.DESCRIPTOR.fields_by_name[ - 'optional_nested_message'], - message.optional_nested_message, - out, - message_formatter=printer) - self.assertEqual( - 'optional_nested_message {\n My lucky number is 1\n}\n', - out.getvalue()) - out.close() - - out = text_format.TextWriter(False) - text_format.PrintFieldValue( - message_module.TestAllTypes.DESCRIPTOR.fields_by_name[ - 'optional_nested_message'], - message.optional_nested_message, - out, - message_formatter=printer) - self.assertEqual( - '{\n My lucky number is 1\n}', - out.getvalue()) - out.close() - - -class WhitespaceTest(TextFormatBase): - - def setUp(self): - self.out = text_format.TextWriter(False) - self.addCleanup(self.out.close) - self.message = unittest_pb2.NestedTestAllTypes() - self.message.child.payload.optional_string = 'value' - self.field = self.message.DESCRIPTOR.fields_by_name['child'] - self.value = self.message.child - - def testMessageToString(self): - self.CompareToGoldenText( - text_format.MessageToString(self.message), - textwrap.dedent("""\ - child { - payload { - optional_string: "value" - } - } - """)) - - def testPrintMessage(self): - text_format.PrintMessage(self.message, self.out) - self.CompareToGoldenText( - self.out.getvalue(), - textwrap.dedent("""\ - child { - payload { - optional_string: "value" - } - } - """)) - - def testPrintField(self): - text_format.PrintField(self.field, self.value, self.out) - self.CompareToGoldenText( - self.out.getvalue(), - textwrap.dedent("""\ - child { - payload { - optional_string: "value" - } - } - """)) - - def testPrintFieldValue(self): - text_format.PrintFieldValue( - self.field, self.value, self.out) - self.CompareToGoldenText( - self.out.getvalue(), - textwrap.dedent("""\ - { - payload { - optional_string: "value" - } - }""")) - - -class OptionalColonMessageToStringTest(unittest.TestCase): - - def testForcePrintOptionalColon(self): - packed_message = unittest_pb2.OneString() - packed_message.data = 'string' - message = any_test_pb2.TestAny() - message.any_value.Pack(packed_message) - output = text_format.MessageToString( - message, - force_colon=True) - expected = ('any_value: {\n' - ' [type.googleapis.com/protobuf_unittest.OneString]: {\n' - ' data: "string"\n' - ' }\n' - '}\n') - self.assertEqual(expected, output) - - def testPrintShortFormatRepeatedFields(self): - message = unittest_pb2.TestAllTypes() - message.repeated_int32.append(1) - output = text_format.MessageToString( - message, use_short_repeated_primitives=True, force_colon=True) - self.assertEqual('repeated_int32: [1]\n', output) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/type_checkers.py b/ext/protobuf/Python/google/protobuf/internal/type_checkers.py index a53e71fe8..165dcd8c2 100644 --- a/ext/protobuf/Python/google/protobuf/internal/type_checkers.py +++ b/ext/protobuf/Python/google/protobuf/internal/type_checkers.py @@ -75,10 +75,6 @@ def ToShortestFloat(original): return rounded -def SupportsOpenEnums(field_descriptor): - return field_descriptor.containing_type.syntax == 'proto3' - - def GetTypeChecker(field): """Returns a type checker for a message field of the specified types. @@ -93,11 +89,11 @@ def GetTypeChecker(field): field.type == _FieldDescriptor.TYPE_STRING): return UnicodeValueChecker() if field.cpp_type == _FieldDescriptor.CPPTYPE_ENUM: - if SupportsOpenEnums(field): + if field.enum_type.is_closed: + return EnumValueChecker(field.enum_type) + else: # When open enums are supported, any int32 can be assigned. return _VALUE_CHECKERS[_FieldDescriptor.CPPTYPE_INT32] - else: - return EnumValueChecker(field.enum_type) return _VALUE_CHECKERS[field.cpp_type] diff --git a/ext/protobuf/Python/google/protobuf/internal/unknown_fields_test.py b/ext/protobuf/Python/google/protobuf/internal/unknown_fields_test.py deleted file mode 100644 index 64a036782..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/unknown_fields_test.py +++ /dev/null @@ -1,461 +0,0 @@ -# -*- coding: utf-8 -*- -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Test for preservation of unknown fields in the pure Python implementation.""" - -__author__ = 'bohdank@google.com (Bohdan Koval)' - -import sys -import unittest - -from google.protobuf import map_unittest_pb2 -from google.protobuf import unittest_mset_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf import unittest_proto3_arena_pb2 -from google.protobuf.internal import api_implementation -from google.protobuf.internal import encoder -from google.protobuf.internal import message_set_extensions_pb2 -from google.protobuf.internal import missing_enum_values_pb2 -from google.protobuf.internal import test_util -from google.protobuf.internal import testing_refleaks -from google.protobuf.internal import type_checkers -from google.protobuf.internal import wire_format -from google.protobuf import descriptor -from google.protobuf import unknown_fields -try: - import tracemalloc # pylint: disable=g-import-not-at-top -except ImportError: - # Requires python 3.4+ - pass - - -@testing_refleaks.TestCase -class UnknownFieldsTest(unittest.TestCase): - - def setUp(self): - self.descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - self.all_fields = unittest_pb2.TestAllTypes() - test_util.SetAllFields(self.all_fields) - self.all_fields_data = self.all_fields.SerializeToString() - self.empty_message = unittest_pb2.TestEmptyMessage() - self.empty_message.ParseFromString(self.all_fields_data) - - def testSerialize(self): - data = self.empty_message.SerializeToString() - - # Don't use assertEqual because we don't want to dump raw binary data to - # stdout. - self.assertTrue(data == self.all_fields_data) - - def testSerializeProto3(self): - # Verify proto3 unknown fields behavior. - message = unittest_proto3_arena_pb2.TestEmptyMessage() - message.ParseFromString(self.all_fields_data) - self.assertEqual(self.all_fields_data, message.SerializeToString()) - - def testByteSize(self): - self.assertEqual(self.all_fields.ByteSize(), self.empty_message.ByteSize()) - - def testListFields(self): - # Make sure ListFields doesn't return unknown fields. - self.assertEqual(0, len(self.empty_message.ListFields())) - - def testSerializeMessageSetWireFormatUnknownExtension(self): - # Create a message using the message set wire format with an unknown - # message. - raw = unittest_mset_pb2.RawMessageSet() - - # Add an unknown extension. - item = raw.item.add() - item.type_id = 98218603 - message1 = message_set_extensions_pb2.TestMessageSetExtension1() - message1.i = 12345 - item.message = message1.SerializeToString() - - serialized = raw.SerializeToString() - - # Parse message using the message set wire format. - proto = message_set_extensions_pb2.TestMessageSet() - proto.MergeFromString(serialized) - - unknown_field_set = unknown_fields.UnknownFieldSet(proto) - self.assertEqual(len(unknown_field_set), 1) - # Unknown field should have wire format data which can be parsed back to - # original message. - self.assertEqual(unknown_field_set[0].field_number, item.type_id) - self.assertEqual(unknown_field_set[0].wire_type, - wire_format.WIRETYPE_LENGTH_DELIMITED) - d = unknown_field_set[0].data - message_new = message_set_extensions_pb2.TestMessageSetExtension1() - message_new.ParseFromString(d) - self.assertEqual(message1, message_new) - - # Verify that the unknown extension is serialized unchanged - reserialized = proto.SerializeToString() - new_raw = unittest_mset_pb2.RawMessageSet() - new_raw.MergeFromString(reserialized) - self.assertEqual(raw, new_raw) - - def testEquals(self): - message = unittest_pb2.TestEmptyMessage() - message.ParseFromString(self.all_fields_data) - self.assertEqual(self.empty_message, message) - - self.all_fields.ClearField('optional_string') - message.ParseFromString(self.all_fields.SerializeToString()) - self.assertNotEqual(self.empty_message, message) - - def testDiscardUnknownFields(self): - self.empty_message.DiscardUnknownFields() - self.assertEqual(b'', self.empty_message.SerializeToString()) - # Test message field and repeated message field. - message = unittest_pb2.TestAllTypes() - other_message = unittest_pb2.TestAllTypes() - other_message.optional_string = 'discard' - message.optional_nested_message.ParseFromString( - other_message.SerializeToString()) - message.repeated_nested_message.add().ParseFromString( - other_message.SerializeToString()) - self.assertNotEqual( - b'', message.optional_nested_message.SerializeToString()) - self.assertNotEqual( - b'', message.repeated_nested_message[0].SerializeToString()) - message.DiscardUnknownFields() - self.assertEqual(b'', message.optional_nested_message.SerializeToString()) - self.assertEqual( - b'', message.repeated_nested_message[0].SerializeToString()) - - msg = map_unittest_pb2.TestMap() - msg.map_int32_all_types[1].optional_nested_message.ParseFromString( - other_message.SerializeToString()) - msg.map_string_string['1'] = 'test' - self.assertNotEqual( - b'', - msg.map_int32_all_types[1].optional_nested_message.SerializeToString()) - msg.DiscardUnknownFields() - self.assertEqual( - b'', - msg.map_int32_all_types[1].optional_nested_message.SerializeToString()) - - -@testing_refleaks.TestCase -class UnknownFieldsAccessorsTest(unittest.TestCase): - - def setUp(self): - self.descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - self.all_fields = unittest_pb2.TestAllTypes() - test_util.SetAllFields(self.all_fields) - self.all_fields_data = self.all_fields.SerializeToString() - self.empty_message = unittest_pb2.TestEmptyMessage() - self.empty_message.ParseFromString(self.all_fields_data) - - # InternalCheckUnknownField() is an additional Pure Python check which checks - # a detail of unknown fields. It cannot be used by the C++ - # implementation because some protect members are called. - # The test is added for historical reasons. It is not necessary as - # serialized string is checked. - # TODO(jieluo): Remove message._unknown_fields. - def InternalCheckUnknownField(self, name, expected_value): - if api_implementation.Type() != 'python': - return - field_descriptor = self.descriptor.fields_by_name[name] - wire_type = type_checkers.FIELD_TYPE_TO_WIRE_TYPE[field_descriptor.type] - field_tag = encoder.TagBytes(field_descriptor.number, wire_type) - result_dict = {} - for tag_bytes, value in self.empty_message._unknown_fields: - if tag_bytes == field_tag: - decoder = unittest_pb2.TestAllTypes._decoders_by_tag[tag_bytes][0] - decoder(memoryview(value), 0, len(value), self.all_fields, result_dict) - self.assertEqual(expected_value, result_dict[field_descriptor]) - - def CheckUnknownField(self, name, unknown_field_set, expected_value): - field_descriptor = self.descriptor.fields_by_name[name] - expected_type = type_checkers.FIELD_TYPE_TO_WIRE_TYPE[ - field_descriptor.type] - for unknown_field in unknown_field_set: - if unknown_field.field_number == field_descriptor.number: - self.assertEqual(expected_type, unknown_field.wire_type) - if expected_type == 3: - # Check group - self.assertEqual(expected_value[0], - unknown_field.data[0].field_number) - self.assertEqual(expected_value[1], unknown_field.data[0].wire_type) - self.assertEqual(expected_value[2], unknown_field.data[0].data) - continue - if expected_type == wire_format.WIRETYPE_LENGTH_DELIMITED: - self.assertIn(type(unknown_field.data), (str, bytes)) - if field_descriptor.label == descriptor.FieldDescriptor.LABEL_REPEATED: - self.assertIn(unknown_field.data, expected_value) - else: - self.assertEqual(expected_value, unknown_field.data) - - def testCheckUnknownFieldValue(self): - unknown_field_set = unknown_fields.UnknownFieldSet(self.empty_message) - # Test enum. - self.CheckUnknownField('optional_nested_enum', - unknown_field_set, - self.all_fields.optional_nested_enum) - self.InternalCheckUnknownField('optional_nested_enum', - self.all_fields.optional_nested_enum) - - # Test repeated enum. - self.CheckUnknownField('repeated_nested_enum', - unknown_field_set, - self.all_fields.repeated_nested_enum) - self.InternalCheckUnknownField('repeated_nested_enum', - self.all_fields.repeated_nested_enum) - - # Test varint. - self.CheckUnknownField('optional_int32', - unknown_field_set, - self.all_fields.optional_int32) - self.InternalCheckUnknownField('optional_int32', - self.all_fields.optional_int32) - - # Test fixed32. - self.CheckUnknownField('optional_fixed32', - unknown_field_set, - self.all_fields.optional_fixed32) - self.InternalCheckUnknownField('optional_fixed32', - self.all_fields.optional_fixed32) - - # Test fixed64. - self.CheckUnknownField('optional_fixed64', - unknown_field_set, - self.all_fields.optional_fixed64) - self.InternalCheckUnknownField('optional_fixed64', - self.all_fields.optional_fixed64) - - # Test length delimited. - self.CheckUnknownField('optional_string', - unknown_field_set, - self.all_fields.optional_string.encode('utf-8')) - self.InternalCheckUnknownField('optional_string', - self.all_fields.optional_string) - - # Test group. - self.CheckUnknownField('optionalgroup', - unknown_field_set, - (17, 0, 117)) - self.InternalCheckUnknownField('optionalgroup', - self.all_fields.optionalgroup) - - self.assertEqual(98, len(unknown_field_set)) - - def testCopyFrom(self): - message = unittest_pb2.TestEmptyMessage() - message.CopyFrom(self.empty_message) - self.assertEqual(message.SerializeToString(), self.all_fields_data) - - def testMergeFrom(self): - message = unittest_pb2.TestAllTypes() - message.optional_int32 = 1 - message.optional_uint32 = 2 - source = unittest_pb2.TestEmptyMessage() - source.ParseFromString(message.SerializeToString()) - - message.ClearField('optional_int32') - message.optional_int64 = 3 - message.optional_uint32 = 4 - destination = unittest_pb2.TestEmptyMessage() - unknown_field_set = unknown_fields.UnknownFieldSet(destination) - self.assertEqual(0, len(unknown_field_set)) - destination.ParseFromString(message.SerializeToString()) - self.assertEqual(0, len(unknown_field_set)) - unknown_field_set = unknown_fields.UnknownFieldSet(destination) - self.assertEqual(2, len(unknown_field_set)) - destination.MergeFrom(source) - self.assertEqual(2, len(unknown_field_set)) - # Check that the fields where correctly merged, even stored in the unknown - # fields set. - message.ParseFromString(destination.SerializeToString()) - self.assertEqual(message.optional_int32, 1) - self.assertEqual(message.optional_uint32, 2) - self.assertEqual(message.optional_int64, 3) - - def testClear(self): - unknown_field_set = unknown_fields.UnknownFieldSet(self.empty_message) - self.empty_message.Clear() - # All cleared, even unknown fields. - self.assertEqual(self.empty_message.SerializeToString(), b'') - self.assertEqual(len(unknown_field_set), 98) - - @unittest.skipIf((sys.version_info.major, sys.version_info.minor) < (3, 4), - 'tracemalloc requires python 3.4+') - def testUnknownFieldsNoMemoryLeak(self): - # Call to UnknownFields must not leak memory - nb_leaks = 1234 - - def leaking_function(): - for _ in range(nb_leaks): - unknown_fields.UnknownFieldSet(self.empty_message) - - tracemalloc.start() - snapshot1 = tracemalloc.take_snapshot() - leaking_function() - snapshot2 = tracemalloc.take_snapshot() - top_stats = snapshot2.compare_to(snapshot1, 'lineno') - tracemalloc.stop() - # There's no easy way to look for a precise leak source. - # Rely on a "marker" count value while checking allocated memory. - self.assertEqual([], [x for x in top_stats if x.count_diff == nb_leaks]) - - def testSubUnknownFields(self): - message = unittest_pb2.TestAllTypes() - message.optionalgroup.a = 123 - destination = unittest_pb2.TestEmptyMessage() - destination.ParseFromString(message.SerializeToString()) - sub_unknown_fields = unknown_fields.UnknownFieldSet(destination)[0].data - self.assertEqual(1, len(sub_unknown_fields)) - self.assertEqual(sub_unknown_fields[0].data, 123) - destination.Clear() - self.assertEqual(1, len(sub_unknown_fields)) - self.assertEqual(sub_unknown_fields[0].data, 123) - message.Clear() - message.optional_uint32 = 456 - nested_message = unittest_pb2.NestedTestAllTypes() - nested_message.payload.optional_nested_message.ParseFromString( - message.SerializeToString()) - unknown_field_set = unknown_fields.UnknownFieldSet( - nested_message.payload.optional_nested_message) - self.assertEqual(unknown_field_set[0].data, 456) - nested_message.ClearField('payload') - self.assertEqual(unknown_field_set[0].data, 456) - unknown_field_set = unknown_fields.UnknownFieldSet( - nested_message.payload.optional_nested_message) - self.assertEqual(0, len(unknown_field_set)) - - def testUnknownField(self): - message = unittest_pb2.TestAllTypes() - message.optional_int32 = 123 - destination = unittest_pb2.TestEmptyMessage() - destination.ParseFromString(message.SerializeToString()) - unknown_field = unknown_fields.UnknownFieldSet(destination)[0] - destination.Clear() - self.assertEqual(unknown_field.data, 123) - - def testUnknownExtensions(self): - message = unittest_pb2.TestEmptyMessageWithExtensions() - message.ParseFromString(self.all_fields_data) - self.assertEqual(len(unknown_fields.UnknownFieldSet(message)), 98) - self.assertEqual(message.SerializeToString(), self.all_fields_data) - - -@testing_refleaks.TestCase -class UnknownEnumValuesTest(unittest.TestCase): - - def setUp(self): - self.descriptor = missing_enum_values_pb2.TestEnumValues.DESCRIPTOR - - self.message = missing_enum_values_pb2.TestEnumValues() - # TestEnumValues.ZERO = 0, but does not exist in the other NestedEnum. - self.message.optional_nested_enum = ( - missing_enum_values_pb2.TestEnumValues.ZERO) - self.message.repeated_nested_enum.extend([ - missing_enum_values_pb2.TestEnumValues.ZERO, - missing_enum_values_pb2.TestEnumValues.ONE, - ]) - self.message.packed_nested_enum.extend([ - missing_enum_values_pb2.TestEnumValues.ZERO, - missing_enum_values_pb2.TestEnumValues.ONE, - ]) - self.message_data = self.message.SerializeToString() - self.missing_message = missing_enum_values_pb2.TestMissingEnumValues() - self.missing_message.ParseFromString(self.message_data) - - # CheckUnknownField() is an additional Pure Python check which checks - # a detail of unknown fields. It cannot be used by the C++ - # implementation because some protect members are called. - # The test is added for historical reasons. It is not necessary as - # serialized string is checked. - - def CheckUnknownField(self, name, expected_value): - field_descriptor = self.descriptor.fields_by_name[name] - unknown_field_set = unknown_fields.UnknownFieldSet(self.missing_message) - self.assertIsInstance(unknown_field_set, unknown_fields.UnknownFieldSet) - count = 0 - for field in unknown_field_set: - if field.field_number == field_descriptor.number: - count += 1 - if field_descriptor.label == descriptor.FieldDescriptor.LABEL_REPEATED: - self.assertIn(field.data, expected_value) - else: - self.assertEqual(expected_value, field.data) - if field_descriptor.label == descriptor.FieldDescriptor.LABEL_REPEATED: - self.assertEqual(count, len(expected_value)) - else: - self.assertEqual(count, 1) - - def testUnknownParseMismatchEnumValue(self): - just_string = missing_enum_values_pb2.JustString() - just_string.dummy = 'blah' - - missing = missing_enum_values_pb2.TestEnumValues() - # The parse is invalid, storing the string proto into the set of - # unknown fields. - missing.ParseFromString(just_string.SerializeToString()) - - # Fetching the enum field shouldn't crash, instead returning the - # default value. - self.assertEqual(missing.optional_nested_enum, 0) - - def testUnknownEnumValue(self): - self.assertFalse(self.missing_message.HasField('optional_nested_enum')) - self.assertEqual(self.missing_message.optional_nested_enum, 2) - # Clear does not do anything. - serialized = self.missing_message.SerializeToString() - self.missing_message.ClearField('optional_nested_enum') - self.assertEqual(self.missing_message.SerializeToString(), serialized) - - def testUnknownRepeatedEnumValue(self): - self.assertEqual([], self.missing_message.repeated_nested_enum) - - def testUnknownPackedEnumValue(self): - self.assertEqual([], self.missing_message.packed_nested_enum) - - def testCheckUnknownFieldValueForEnum(self): - unknown_field_set = unknown_fields.UnknownFieldSet(self.missing_message) - self.assertEqual(len(unknown_field_set), 5) - self.CheckUnknownField('optional_nested_enum', - self.message.optional_nested_enum) - self.CheckUnknownField('repeated_nested_enum', - self.message.repeated_nested_enum) - self.CheckUnknownField('packed_nested_enum', - self.message.packed_nested_enum) - - def testRoundTrip(self): - new_message = missing_enum_values_pb2.TestEnumValues() - new_message.ParseFromString(self.missing_message.SerializeToString()) - self.assertEqual(self.message, new_message) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/well_known_types.py b/ext/protobuf/Python/google/protobuf/internal/well_known_types.py index 8881d758a..e340f9087 100644 --- a/ext/protobuf/Python/google/protobuf/internal/well_known_types.py +++ b/ext/protobuf/Python/google/protobuf/internal/well_known_types.py @@ -44,7 +44,9 @@ import collections.abc import datetime -from google.protobuf.descriptor import FieldDescriptor +from google.protobuf.internal import field_mask + +FieldMask = field_mask.FieldMask _TIMESTAMPFOMAT = '%Y-%m-%dT%H:%M:%S' _NANOS_PER_SECOND = 1000000000 @@ -430,306 +432,6 @@ def _RoundTowardZero(value, divider): return result -class FieldMask(object): - """Class for FieldMask message type.""" - - __slots__ = () - - def ToJsonString(self): - """Converts FieldMask to string according to proto3 JSON spec.""" - camelcase_paths = [] - for path in self.paths: - camelcase_paths.append(_SnakeCaseToCamelCase(path)) - return ','.join(camelcase_paths) - - def FromJsonString(self, value): - """Converts string to FieldMask according to proto3 JSON spec.""" - if not isinstance(value, str): - raise ValueError('FieldMask JSON value not a string: {!r}'.format(value)) - self.Clear() - if value: - for path in value.split(','): - self.paths.append(_CamelCaseToSnakeCase(path)) - - def IsValidForDescriptor(self, message_descriptor): - """Checks whether the FieldMask is valid for Message Descriptor.""" - for path in self.paths: - if not _IsValidPath(message_descriptor, path): - return False - return True - - def AllFieldsFromDescriptor(self, message_descriptor): - """Gets all direct fields of Message Descriptor to FieldMask.""" - self.Clear() - for field in message_descriptor.fields: - self.paths.append(field.name) - - def CanonicalFormFromMask(self, mask): - """Converts a FieldMask to the canonical form. - - Removes paths that are covered by another path. For example, - "foo.bar" is covered by "foo" and will be removed if "foo" - is also in the FieldMask. Then sorts all paths in alphabetical order. - - Args: - mask: The original FieldMask to be converted. - """ - tree = _FieldMaskTree(mask) - tree.ToFieldMask(self) - - def Union(self, mask1, mask2): - """Merges mask1 and mask2 into this FieldMask.""" - _CheckFieldMaskMessage(mask1) - _CheckFieldMaskMessage(mask2) - tree = _FieldMaskTree(mask1) - tree.MergeFromFieldMask(mask2) - tree.ToFieldMask(self) - - def Intersect(self, mask1, mask2): - """Intersects mask1 and mask2 into this FieldMask.""" - _CheckFieldMaskMessage(mask1) - _CheckFieldMaskMessage(mask2) - tree = _FieldMaskTree(mask1) - intersection = _FieldMaskTree() - for path in mask2.paths: - tree.IntersectPath(path, intersection) - intersection.ToFieldMask(self) - - def MergeMessage( - self, source, destination, - replace_message_field=False, replace_repeated_field=False): - """Merges fields specified in FieldMask from source to destination. - - Args: - source: Source message. - destination: The destination message to be merged into. - replace_message_field: Replace message field if True. Merge message - field if False. - replace_repeated_field: Replace repeated field if True. Append - elements of repeated field if False. - """ - tree = _FieldMaskTree(self) - tree.MergeMessage( - source, destination, replace_message_field, replace_repeated_field) - - -def _IsValidPath(message_descriptor, path): - """Checks whether the path is valid for Message Descriptor.""" - parts = path.split('.') - last = parts.pop() - for name in parts: - field = message_descriptor.fields_by_name.get(name) - if (field is None or - field.label == FieldDescriptor.LABEL_REPEATED or - field.type != FieldDescriptor.TYPE_MESSAGE): - return False - message_descriptor = field.message_type - return last in message_descriptor.fields_by_name - - -def _CheckFieldMaskMessage(message): - """Raises ValueError if message is not a FieldMask.""" - message_descriptor = message.DESCRIPTOR - if (message_descriptor.name != 'FieldMask' or - message_descriptor.file.name != 'google/protobuf/field_mask.proto'): - raise ValueError('Message {0} is not a FieldMask.'.format( - message_descriptor.full_name)) - - -def _SnakeCaseToCamelCase(path_name): - """Converts a path name from snake_case to camelCase.""" - result = [] - after_underscore = False - for c in path_name: - if c.isupper(): - raise ValueError( - 'Fail to print FieldMask to Json string: Path name ' - '{0} must not contain uppercase letters.'.format(path_name)) - if after_underscore: - if c.islower(): - result.append(c.upper()) - after_underscore = False - else: - raise ValueError( - 'Fail to print FieldMask to Json string: The ' - 'character after a "_" must be a lowercase letter ' - 'in path name {0}.'.format(path_name)) - elif c == '_': - after_underscore = True - else: - result += c - - if after_underscore: - raise ValueError('Fail to print FieldMask to Json string: Trailing "_" ' - 'in path name {0}.'.format(path_name)) - return ''.join(result) - - -def _CamelCaseToSnakeCase(path_name): - """Converts a field name from camelCase to snake_case.""" - result = [] - for c in path_name: - if c == '_': - raise ValueError('Fail to parse FieldMask: Path name ' - '{0} must not contain "_"s.'.format(path_name)) - if c.isupper(): - result += '_' - result += c.lower() - else: - result += c - return ''.join(result) - - -class _FieldMaskTree(object): - """Represents a FieldMask in a tree structure. - - For example, given a FieldMask "foo.bar,foo.baz,bar.baz", - the FieldMaskTree will be: - [_root] -+- foo -+- bar - | | - | +- baz - | - +- bar --- baz - In the tree, each leaf node represents a field path. - """ - - __slots__ = ('_root',) - - def __init__(self, field_mask=None): - """Initializes the tree by FieldMask.""" - self._root = {} - if field_mask: - self.MergeFromFieldMask(field_mask) - - def MergeFromFieldMask(self, field_mask): - """Merges a FieldMask to the tree.""" - for path in field_mask.paths: - self.AddPath(path) - - def AddPath(self, path): - """Adds a field path into the tree. - - If the field path to add is a sub-path of an existing field path - in the tree (i.e., a leaf node), it means the tree already matches - the given path so nothing will be added to the tree. If the path - matches an existing non-leaf node in the tree, that non-leaf node - will be turned into a leaf node with all its children removed because - the path matches all the node's children. Otherwise, a new path will - be added. - - Args: - path: The field path to add. - """ - node = self._root - for name in path.split('.'): - if name not in node: - node[name] = {} - elif not node[name]: - # Pre-existing empty node implies we already have this entire tree. - return - node = node[name] - # Remove any sub-trees we might have had. - node.clear() - - def ToFieldMask(self, field_mask): - """Converts the tree to a FieldMask.""" - field_mask.Clear() - _AddFieldPaths(self._root, '', field_mask) - - def IntersectPath(self, path, intersection): - """Calculates the intersection part of a field path with this tree. - - Args: - path: The field path to calculates. - intersection: The out tree to record the intersection part. - """ - node = self._root - for name in path.split('.'): - if name not in node: - return - elif not node[name]: - intersection.AddPath(path) - return - node = node[name] - intersection.AddLeafNodes(path, node) - - def AddLeafNodes(self, prefix, node): - """Adds leaf nodes begin with prefix to this tree.""" - if not node: - self.AddPath(prefix) - for name in node: - child_path = prefix + '.' + name - self.AddLeafNodes(child_path, node[name]) - - def MergeMessage( - self, source, destination, - replace_message, replace_repeated): - """Merge all fields specified by this tree from source to destination.""" - _MergeMessage( - self._root, source, destination, replace_message, replace_repeated) - - -def _StrConvert(value): - """Converts value to str if it is not.""" - # This file is imported by c extension and some methods like ClearField - # requires string for the field name. py2/py3 has different text - # type and may use unicode. - if not isinstance(value, str): - return value.encode('utf-8') - return value - - -def _MergeMessage( - node, source, destination, replace_message, replace_repeated): - """Merge all fields specified by a sub-tree from source to destination.""" - source_descriptor = source.DESCRIPTOR - for name in node: - child = node[name] - field = source_descriptor.fields_by_name[name] - if field is None: - raise ValueError('Error: Can\'t find field {0} in message {1}.'.format( - name, source_descriptor.full_name)) - if child: - # Sub-paths are only allowed for singular message fields. - if (field.label == FieldDescriptor.LABEL_REPEATED or - field.cpp_type != FieldDescriptor.CPPTYPE_MESSAGE): - raise ValueError('Error: Field {0} in message {1} is not a singular ' - 'message field and cannot have sub-fields.'.format( - name, source_descriptor.full_name)) - if source.HasField(name): - _MergeMessage( - child, getattr(source, name), getattr(destination, name), - replace_message, replace_repeated) - continue - if field.label == FieldDescriptor.LABEL_REPEATED: - if replace_repeated: - destination.ClearField(_StrConvert(name)) - repeated_source = getattr(source, name) - repeated_destination = getattr(destination, name) - repeated_destination.MergeFrom(repeated_source) - else: - if field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE: - if replace_message: - destination.ClearField(_StrConvert(name)) - if source.HasField(name): - getattr(destination, name).MergeFrom(getattr(source, name)) - else: - setattr(destination, name, getattr(source, name)) - - -def _AddFieldPaths(node, prefix, field_mask): - """Adds the field paths descended from node to field_mask.""" - if not node and prefix: - field_mask.paths.append(prefix) - return - for name in sorted(node): - if prefix: - child_path = prefix + '.' + name - else: - child_path = name - _AddFieldPaths(node[name], child_path, field_mask) - - def _SetStructValue(struct_value, value): if value is None: struct_value.null_value = 0 diff --git a/ext/protobuf/Python/google/protobuf/internal/well_known_types_test.py b/ext/protobuf/Python/google/protobuf/internal/well_known_types_test.py deleted file mode 100644 index a32459a9e..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/well_known_types_test.py +++ /dev/null @@ -1,1013 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Test for google.protobuf.internal.well_known_types.""" - -__author__ = 'jieluo@google.com (Jie Luo)' - -import collections.abc as collections_abc -import datetime -import unittest - -from google.protobuf import any_pb2 -from google.protobuf import duration_pb2 -from google.protobuf import field_mask_pb2 -from google.protobuf import struct_pb2 -from google.protobuf import timestamp_pb2 -from google.protobuf import map_unittest_pb2 -from google.protobuf import unittest_pb2 -from google.protobuf.internal import any_test_pb2 -from google.protobuf.internal import test_util -from google.protobuf.internal import well_known_types -from google.protobuf import descriptor -from google.protobuf import text_format -from google.protobuf.internal import _parameterized - -try: - # New module in Python 3.9: - import zoneinfo # pylint:disable=g-import-not-at-top - _TZ_JAPAN = zoneinfo.ZoneInfo('Japan') - _TZ_PACIFIC = zoneinfo.ZoneInfo('US/Pacific') -except ImportError: - _TZ_JAPAN = datetime.timezone(datetime.timedelta(hours=9), 'Japan') - _TZ_PACIFIC = datetime.timezone(datetime.timedelta(hours=-8), 'US/Pacific') - - -class TimeUtilTestBase(_parameterized.TestCase): - - def CheckTimestampConversion(self, message, text): - self.assertEqual(text, message.ToJsonString()) - parsed_message = timestamp_pb2.Timestamp() - parsed_message.FromJsonString(text) - self.assertEqual(message, parsed_message) - - def CheckDurationConversion(self, message, text): - self.assertEqual(text, message.ToJsonString()) - parsed_message = duration_pb2.Duration() - parsed_message.FromJsonString(text) - self.assertEqual(message, parsed_message) - - -class TimeUtilTest(TimeUtilTestBase): - - def testTimestampSerializeAndParse(self): - message = timestamp_pb2.Timestamp() - # Generated output should contain 3, 6, or 9 fractional digits. - message.seconds = 0 - message.nanos = 0 - self.CheckTimestampConversion(message, '1970-01-01T00:00:00Z') - message.nanos = 10000000 - self.CheckTimestampConversion(message, '1970-01-01T00:00:00.010Z') - message.nanos = 10000 - self.CheckTimestampConversion(message, '1970-01-01T00:00:00.000010Z') - message.nanos = 10 - self.CheckTimestampConversion(message, '1970-01-01T00:00:00.000000010Z') - # Test min timestamps. - message.seconds = -62135596800 - message.nanos = 0 - self.CheckTimestampConversion(message, '0001-01-01T00:00:00Z') - # Test max timestamps. - message.seconds = 253402300799 - message.nanos = 999999999 - self.CheckTimestampConversion(message, '9999-12-31T23:59:59.999999999Z') - # Test negative timestamps. - message.seconds = -1 - self.CheckTimestampConversion(message, '1969-12-31T23:59:59.999999999Z') - - # Parsing accepts an fractional digits as long as they fit into nano - # precision. - message.FromJsonString('1970-01-01T00:00:00.1Z') - self.assertEqual(0, message.seconds) - self.assertEqual(100000000, message.nanos) - # Parsing accepts offsets. - message.FromJsonString('1970-01-01T00:00:00-08:00') - self.assertEqual(8 * 3600, message.seconds) - self.assertEqual(0, message.nanos) - - # It is not easy to check with current time. For test coverage only. - message.GetCurrentTime() - self.assertNotEqual(8 * 3600, message.seconds) - - def testDurationSerializeAndParse(self): - message = duration_pb2.Duration() - # Generated output should contain 3, 6, or 9 fractional digits. - message.seconds = 0 - message.nanos = 0 - self.CheckDurationConversion(message, '0s') - message.nanos = 10000000 - self.CheckDurationConversion(message, '0.010s') - message.nanos = 10000 - self.CheckDurationConversion(message, '0.000010s') - message.nanos = 10 - self.CheckDurationConversion(message, '0.000000010s') - - # Test min and max - message.seconds = 315576000000 - message.nanos = 999999999 - self.CheckDurationConversion(message, '315576000000.999999999s') - message.seconds = -315576000000 - message.nanos = -999999999 - self.CheckDurationConversion(message, '-315576000000.999999999s') - - # Parsing accepts an fractional digits as long as they fit into nano - # precision. - message.FromJsonString('0.1s') - self.assertEqual(100000000, message.nanos) - message.FromJsonString('0.0000001s') - self.assertEqual(100, message.nanos) - - def testTimestampIntegerConversion(self): - message = timestamp_pb2.Timestamp() - message.FromNanoseconds(1) - self.assertEqual('1970-01-01T00:00:00.000000001Z', - message.ToJsonString()) - self.assertEqual(1, message.ToNanoseconds()) - - message.FromNanoseconds(-1) - self.assertEqual('1969-12-31T23:59:59.999999999Z', - message.ToJsonString()) - self.assertEqual(-1, message.ToNanoseconds()) - - message.FromMicroseconds(1) - self.assertEqual('1970-01-01T00:00:00.000001Z', - message.ToJsonString()) - self.assertEqual(1, message.ToMicroseconds()) - - message.FromMicroseconds(-1) - self.assertEqual('1969-12-31T23:59:59.999999Z', - message.ToJsonString()) - self.assertEqual(-1, message.ToMicroseconds()) - - message.FromMilliseconds(1) - self.assertEqual('1970-01-01T00:00:00.001Z', - message.ToJsonString()) - self.assertEqual(1, message.ToMilliseconds()) - - message.FromMilliseconds(-1) - self.assertEqual('1969-12-31T23:59:59.999Z', - message.ToJsonString()) - self.assertEqual(-1, message.ToMilliseconds()) - - message.FromSeconds(1) - self.assertEqual('1970-01-01T00:00:01Z', - message.ToJsonString()) - self.assertEqual(1, message.ToSeconds()) - - message.FromSeconds(-1) - self.assertEqual('1969-12-31T23:59:59Z', - message.ToJsonString()) - self.assertEqual(-1, message.ToSeconds()) - - message.FromNanoseconds(1999) - self.assertEqual(1, message.ToMicroseconds()) - # For negative values, Timestamp will be rounded down. - # For example, "1969-12-31T23:59:59.5Z" (i.e., -0.5s) rounded to seconds - # will be "1969-12-31T23:59:59Z" (i.e., -1s) rather than - # "1970-01-01T00:00:00Z" (i.e., 0s). - message.FromNanoseconds(-1999) - self.assertEqual(-2, message.ToMicroseconds()) - - def testDurationIntegerConversion(self): - message = duration_pb2.Duration() - message.FromNanoseconds(1) - self.assertEqual('0.000000001s', - message.ToJsonString()) - self.assertEqual(1, message.ToNanoseconds()) - - message.FromNanoseconds(-1) - self.assertEqual('-0.000000001s', - message.ToJsonString()) - self.assertEqual(-1, message.ToNanoseconds()) - - message.FromMicroseconds(1) - self.assertEqual('0.000001s', - message.ToJsonString()) - self.assertEqual(1, message.ToMicroseconds()) - - message.FromMicroseconds(-1) - self.assertEqual('-0.000001s', - message.ToJsonString()) - self.assertEqual(-1, message.ToMicroseconds()) - - message.FromMilliseconds(1) - self.assertEqual('0.001s', - message.ToJsonString()) - self.assertEqual(1, message.ToMilliseconds()) - - message.FromMilliseconds(-1) - self.assertEqual('-0.001s', - message.ToJsonString()) - self.assertEqual(-1, message.ToMilliseconds()) - - message.FromSeconds(1) - self.assertEqual('1s', message.ToJsonString()) - self.assertEqual(1, message.ToSeconds()) - - message.FromSeconds(-1) - self.assertEqual('-1s', - message.ToJsonString()) - self.assertEqual(-1, message.ToSeconds()) - - # Test truncation behavior. - message.FromNanoseconds(1999) - self.assertEqual(1, message.ToMicroseconds()) - - # For negative values, Duration will be rounded towards 0. - message.FromNanoseconds(-1999) - self.assertEqual(-1, message.ToMicroseconds()) - - def testTimezoneNaiveDatetimeConversion(self): - message = timestamp_pb2.Timestamp() - naive_utc_epoch = datetime.datetime(1970, 1, 1) - message.FromDatetime(naive_utc_epoch) - self.assertEqual(0, message.seconds) - self.assertEqual(0, message.nanos) - - self.assertEqual(naive_utc_epoch, message.ToDatetime()) - - naive_epoch_morning = datetime.datetime(1970, 1, 1, 8, 0, 0, 1) - message.FromDatetime(naive_epoch_morning) - self.assertEqual(8 * 3600, message.seconds) - self.assertEqual(1000, message.nanos) - - self.assertEqual(naive_epoch_morning, message.ToDatetime()) - - message.FromMilliseconds(1999) - self.assertEqual(1, message.seconds) - self.assertEqual(999_000_000, message.nanos) - - self.assertEqual(datetime.datetime(1970, 1, 1, 0, 0, 1, 999000), - message.ToDatetime()) - - naive_future = datetime.datetime(2555, 2, 22, 1, 2, 3, 456789) - message.FromDatetime(naive_future) - self.assertEqual(naive_future, message.ToDatetime()) - - naive_end_of_time = datetime.datetime.max - message.FromDatetime(naive_end_of_time) - self.assertEqual(naive_end_of_time, message.ToDatetime()) - - # Two hours after the Unix Epoch, around the world. - @_parameterized.named_parameters( - ('London', [1970, 1, 1, 2], datetime.timezone.utc), - ('Tokyo', [1970, 1, 1, 11], _TZ_JAPAN), - ('LA', [1969, 12, 31, 18], _TZ_PACIFIC), - ) - def testTimezoneAwareDatetimeConversion(self, date_parts, tzinfo): - original_datetime = datetime.datetime(*date_parts, tzinfo=tzinfo) # pylint:disable=g-tzinfo-datetime - - message = timestamp_pb2.Timestamp() - message.FromDatetime(original_datetime) - self.assertEqual(7200, message.seconds) - self.assertEqual(0, message.nanos) - - # ToDatetime() with no parameters produces a naive UTC datetime, i.e. it not - # only loses the original timezone information (e.g. US/Pacific) as it's - # "normalised" to UTC, but also drops the information that the datetime - # represents a UTC one. - naive_datetime = message.ToDatetime() - self.assertEqual(datetime.datetime(1970, 1, 1, 2), naive_datetime) - self.assertIsNone(naive_datetime.tzinfo) - self.assertNotEqual(original_datetime, naive_datetime) # not even for UTC! - - # In contrast, ToDatetime(tzinfo=) produces an aware datetime in the given - # timezone. - aware_datetime = message.ToDatetime(tzinfo=tzinfo) - self.assertEqual(original_datetime, aware_datetime) - self.assertEqual( - datetime.datetime(1970, 1, 1, 2, tzinfo=datetime.timezone.utc), - aware_datetime) - self.assertEqual(tzinfo, aware_datetime.tzinfo) - - def testTimedeltaConversion(self): - message = duration_pb2.Duration() - message.FromNanoseconds(1999999999) - td = message.ToTimedelta() - self.assertEqual(1, td.seconds) - self.assertEqual(999999, td.microseconds) - - message.FromNanoseconds(-1999999999) - td = message.ToTimedelta() - self.assertEqual(-1, td.days) - self.assertEqual(86398, td.seconds) - self.assertEqual(1, td.microseconds) - - message.FromMicroseconds(-1) - td = message.ToTimedelta() - self.assertEqual(-1, td.days) - self.assertEqual(86399, td.seconds) - self.assertEqual(999999, td.microseconds) - converted_message = duration_pb2.Duration() - converted_message.FromTimedelta(td) - self.assertEqual(message, converted_message) - - def testInvalidTimestamp(self): - message = timestamp_pb2.Timestamp() - self.assertRaisesRegex( - ValueError, 'Failed to parse timestamp: missing valid timezone offset.', - message.FromJsonString, '') - self.assertRaisesRegex( - ValueError, 'Failed to parse timestamp: invalid trailing data ' - '1970-01-01T00:00:01Ztrail.', message.FromJsonString, - '1970-01-01T00:00:01Ztrail') - self.assertRaisesRegex( - ValueError, 'time data \'10000-01-01T00:00:00\' does not match' - ' format \'%Y-%m-%dT%H:%M:%S\'', message.FromJsonString, - '10000-01-01T00:00:00.00Z') - self.assertRaisesRegex( - ValueError, 'nanos 0123456789012 more than 9 fractional digits.', - message.FromJsonString, '1970-01-01T00:00:00.0123456789012Z') - self.assertRaisesRegex( - ValueError, - (r'Invalid timezone offset value: \+08.'), - message.FromJsonString, - '1972-01-01T01:00:00.01+08', - ) - self.assertRaisesRegex(ValueError, 'year (0 )?is out of range', - message.FromJsonString, '0000-01-01T00:00:00Z') - message.seconds = 253402300800 - self.assertRaisesRegex(OverflowError, 'date value out of range', - message.ToJsonString) - - def testInvalidDuration(self): - message = duration_pb2.Duration() - self.assertRaisesRegex(ValueError, 'Duration must end with letter "s": 1.', - message.FromJsonString, '1') - self.assertRaisesRegex(ValueError, 'Couldn\'t parse duration: 1...2s.', - message.FromJsonString, '1...2s') - text = '-315576000001.000000000s' - self.assertRaisesRegex( - ValueError, - r'Duration is not valid\: Seconds -315576000001 must be in range' - r' \[-315576000000\, 315576000000\].', message.FromJsonString, text) - text = '315576000001.000000000s' - self.assertRaisesRegex( - ValueError, - r'Duration is not valid\: Seconds 315576000001 must be in range' - r' \[-315576000000\, 315576000000\].', message.FromJsonString, text) - message.seconds = -315576000001 - message.nanos = 0 - self.assertRaisesRegex( - ValueError, - r'Duration is not valid\: Seconds -315576000001 must be in range' - r' \[-315576000000\, 315576000000\].', message.ToJsonString) - message.seconds = 0 - message.nanos = 999999999 + 1 - self.assertRaisesRegex( - ValueError, r'Duration is not valid\: Nanos 1000000000 must be in range' - r' \[-999999999\, 999999999\].', message.ToJsonString) - message.seconds = -1 - message.nanos = 1 - self.assertRaisesRegex(ValueError, - r'Duration is not valid\: Sign mismatch.', - message.ToJsonString) - - -class FieldMaskTest(unittest.TestCase): - - def testStringFormat(self): - mask = field_mask_pb2.FieldMask() - self.assertEqual('', mask.ToJsonString()) - mask.paths.append('foo') - self.assertEqual('foo', mask.ToJsonString()) - mask.paths.append('bar') - self.assertEqual('foo,bar', mask.ToJsonString()) - - mask.FromJsonString('') - self.assertEqual('', mask.ToJsonString()) - mask.FromJsonString('foo') - self.assertEqual(['foo'], mask.paths) - mask.FromJsonString('foo,bar') - self.assertEqual(['foo', 'bar'], mask.paths) - - # Test camel case - mask.Clear() - mask.paths.append('foo_bar') - self.assertEqual('fooBar', mask.ToJsonString()) - mask.paths.append('bar_quz') - self.assertEqual('fooBar,barQuz', mask.ToJsonString()) - - mask.FromJsonString('') - self.assertEqual('', mask.ToJsonString()) - self.assertEqual([], mask.paths) - mask.FromJsonString('fooBar') - self.assertEqual(['foo_bar'], mask.paths) - mask.FromJsonString('fooBar,barQuz') - self.assertEqual(['foo_bar', 'bar_quz'], mask.paths) - - def testDescriptorToFieldMask(self): - mask = field_mask_pb2.FieldMask() - msg_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - mask.AllFieldsFromDescriptor(msg_descriptor) - self.assertEqual(76, len(mask.paths)) - self.assertTrue(mask.IsValidForDescriptor(msg_descriptor)) - for field in msg_descriptor.fields: - self.assertTrue(field.name in mask.paths) - - def testIsValidForDescriptor(self): - msg_descriptor = unittest_pb2.TestAllTypes.DESCRIPTOR - # Empty mask - mask = field_mask_pb2.FieldMask() - self.assertTrue(mask.IsValidForDescriptor(msg_descriptor)) - # All fields from descriptor - mask.AllFieldsFromDescriptor(msg_descriptor) - self.assertTrue(mask.IsValidForDescriptor(msg_descriptor)) - # Child under optional message - mask.paths.append('optional_nested_message.bb') - self.assertTrue(mask.IsValidForDescriptor(msg_descriptor)) - # Repeated field is only allowed in the last position of path - mask.paths.append('repeated_nested_message.bb') - self.assertFalse(mask.IsValidForDescriptor(msg_descriptor)) - # Invalid top level field - mask = field_mask_pb2.FieldMask() - mask.paths.append('xxx') - self.assertFalse(mask.IsValidForDescriptor(msg_descriptor)) - # Invalid field in root - mask = field_mask_pb2.FieldMask() - mask.paths.append('xxx.zzz') - self.assertFalse(mask.IsValidForDescriptor(msg_descriptor)) - # Invalid field in internal node - mask = field_mask_pb2.FieldMask() - mask.paths.append('optional_nested_message.xxx.zzz') - self.assertFalse(mask.IsValidForDescriptor(msg_descriptor)) - # Invalid field in leaf - mask = field_mask_pb2.FieldMask() - mask.paths.append('optional_nested_message.xxx') - self.assertFalse(mask.IsValidForDescriptor(msg_descriptor)) - - def testCanonicalFrom(self): - mask = field_mask_pb2.FieldMask() - out_mask = field_mask_pb2.FieldMask() - # Paths will be sorted. - mask.FromJsonString('baz.quz,bar,foo') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('bar,baz.quz,foo', out_mask.ToJsonString()) - # Duplicated paths will be removed. - mask.FromJsonString('foo,bar,foo') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('bar,foo', out_mask.ToJsonString()) - # Sub-paths of other paths will be removed. - mask.FromJsonString('foo.b1,bar.b1,foo.b2,bar') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('bar,foo.b1,foo.b2', out_mask.ToJsonString()) - - # Test more deeply nested cases. - mask.FromJsonString( - 'foo.bar.baz1,foo.bar.baz2.quz,foo.bar.baz2') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('foo.bar.baz1,foo.bar.baz2', - out_mask.ToJsonString()) - mask.FromJsonString( - 'foo.bar.baz1,foo.bar.baz2,foo.bar.baz2.quz') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('foo.bar.baz1,foo.bar.baz2', - out_mask.ToJsonString()) - mask.FromJsonString( - 'foo.bar.baz1,foo.bar.baz2,foo.bar.baz2.quz,foo.bar') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('foo.bar', out_mask.ToJsonString()) - mask.FromJsonString( - 'foo.bar.baz1,foo.bar.baz2,foo.bar.baz2.quz,foo') - out_mask.CanonicalFormFromMask(mask) - self.assertEqual('foo', out_mask.ToJsonString()) - - def testUnion(self): - mask1 = field_mask_pb2.FieldMask() - mask2 = field_mask_pb2.FieldMask() - out_mask = field_mask_pb2.FieldMask() - mask1.FromJsonString('foo,baz') - mask2.FromJsonString('bar,quz') - out_mask.Union(mask1, mask2) - self.assertEqual('bar,baz,foo,quz', out_mask.ToJsonString()) - # Overlap with duplicated paths. - mask1.FromJsonString('foo,baz.bb') - mask2.FromJsonString('baz.bb,quz') - out_mask.Union(mask1, mask2) - self.assertEqual('baz.bb,foo,quz', out_mask.ToJsonString()) - # Overlap with paths covering some other paths. - mask1.FromJsonString('foo.bar.baz,quz') - mask2.FromJsonString('foo.bar,bar') - out_mask.Union(mask1, mask2) - self.assertEqual('bar,foo.bar,quz', out_mask.ToJsonString()) - src = unittest_pb2.TestAllTypes() - with self.assertRaises(ValueError): - out_mask.Union(src, mask2) - - def testIntersect(self): - mask1 = field_mask_pb2.FieldMask() - mask2 = field_mask_pb2.FieldMask() - out_mask = field_mask_pb2.FieldMask() - # Test cases without overlapping. - mask1.FromJsonString('foo,baz') - mask2.FromJsonString('bar,quz') - out_mask.Intersect(mask1, mask2) - self.assertEqual('', out_mask.ToJsonString()) - self.assertEqual(len(out_mask.paths), 0) - self.assertEqual(out_mask.paths, []) - # Overlap with duplicated paths. - mask1.FromJsonString('foo,baz.bb') - mask2.FromJsonString('baz.bb,quz') - out_mask.Intersect(mask1, mask2) - self.assertEqual('baz.bb', out_mask.ToJsonString()) - # Overlap with paths covering some other paths. - mask1.FromJsonString('foo.bar.baz,quz') - mask2.FromJsonString('foo.bar,bar') - out_mask.Intersect(mask1, mask2) - self.assertEqual('foo.bar.baz', out_mask.ToJsonString()) - mask1.FromJsonString('foo.bar,bar') - mask2.FromJsonString('foo.bar.baz,quz') - out_mask.Intersect(mask1, mask2) - self.assertEqual('foo.bar.baz', out_mask.ToJsonString()) - # Intersect '' with '' - mask1.Clear() - mask2.Clear() - mask1.paths.append('') - mask2.paths.append('') - self.assertEqual(mask1.paths, ['']) - self.assertEqual('', mask1.ToJsonString()) - out_mask.Intersect(mask1, mask2) - self.assertEqual(out_mask.paths, []) - - def testMergeMessageWithoutMapFields(self): - # Test merge one field. - src = unittest_pb2.TestAllTypes() - test_util.SetAllFields(src) - for field in src.DESCRIPTOR.fields: - if field.containing_oneof: - continue - field_name = field.name - dst = unittest_pb2.TestAllTypes() - # Only set one path to mask. - mask = field_mask_pb2.FieldMask() - mask.paths.append(field_name) - mask.MergeMessage(src, dst) - # The expected result message. - msg = unittest_pb2.TestAllTypes() - if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: - repeated_src = getattr(src, field_name) - repeated_msg = getattr(msg, field_name) - if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: - for item in repeated_src: - repeated_msg.add().CopyFrom(item) - else: - repeated_msg.extend(repeated_src) - elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: - getattr(msg, field_name).CopyFrom(getattr(src, field_name)) - else: - setattr(msg, field_name, getattr(src, field_name)) - # Only field specified in mask is merged. - self.assertEqual(msg, dst) - - # Test merge nested fields. - nested_src = unittest_pb2.NestedTestAllTypes() - nested_dst = unittest_pb2.NestedTestAllTypes() - nested_src.child.payload.optional_int32 = 1234 - nested_src.child.child.payload.optional_int32 = 5678 - mask = field_mask_pb2.FieldMask() - mask.FromJsonString('child.payload') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(1234, nested_dst.child.payload.optional_int32) - self.assertEqual(0, nested_dst.child.child.payload.optional_int32) - - mask.FromJsonString('child.child.payload') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(1234, nested_dst.child.payload.optional_int32) - self.assertEqual(5678, nested_dst.child.child.payload.optional_int32) - - nested_dst.Clear() - mask.FromJsonString('child.child.payload') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(0, nested_dst.child.payload.optional_int32) - self.assertEqual(5678, nested_dst.child.child.payload.optional_int32) - - nested_dst.Clear() - mask.FromJsonString('child') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(1234, nested_dst.child.payload.optional_int32) - self.assertEqual(5678, nested_dst.child.child.payload.optional_int32) - - # Test MergeOptions. - nested_dst.Clear() - nested_dst.child.payload.optional_int64 = 4321 - # Message fields will be merged by default. - mask.FromJsonString('child.payload') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(1234, nested_dst.child.payload.optional_int32) - self.assertEqual(4321, nested_dst.child.payload.optional_int64) - # Change the behavior to replace message fields. - mask.FromJsonString('child.payload') - mask.MergeMessage(nested_src, nested_dst, True, False) - self.assertEqual(1234, nested_dst.child.payload.optional_int32) - self.assertEqual(0, nested_dst.child.payload.optional_int64) - - # By default, fields missing in source are not cleared in destination. - nested_dst.payload.optional_int32 = 1234 - self.assertTrue(nested_dst.HasField('payload')) - mask.FromJsonString('payload') - mask.MergeMessage(nested_src, nested_dst) - self.assertTrue(nested_dst.HasField('payload')) - # But they are cleared when replacing message fields. - nested_dst.Clear() - nested_dst.payload.optional_int32 = 1234 - mask.FromJsonString('payload') - mask.MergeMessage(nested_src, nested_dst, True, False) - self.assertFalse(nested_dst.HasField('payload')) - - nested_src.payload.repeated_int32.append(1234) - nested_dst.payload.repeated_int32.append(5678) - # Repeated fields will be appended by default. - mask.FromJsonString('payload.repeatedInt32') - mask.MergeMessage(nested_src, nested_dst) - self.assertEqual(2, len(nested_dst.payload.repeated_int32)) - self.assertEqual(5678, nested_dst.payload.repeated_int32[0]) - self.assertEqual(1234, nested_dst.payload.repeated_int32[1]) - # Change the behavior to replace repeated fields. - mask.FromJsonString('payload.repeatedInt32') - mask.MergeMessage(nested_src, nested_dst, False, True) - self.assertEqual(1, len(nested_dst.payload.repeated_int32)) - self.assertEqual(1234, nested_dst.payload.repeated_int32[0]) - - # Test Merge oneof field. - new_msg = unittest_pb2.TestOneof2() - dst = unittest_pb2.TestOneof2() - dst.foo_message.moo_int = 1 - mask = field_mask_pb2.FieldMask() - mask.FromJsonString('fooMessage,fooLazyMessage.mooInt') - mask.MergeMessage(new_msg, dst) - self.assertTrue(dst.HasField('foo_message')) - self.assertFalse(dst.HasField('foo_lazy_message')) - - def testMergeMessageWithMapField(self): - empty_map = map_unittest_pb2.TestRecursiveMapMessage() - src_level_2 = map_unittest_pb2.TestRecursiveMapMessage() - src_level_2.a['src level 2'].CopyFrom(empty_map) - src = map_unittest_pb2.TestRecursiveMapMessage() - src.a['common key'].CopyFrom(src_level_2) - src.a['src level 1'].CopyFrom(src_level_2) - - dst_level_2 = map_unittest_pb2.TestRecursiveMapMessage() - dst_level_2.a['dst level 2'].CopyFrom(empty_map) - dst = map_unittest_pb2.TestRecursiveMapMessage() - dst.a['common key'].CopyFrom(dst_level_2) - dst.a['dst level 1'].CopyFrom(empty_map) - - mask = field_mask_pb2.FieldMask() - mask.FromJsonString('a') - mask.MergeMessage(src, dst) - - # map from dst is replaced with map from src. - self.assertEqual(dst.a['common key'], src_level_2) - self.assertEqual(dst.a['src level 1'], src_level_2) - self.assertEqual(dst.a['dst level 1'], empty_map) - - def testMergeErrors(self): - src = unittest_pb2.TestAllTypes() - dst = unittest_pb2.TestAllTypes() - mask = field_mask_pb2.FieldMask() - test_util.SetAllFields(src) - mask.FromJsonString('optionalInt32.field') - with self.assertRaises(ValueError) as e: - mask.MergeMessage(src, dst) - self.assertEqual('Error: Field optional_int32 in message ' - 'protobuf_unittest.TestAllTypes is not a singular ' - 'message field and cannot have sub-fields.', - str(e.exception)) - - def testSnakeCaseToCamelCase(self): - self.assertEqual('fooBar', - well_known_types._SnakeCaseToCamelCase('foo_bar')) - self.assertEqual('FooBar', - well_known_types._SnakeCaseToCamelCase('_foo_bar')) - self.assertEqual('foo3Bar', - well_known_types._SnakeCaseToCamelCase('foo3_bar')) - - # No uppercase letter is allowed. - self.assertRaisesRegex( - ValueError, - 'Fail to print FieldMask to Json string: Path name Foo must ' - 'not contain uppercase letters.', - well_known_types._SnakeCaseToCamelCase, 'Foo') - # Any character after a "_" must be a lowercase letter. - # 1. "_" cannot be followed by another "_". - # 2. "_" cannot be followed by a digit. - # 3. "_" cannot appear as the last character. - self.assertRaisesRegex( - ValueError, - 'Fail to print FieldMask to Json string: The character after a ' - '"_" must be a lowercase letter in path name foo__bar.', - well_known_types._SnakeCaseToCamelCase, 'foo__bar') - self.assertRaisesRegex( - ValueError, - 'Fail to print FieldMask to Json string: The character after a ' - '"_" must be a lowercase letter in path name foo_3bar.', - well_known_types._SnakeCaseToCamelCase, 'foo_3bar') - self.assertRaisesRegex( - ValueError, - 'Fail to print FieldMask to Json string: Trailing "_" in path ' - 'name foo_bar_.', well_known_types._SnakeCaseToCamelCase, 'foo_bar_') - - def testCamelCaseToSnakeCase(self): - self.assertEqual('foo_bar', - well_known_types._CamelCaseToSnakeCase('fooBar')) - self.assertEqual('_foo_bar', - well_known_types._CamelCaseToSnakeCase('FooBar')) - self.assertEqual('foo3_bar', - well_known_types._CamelCaseToSnakeCase('foo3Bar')) - self.assertRaisesRegex( - ValueError, - 'Fail to parse FieldMask: Path name foo_bar must not contain "_"s.', - well_known_types._CamelCaseToSnakeCase, 'foo_bar') - - -class StructTest(unittest.TestCase): - - def testStruct(self): - struct = struct_pb2.Struct() - self.assertIsInstance(struct, collections_abc.Mapping) - self.assertEqual(0, len(struct)) - struct_class = struct.__class__ - - struct['key1'] = 5 - struct['key2'] = 'abc' - struct['key3'] = True - struct.get_or_create_struct('key4')['subkey'] = 11.0 - struct_list = struct.get_or_create_list('key5') - self.assertIsInstance(struct_list, collections_abc.Sequence) - struct_list.extend([6, 'seven', True, False, None]) - struct_list.add_struct()['subkey2'] = 9 - struct['key6'] = {'subkey': {}} - struct['key7'] = [2, False] - - self.assertEqual(7, len(struct)) - self.assertTrue(isinstance(struct, well_known_types.Struct)) - self.assertEqual(5, struct['key1']) - self.assertEqual('abc', struct['key2']) - self.assertIs(True, struct['key3']) - self.assertEqual(11, struct['key4']['subkey']) - inner_struct = struct_class() - inner_struct['subkey2'] = 9 - self.assertEqual([6, 'seven', True, False, None, inner_struct], - list(struct['key5'].items())) - self.assertEqual({}, dict(struct['key6']['subkey'].fields)) - self.assertEqual([2, False], list(struct['key7'].items())) - - serialized = struct.SerializeToString() - struct2 = struct_pb2.Struct() - struct2.ParseFromString(serialized) - - self.assertEqual(struct, struct2) - for key, value in struct.items(): - self.assertIn(key, struct) - self.assertIn(key, struct2) - self.assertEqual(value, struct2[key]) - - self.assertEqual(7, len(struct.keys())) - self.assertEqual(7, len(struct.values())) - for key in struct.keys(): - self.assertIn(key, struct) - self.assertIn(key, struct2) - self.assertEqual(struct[key], struct2[key]) - - item = (next(iter(struct.keys())), next(iter(struct.values()))) - self.assertEqual(item, next(iter(struct.items()))) - - self.assertTrue(isinstance(struct2, well_known_types.Struct)) - self.assertEqual(5, struct2['key1']) - self.assertEqual('abc', struct2['key2']) - self.assertIs(True, struct2['key3']) - self.assertEqual(11, struct2['key4']['subkey']) - self.assertEqual([6, 'seven', True, False, None, inner_struct], - list(struct2['key5'].items())) - - struct_list = struct2['key5'] - self.assertEqual(6, struct_list[0]) - self.assertEqual('seven', struct_list[1]) - self.assertEqual(True, struct_list[2]) - self.assertEqual(False, struct_list[3]) - self.assertEqual(None, struct_list[4]) - self.assertEqual(inner_struct, struct_list[5]) - - struct_list[1] = 7 - self.assertEqual(7, struct_list[1]) - - struct_list.add_list().extend([1, 'two', True, False, None]) - self.assertEqual([1, 'two', True, False, None], - list(struct_list[6].items())) - struct_list.extend([{'nested_struct': 30}, ['nested_list', 99], {}, []]) - self.assertEqual(11, len(struct_list.values)) - self.assertEqual(30, struct_list[7]['nested_struct']) - self.assertEqual('nested_list', struct_list[8][0]) - self.assertEqual(99, struct_list[8][1]) - self.assertEqual({}, dict(struct_list[9].fields)) - self.assertEqual([], list(struct_list[10].items())) - struct_list[0] = {'replace': 'set'} - struct_list[1] = ['replace', 'set'] - self.assertEqual('set', struct_list[0]['replace']) - self.assertEqual(['replace', 'set'], list(struct_list[1].items())) - - text_serialized = str(struct) - struct3 = struct_pb2.Struct() - text_format.Merge(text_serialized, struct3) - self.assertEqual(struct, struct3) - - struct.get_or_create_struct('key3')['replace'] = 12 - self.assertEqual(12, struct['key3']['replace']) - - # Tests empty list. - struct.get_or_create_list('empty_list') - empty_list = struct['empty_list'] - self.assertEqual([], list(empty_list.items())) - list2 = struct_pb2.ListValue() - list2.add_list() - empty_list = list2[0] - self.assertEqual([], list(empty_list.items())) - - # Tests empty struct. - struct.get_or_create_struct('empty_struct') - empty_struct = struct['empty_struct'] - self.assertEqual({}, dict(empty_struct.fields)) - list2.add_struct() - empty_struct = list2[1] - self.assertEqual({}, dict(empty_struct.fields)) - - self.assertEqual(9, len(struct)) - del struct['key3'] - del struct['key4'] - self.assertEqual(7, len(struct)) - self.assertEqual(6, len(struct['key5'])) - del struct['key5'][1] - self.assertEqual(5, len(struct['key5'])) - self.assertEqual([6, True, False, None, inner_struct], - list(struct['key5'].items())) - - def testStructAssignment(self): - # Tests struct assignment from another struct - s1 = struct_pb2.Struct() - s2 = struct_pb2.Struct() - for value in [1, 'a', [1], ['a'], {'a': 'b'}]: - s1['x'] = value - s2['x'] = s1['x'] - self.assertEqual(s1['x'], s2['x']) - - def testMergeFrom(self): - struct = struct_pb2.Struct() - struct_class = struct.__class__ - - dictionary = { - 'key1': 5, - 'key2': 'abc', - 'key3': True, - 'key4': {'subkey': 11.0}, - 'key5': [6, 'seven', True, False, None, {'subkey2': 9}], - 'key6': [['nested_list', True]], - 'empty_struct': {}, - 'empty_list': [] - } - struct.update(dictionary) - self.assertEqual(5, struct['key1']) - self.assertEqual('abc', struct['key2']) - self.assertIs(True, struct['key3']) - self.assertEqual(11, struct['key4']['subkey']) - inner_struct = struct_class() - inner_struct['subkey2'] = 9 - self.assertEqual([6, 'seven', True, False, None, inner_struct], - list(struct['key5'].items())) - self.assertEqual(2, len(struct['key6'][0].values)) - self.assertEqual('nested_list', struct['key6'][0][0]) - self.assertEqual(True, struct['key6'][0][1]) - empty_list = struct['empty_list'] - self.assertEqual([], list(empty_list.items())) - empty_struct = struct['empty_struct'] - self.assertEqual({}, dict(empty_struct.fields)) - - # According to documentation: "When parsing from the wire or when merging, - # if there are duplicate map keys the last key seen is used". - duplicate = { - 'key4': {'replace': 20}, - 'key5': [[False, 5]] - } - struct.update(duplicate) - self.assertEqual(1, len(struct['key4'].fields)) - self.assertEqual(20, struct['key4']['replace']) - self.assertEqual(1, len(struct['key5'].values)) - self.assertEqual(False, struct['key5'][0][0]) - self.assertEqual(5, struct['key5'][0][1]) - - -class AnyTest(unittest.TestCase): - - def testAnyMessage(self): - # Creates and sets message. - msg = any_test_pb2.TestAny() - msg_descriptor = msg.DESCRIPTOR - all_types = unittest_pb2.TestAllTypes() - all_descriptor = all_types.DESCRIPTOR - all_types.repeated_string.append(u'\u00fc\ua71f') - # Packs to Any. - msg.value.Pack(all_types) - self.assertEqual(msg.value.type_url, - 'type.googleapis.com/%s' % all_descriptor.full_name) - self.assertEqual(msg.value.value, - all_types.SerializeToString()) - # Tests Is() method. - self.assertTrue(msg.value.Is(all_descriptor)) - self.assertFalse(msg.value.Is(msg_descriptor)) - # Unpacks Any. - unpacked_message = unittest_pb2.TestAllTypes() - self.assertTrue(msg.value.Unpack(unpacked_message)) - self.assertEqual(all_types, unpacked_message) - # Unpacks to different type. - self.assertFalse(msg.value.Unpack(msg)) - # Only Any messages have Pack method. - try: - msg.Pack(all_types) - except AttributeError: - pass - else: - raise AttributeError('%s should not have Pack method.' % - msg_descriptor.full_name) - - def testUnpackWithNoSlashInTypeUrl(self): - msg = any_test_pb2.TestAny() - all_types = unittest_pb2.TestAllTypes() - all_descriptor = all_types.DESCRIPTOR - msg.value.Pack(all_types) - # Reset type_url to part of type_url after '/' - msg.value.type_url = msg.value.TypeName() - self.assertFalse(msg.value.Is(all_descriptor)) - unpacked_message = unittest_pb2.TestAllTypes() - self.assertFalse(msg.value.Unpack(unpacked_message)) - - def testMessageName(self): - # Creates and sets message. - submessage = any_test_pb2.TestAny() - submessage.int_value = 12345 - msg = any_pb2.Any() - msg.Pack(submessage) - self.assertEqual(msg.TypeName(), 'google.protobuf.internal.TestAny') - - def testPackWithCustomTypeUrl(self): - submessage = any_test_pb2.TestAny() - submessage.int_value = 12345 - msg = any_pb2.Any() - # Pack with a custom type URL prefix. - msg.Pack(submessage, 'type.myservice.com') - self.assertEqual(msg.type_url, - 'type.myservice.com/%s' % submessage.DESCRIPTOR.full_name) - # Pack with a custom type URL prefix ending with '/'. - msg.Pack(submessage, 'type.myservice.com/') - self.assertEqual(msg.type_url, - 'type.myservice.com/%s' % submessage.DESCRIPTOR.full_name) - # Pack with an empty type URL prefix. - msg.Pack(submessage, '') - self.assertEqual(msg.type_url, - '/%s' % submessage.DESCRIPTOR.full_name) - # Test unpacking the type. - unpacked_message = any_test_pb2.TestAny() - self.assertTrue(msg.Unpack(unpacked_message)) - self.assertEqual(submessage, unpacked_message) - - def testPackDeterministic(self): - submessage = any_test_pb2.TestAny() - for i in range(10): - submessage.map_value[str(i)] = i * 2 - msg = any_pb2.Any() - msg.Pack(submessage, deterministic=True) - serialized = msg.SerializeToString(deterministic=True) - golden = (b'\n4type.googleapis.com/google.protobuf.internal.TestAny\x12F' - b'\x1a\x05\n\x010\x10\x00\x1a\x05\n\x011\x10\x02\x1a\x05\n\x01' - b'2\x10\x04\x1a\x05\n\x013\x10\x06\x1a\x05\n\x014\x10\x08\x1a' - b'\x05\n\x015\x10\n\x1a\x05\n\x016\x10\x0c\x1a\x05\n\x017\x10' - b'\x0e\x1a\x05\n\x018\x10\x10\x1a\x05\n\x019\x10\x12') - self.assertEqual(golden, serialized) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/internal/wire_format.py b/ext/protobuf/Python/google/protobuf/internal/wire_format.py index 883f52558..1f54414b1 100644 --- a/ext/protobuf/Python/google/protobuf/internal/wire_format.py +++ b/ext/protobuf/Python/google/protobuf/internal/wire_format.py @@ -43,7 +43,7 @@ # These numbers identify the wire type of a protocol buffer value. # We use the least-significant TAG_TYPE_BITS bits of the varint-encoded # tag-and-type to store one of these WIRETYPE_* constants. -# These values must match WireType enum in google/protobuf/wire_format.h. +# These values must match WireType enum in //google/protobuf/wire_format.h. WIRETYPE_VARINT = 0 WIRETYPE_FIXED64 = 1 WIRETYPE_LENGTH_DELIMITED = 2 diff --git a/ext/protobuf/Python/google/protobuf/internal/wire_format_test.py b/ext/protobuf/Python/google/protobuf/internal/wire_format_test.py deleted file mode 100644 index f7ad0c79a..000000000 --- a/ext/protobuf/Python/google/protobuf/internal/wire_format_test.py +++ /dev/null @@ -1,252 +0,0 @@ -# Protocol Buffers - Google's data interchange format -# Copyright 2008 Google Inc. All rights reserved. -# https://developers.google.com/protocol-buffers/ -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -"""Test for google.protobuf.internal.wire_format.""" - -__author__ = 'robinson@google.com (Will Robinson)' - -import unittest - -from google.protobuf import message -from google.protobuf.internal import wire_format - - -class WireFormatTest(unittest.TestCase): - - def testPackTag(self): - field_number = 0xabc - tag_type = 2 - self.assertEqual((field_number << 3) | tag_type, - wire_format.PackTag(field_number, tag_type)) - PackTag = wire_format.PackTag - # Number too high. - self.assertRaises(message.EncodeError, PackTag, field_number, 6) - # Number too low. - self.assertRaises(message.EncodeError, PackTag, field_number, -1) - - def testUnpackTag(self): - # Test field numbers that will require various varint sizes. - for expected_field_number in (1, 15, 16, 2047, 2048): - for expected_wire_type in range(6): # Highest-numbered wiretype is 5. - field_number, wire_type = wire_format.UnpackTag( - wire_format.PackTag(expected_field_number, expected_wire_type)) - self.assertEqual(expected_field_number, field_number) - self.assertEqual(expected_wire_type, wire_type) - - self.assertRaises(TypeError, wire_format.UnpackTag, None) - self.assertRaises(TypeError, wire_format.UnpackTag, 'abc') - self.assertRaises(TypeError, wire_format.UnpackTag, 0.0) - self.assertRaises(TypeError, wire_format.UnpackTag, object()) - - def testZigZagEncode(self): - Z = wire_format.ZigZagEncode - self.assertEqual(0, Z(0)) - self.assertEqual(1, Z(-1)) - self.assertEqual(2, Z(1)) - self.assertEqual(3, Z(-2)) - self.assertEqual(4, Z(2)) - self.assertEqual(0xfffffffe, Z(0x7fffffff)) - self.assertEqual(0xffffffff, Z(-0x80000000)) - self.assertEqual(0xfffffffffffffffe, Z(0x7fffffffffffffff)) - self.assertEqual(0xffffffffffffffff, Z(-0x8000000000000000)) - - self.assertRaises(TypeError, Z, None) - self.assertRaises(TypeError, Z, 'abcd') - self.assertRaises(TypeError, Z, 0.0) - self.assertRaises(TypeError, Z, object()) - - def testZigZagDecode(self): - Z = wire_format.ZigZagDecode - self.assertEqual(0, Z(0)) - self.assertEqual(-1, Z(1)) - self.assertEqual(1, Z(2)) - self.assertEqual(-2, Z(3)) - self.assertEqual(2, Z(4)) - self.assertEqual(0x7fffffff, Z(0xfffffffe)) - self.assertEqual(-0x80000000, Z(0xffffffff)) - self.assertEqual(0x7fffffffffffffff, Z(0xfffffffffffffffe)) - self.assertEqual(-0x8000000000000000, Z(0xffffffffffffffff)) - - self.assertRaises(TypeError, Z, None) - self.assertRaises(TypeError, Z, 'abcd') - self.assertRaises(TypeError, Z, 0.0) - self.assertRaises(TypeError, Z, object()) - - def NumericByteSizeTestHelper(self, byte_size_fn, value, expected_value_size): - # Use field numbers that cause various byte sizes for the tag information. - for field_number, tag_bytes in ((15, 1), (16, 2), (2047, 2), (2048, 3)): - expected_size = expected_value_size + tag_bytes - actual_size = byte_size_fn(field_number, value) - self.assertEqual(expected_size, actual_size, - 'byte_size_fn: %s, field_number: %d, value: %r\n' - 'Expected: %d, Actual: %d'% ( - byte_size_fn, field_number, value, expected_size, actual_size)) - - def testByteSizeFunctions(self): - # Test all numeric *ByteSize() functions. - NUMERIC_ARGS = [ - # Int32ByteSize(). - [wire_format.Int32ByteSize, 0, 1], - [wire_format.Int32ByteSize, 127, 1], - [wire_format.Int32ByteSize, 128, 2], - [wire_format.Int32ByteSize, -1, 10], - # Int64ByteSize(). - [wire_format.Int64ByteSize, 0, 1], - [wire_format.Int64ByteSize, 127, 1], - [wire_format.Int64ByteSize, 128, 2], - [wire_format.Int64ByteSize, -1, 10], - # UInt32ByteSize(). - [wire_format.UInt32ByteSize, 0, 1], - [wire_format.UInt32ByteSize, 127, 1], - [wire_format.UInt32ByteSize, 128, 2], - [wire_format.UInt32ByteSize, wire_format.UINT32_MAX, 5], - # UInt64ByteSize(). - [wire_format.UInt64ByteSize, 0, 1], - [wire_format.UInt64ByteSize, 127, 1], - [wire_format.UInt64ByteSize, 128, 2], - [wire_format.UInt64ByteSize, wire_format.UINT64_MAX, 10], - # SInt32ByteSize(). - [wire_format.SInt32ByteSize, 0, 1], - [wire_format.SInt32ByteSize, -1, 1], - [wire_format.SInt32ByteSize, 1, 1], - [wire_format.SInt32ByteSize, -63, 1], - [wire_format.SInt32ByteSize, 63, 1], - [wire_format.SInt32ByteSize, -64, 1], - [wire_format.SInt32ByteSize, 64, 2], - # SInt64ByteSize(). - [wire_format.SInt64ByteSize, 0, 1], - [wire_format.SInt64ByteSize, -1, 1], - [wire_format.SInt64ByteSize, 1, 1], - [wire_format.SInt64ByteSize, -63, 1], - [wire_format.SInt64ByteSize, 63, 1], - [wire_format.SInt64ByteSize, -64, 1], - [wire_format.SInt64ByteSize, 64, 2], - # Fixed32ByteSize(). - [wire_format.Fixed32ByteSize, 0, 4], - [wire_format.Fixed32ByteSize, wire_format.UINT32_MAX, 4], - # Fixed64ByteSize(). - [wire_format.Fixed64ByteSize, 0, 8], - [wire_format.Fixed64ByteSize, wire_format.UINT64_MAX, 8], - # SFixed32ByteSize(). - [wire_format.SFixed32ByteSize, 0, 4], - [wire_format.SFixed32ByteSize, wire_format.INT32_MIN, 4], - [wire_format.SFixed32ByteSize, wire_format.INT32_MAX, 4], - # SFixed64ByteSize(). - [wire_format.SFixed64ByteSize, 0, 8], - [wire_format.SFixed64ByteSize, wire_format.INT64_MIN, 8], - [wire_format.SFixed64ByteSize, wire_format.INT64_MAX, 8], - # FloatByteSize(). - [wire_format.FloatByteSize, 0.0, 4], - [wire_format.FloatByteSize, 1000000000.0, 4], - [wire_format.FloatByteSize, -1000000000.0, 4], - # DoubleByteSize(). - [wire_format.DoubleByteSize, 0.0, 8], - [wire_format.DoubleByteSize, 1000000000.0, 8], - [wire_format.DoubleByteSize, -1000000000.0, 8], - # BoolByteSize(). - [wire_format.BoolByteSize, False, 1], - [wire_format.BoolByteSize, True, 1], - # EnumByteSize(). - [wire_format.EnumByteSize, 0, 1], - [wire_format.EnumByteSize, 127, 1], - [wire_format.EnumByteSize, 128, 2], - [wire_format.EnumByteSize, wire_format.UINT32_MAX, 5], - ] - for args in NUMERIC_ARGS: - self.NumericByteSizeTestHelper(*args) - - # Test strings and bytes. - for byte_size_fn in (wire_format.StringByteSize, wire_format.BytesByteSize): - # 1 byte for tag, 1 byte for length, 3 bytes for contents. - self.assertEqual(5, byte_size_fn(10, 'abc')) - # 2 bytes for tag, 1 byte for length, 3 bytes for contents. - self.assertEqual(6, byte_size_fn(16, 'abc')) - # 2 bytes for tag, 2 bytes for length, 128 bytes for contents. - self.assertEqual(132, byte_size_fn(16, 'a' * 128)) - - # Test UTF-8 string byte size calculation. - # 1 byte for tag, 1 byte for length, 8 bytes for content. - self.assertEqual(10, wire_format.StringByteSize( - 5, b'\xd0\xa2\xd0\xb5\xd1\x81\xd1\x82'.decode('utf-8'))) - - class MockMessage(object): - def __init__(self, byte_size): - self.byte_size = byte_size - def ByteSize(self): - return self.byte_size - - message_byte_size = 10 - mock_message = MockMessage(byte_size=message_byte_size) - # Test groups. - # (2 * 1) bytes for begin and end tags, plus message_byte_size. - self.assertEqual(2 + message_byte_size, - wire_format.GroupByteSize(1, mock_message)) - # (2 * 2) bytes for begin and end tags, plus message_byte_size. - self.assertEqual(4 + message_byte_size, - wire_format.GroupByteSize(16, mock_message)) - - # Test messages. - # 1 byte for tag, plus 1 byte for length, plus contents. - self.assertEqual(2 + mock_message.byte_size, - wire_format.MessageByteSize(1, mock_message)) - # 2 bytes for tag, plus 1 byte for length, plus contents. - self.assertEqual(3 + mock_message.byte_size, - wire_format.MessageByteSize(16, mock_message)) - # 2 bytes for tag, plus 2 bytes for length, plus contents. - mock_message.byte_size = 128 - self.assertEqual(4 + mock_message.byte_size, - wire_format.MessageByteSize(16, mock_message)) - - - # Test message set item byte size. - # 4 bytes for tags, plus 1 byte for length, plus 1 byte for type_id, - # plus contents. - mock_message.byte_size = 10 - self.assertEqual(mock_message.byte_size + 6, - wire_format.MessageSetItemByteSize(1, mock_message)) - - # 4 bytes for tags, plus 2 bytes for length, plus 1 byte for type_id, - # plus contents. - mock_message.byte_size = 128 - self.assertEqual(mock_message.byte_size + 7, - wire_format.MessageSetItemByteSize(1, mock_message)) - - # 4 bytes for tags, plus 2 bytes for length, plus 2 byte for type_id, - # plus contents. - self.assertEqual(mock_message.byte_size + 8, - wire_format.MessageSetItemByteSize(128, mock_message)) - - # Too-long varint. - self.assertRaises(message.EncodeError, - wire_format.UInt64ByteSize, 1, 1 << 128) - - -if __name__ == '__main__': - unittest.main() diff --git a/ext/protobuf/Python/google/protobuf/json_format.py b/ext/protobuf/Python/google/protobuf/json_format.py index 5024ed89d..a04e8aef1 100644 --- a/ext/protobuf/Python/google/protobuf/json_format.py +++ b/ext/protobuf/Python/google/protobuf/json_format.py @@ -53,6 +53,7 @@ from google.protobuf.internal import type_checkers from google.protobuf import descriptor +from google.protobuf import message_factory from google.protobuf import symbol_database @@ -109,7 +110,8 @@ def MessageToJson( names as defined in the .proto file. If False, convert the field names to lowerCamelCase. indent: The JSON object will be pretty-printed with this indent level. - An indent level of 0 or negative will only insert newlines. + An indent level of 0 or negative will only insert newlines. If the + indent level is None, no newlines will be inserted. sort_keys: If True, then the output will be sorted by field names. use_integers_for_enums: If true, print integers instead of enum names. descriptor_pool: A Descriptor Pool for resolving types. If None use the @@ -269,7 +271,7 @@ def _RegularMessageToJsonObject(self, message, js): except ValueError as e: raise SerializeToJsonError( - 'Failed to serialize {0} field: {1}.'.format(field.name, e)) + 'Failed to serialize {0} field: {1}.'.format(field.name, e)) from e return js @@ -286,10 +288,11 @@ def _FieldToJsonObject(self, field, value): if enum_value is not None: return enum_value.name else: - if field.file.syntax == 'proto3': + if field.enum_type.is_closed: + raise SerializeToJsonError('Enum field contains an integer value ' + 'which can not mapped to an enum value.') + else: return value - raise SerializeToJsonError('Enum field contains an integer value ' - 'which can not mapped to an enum value.') elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING: if field.type == descriptor.FieldDescriptor.TYPE_BYTES: # Use base64 Data encoding for bytes @@ -352,8 +355,14 @@ def _ValueMessageToJsonObject(self, message): return None if which == 'list_value': return self._ListValueMessageToJsonObject(message.list_value) - if which == 'struct_value': - value = message.struct_value + if which == 'number_value': + value = message.number_value + if math.isinf(value): + raise ValueError('Fail to serialize Infinity for Value.number_value, ' + 'which would parse as string_value') + if math.isnan(value): + raise ValueError('Fail to serialize NaN for Value.number_value, ' + 'which would parse as string_value') else: value = getattr(message, which) oneof_descriptor = message.DESCRIPTOR.fields_by_name[which] @@ -397,10 +406,11 @@ def _CreateMessageFromTypeUrl(type_url, descriptor_pool): type_name = type_url.split('/')[-1] try: message_descriptor = pool.FindMessageTypeByName(type_name) - except KeyError: + except KeyError as e: raise TypeError( - 'Can not find message descriptor by type_url: {0}'.format(type_url)) - message_class = db.GetPrototype(message_descriptor) + 'Can not find message descriptor by type_url: {0}'.format(type_url) + ) from e + message_class = message_factory.GetMessageClass(message_descriptor) return message_class() @@ -432,7 +442,7 @@ def Parse(text, try: js = json.loads(text, object_pairs_hook=_DuplicateChecker) except ValueError as e: - raise ParseError('Failed to load JSON: {0}.'.format(str(e))) + raise ParseError('Failed to load JSON: {0}.'.format(str(e))) from e return ParseDict(js, message, ignore_unknown_fields, descriptor_pool, max_recursion_depth) @@ -624,13 +634,19 @@ def _ConvertFieldValuePair(self, js, message, path): '{0}.{1}'.format(path, name))) except ParseError as e: if field and field.containing_oneof is None: - raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e else: - raise ParseError(str(e)) + raise ParseError(str(e)) from e except ValueError as e: - raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e except TypeError as e: - raise ParseError('Failed to parse {0} field: {1}.'.format(name, e)) + raise ParseError( + 'Failed to parse {0} field: {1}.'.format(name, e) + ) from e def _ConvertAnyMessage(self, value, message, path): """Convert a JSON representation into Any message.""" @@ -638,14 +654,15 @@ def _ConvertAnyMessage(self, value, message, path): return try: type_url = value['@type'] - except KeyError: + except KeyError as e: raise ParseError( - '@type is missing when parsing any message at {0}'.format(path)) + '@type is missing when parsing any message at {0}'.format(path) + ) from e try: sub_message = _CreateMessageFromTypeUrl(type_url, self.descriptor_pool) except TypeError as e: - raise ParseError('{0} at {1}'.format(e, path)) + raise ParseError('{0} at {1}'.format(e, path)) from e message_descriptor = sub_message.DESCRIPTOR full_name = message_descriptor.full_name if _IsWrapperMessage(message_descriptor): @@ -670,7 +687,7 @@ def _ConvertGenericMessage(self, value, message, path): try: message.FromJsonString(value) except ValueError as e: - raise ParseError('{0} at {1}'.format(e, path)) + raise ParseError('{0} at {1}'.format(e, path)) from e def _ConvertValueMessage(self, value, message, path): """Convert a JSON representation into Value message.""" @@ -794,18 +811,18 @@ def _ConvertScalarFieldValue(value, field, path, require_str=False): try: number = int(value) enum_value = field.enum_type.values_by_number.get(number, None) - except ValueError: + except ValueError as e: raise ParseError('Invalid enum value {0} for enum type {1}'.format( - value, field.enum_type.full_name)) + value, field.enum_type.full_name)) from e if enum_value is None: - if field.file.syntax == 'proto3': - # Proto3 accepts unknown enums. + if field.enum_type.is_closed: + raise ParseError('Invalid enum value {0} for enum type {1}'.format( + value, field.enum_type.full_name)) + else: return number - raise ParseError('Invalid enum value {0} for enum type {1}'.format( - value, field.enum_type.full_name)) return enum_value.number except ParseError as e: - raise ParseError('{0} at {1}'.format(e, path)) + raise ParseError('{0} at {1}'.format(e, path)) from e def _ConvertInteger(value): @@ -857,7 +874,7 @@ def _ConvertFloat(value, field): try: # Assume Python compatible syntax. return float(value) - except ValueError: + except ValueError as e: # Check alternative spellings. if value == _NEG_INFINITY: return float('-inf') @@ -866,7 +883,7 @@ def _ConvertFloat(value, field): elif value == _NAN: return float('nan') else: - raise ParseError('Couldn\'t parse float: {0}'.format(value)) + raise ParseError('Couldn\'t parse float: {0}'.format(value)) from e def _ConvertBool(value, require_str): diff --git a/ext/protobuf/Python/google/protobuf/message.py b/ext/protobuf/Python/google/protobuf/message.py index 76c6802f7..37b9c4054 100644 --- a/ext/protobuf/Python/google/protobuf/message.py +++ b/ext/protobuf/Python/google/protobuf/message.py @@ -74,7 +74,8 @@ class Message(object): __slots__ = [] - #: The :class:`google.protobuf.descriptor.Descriptor` for this message type. + #: The :class:`google.protobuf.Descriptor` + # for this message type. DESCRIPTOR = None def __deepcopy__(self, memo=None): @@ -191,7 +192,7 @@ def MergeFromString(self, serialized): raise NotImplementedError def ParseFromString(self, serialized): - """Parse serialized protocol buffer data into this message. + """Parse serialized protocol buffer data in binary form into this message. Like :func:`MergeFromString()`, except we clear the object first. @@ -311,13 +312,13 @@ def WhichOneof(self, oneof_group): """ raise NotImplementedError - def HasExtension(self, extension_handle): + def HasExtension(self, field_descriptor): """Checks if a certain extension is present for this message. Extensions are retrieved using the :attr:`Extensions` mapping (if present). Args: - extension_handle: The handle for the extension to check. + field_descriptor: The field descriptor for the extension to check. Returns: bool: Whether the extension is present for this message. @@ -329,11 +330,11 @@ def HasExtension(self, extension_handle): """ raise NotImplementedError - def ClearExtension(self, extension_handle): + def ClearExtension(self, field_descriptor): """Clears the contents of a given extension. Args: - extension_handle: The handle for the extension to clear. + field_descriptor: The field descriptor for the extension to clear. """ raise NotImplementedError @@ -367,7 +368,7 @@ def FromString(cls, s): raise NotImplementedError @staticmethod - def RegisterExtension(extension_handle): + def RegisterExtension(field_descriptor): raise NotImplementedError def _SetListener(self, message_listener): diff --git a/ext/protobuf/Python/google/protobuf/message_factory.py b/ext/protobuf/Python/google/protobuf/message_factory.py index 8d6520458..fac1165c5 100644 --- a/ext/protobuf/Python/google/protobuf/message_factory.py +++ b/ext/protobuf/Python/google/protobuf/message_factory.py @@ -39,6 +39,8 @@ __author__ = 'matthewtoia@google.com (Matt Toia)' +import warnings + from google.protobuf.internal import api_implementation from google.protobuf import descriptor_pool from google.protobuf import message @@ -53,6 +55,95 @@ _GENERATED_PROTOCOL_MESSAGE_TYPE = message_impl.GeneratedProtocolMessageType +def GetMessageClass(descriptor): + """Obtains a proto2 message class based on the passed in descriptor. + + Passing a descriptor with a fully qualified name matching a previous + invocation will cause the same class to be returned. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + concrete_class = getattr(descriptor, '_concrete_class', None) + if concrete_class: + return concrete_class + return _InternalCreateMessageClass(descriptor) + + +def GetMessageClassesForFiles(files, pool): + """Gets all the messages from specified files. + + This will find and resolve dependencies, failing if the descriptor + pool cannot satisfy them. + + Args: + files: The file names to extract messages from. + pool: The descriptor pool to find the files including the dependent + files. + + Returns: + A dictionary mapping proto names to the message classes. + """ + result = {} + for file_name in files: + file_desc = pool.FindFileByName(file_name) + for desc in file_desc.message_types_by_name.values(): + result[desc.full_name] = GetMessageClass(desc) + + # While the extension FieldDescriptors are created by the descriptor pool, + # the python classes created in the factory need them to be registered + # explicitly, which is done below. + # + # The call to RegisterExtension will specifically check if the + # extension was already registered on the object and either + # ignore the registration if the original was the same, or raise + # an error if they were different. + + for extension in file_desc.extensions_by_name.values(): + extended_class = GetMessageClass(extension.containing_type) + extended_class.RegisterExtension(extension) + # Recursively load protos for extension field, in order to be able to + # fully represent the extension. This matches the behavior for regular + # fields too. + if extension.message_type: + GetMessageClass(extension.message_type) + return result + + +def _InternalCreateMessageClass(descriptor): + """Builds a proto2 message class based on the passed in descriptor. + + Args: + descriptor: The descriptor to build from. + + Returns: + A class describing the passed in descriptor. + """ + descriptor_name = descriptor.name + result_class = _GENERATED_PROTOCOL_MESSAGE_TYPE( + descriptor_name, + (message.Message,), + { + 'DESCRIPTOR': descriptor, + # If module not set, it wrongly points to message_factory module. + '__module__': None, + }) + for field in descriptor.fields: + if field.message_type: + GetMessageClass(field.message_type) + for extension in result_class.DESCRIPTOR.extensions: + extended_class = GetMessageClass(extension.containing_type) + extended_class.RegisterExtension(extension) + if extension.message_type: + GetMessageClass(extension.message_type) + return result_class + + +# Deprecated. Please use GetMessageClass() or GetMessageClassesForFiles() +# method above instead. class MessageFactory(object): """Factory for creating Proto2 messages from descriptors in a pool.""" @@ -60,9 +151,6 @@ def __init__(self, pool=None): """Initializes a new factory.""" self.pool = pool or descriptor_pool.DescriptorPool() - # local cache of all classes built from protobuf descriptors - self._classes = {} - def GetPrototype(self, descriptor): """Obtains a proto2 message class based on the passed in descriptor. @@ -75,21 +163,17 @@ def GetPrototype(self, descriptor): Returns: A class describing the passed in descriptor. """ - if descriptor not in self._classes: - result_class = self.CreatePrototype(descriptor) - # The assignment to _classes is redundant for the base implementation, but - # might avoid confusion in cases where CreatePrototype gets overridden and - # does not call the base implementation. - self._classes[descriptor] = result_class - return result_class - return self._classes[descriptor] + # TODO(b/258832141): add this warning + # warnings.warn('MessageFactory class is deprecated. Please use ' + # 'GetMessageClass() instead of MessageFactory.GetPrototype. ' + # 'MessageFactory class will be removed after 2024.') + return GetMessageClass(descriptor) def CreatePrototype(self, descriptor): """Builds a proto2 message class based on the passed in descriptor. Don't call this function directly, it always creates a new class. Call - GetPrototype() instead. This method is meant to be overridden in subblasses - to perform additional operations on the newly constructed class. + GetMessageClass() instead. Args: descriptor: The descriptor to build from. @@ -97,30 +181,11 @@ def CreatePrototype(self, descriptor): Returns: A class describing the passed in descriptor. """ - descriptor_name = descriptor.name - result_class = _GENERATED_PROTOCOL_MESSAGE_TYPE( - descriptor_name, - (message.Message,), - { - 'DESCRIPTOR': descriptor, - # If module not set, it wrongly points to message_factory module. - '__module__': None, - }) - result_class._FACTORY = self # pylint: disable=protected-access - # Assign in _classes before doing recursive calls to avoid infinite - # recursion. - self._classes[descriptor] = result_class - for field in descriptor.fields: - if field.message_type: - self.GetPrototype(field.message_type) - for extension in result_class.DESCRIPTOR.extensions: - if extension.containing_type not in self._classes: - self.GetPrototype(extension.containing_type) - extended_class = self._classes[extension.containing_type] - extended_class.RegisterExtension(extension) - if extension.message_type: - self.GetPrototype(extension.message_type) - return result_class + # TODO(b/258832141): add this warning + # warnings.warn('Directly call CreatePrototype is wrong. Please use ' + # 'GetMessageClass() method instead. Directly use ' + # 'CreatePrototype will raise error after July 2023.') + return _InternalCreateMessageClass(descriptor) def GetMessages(self, files): """Gets all the messages from a specified file. @@ -136,39 +201,20 @@ def GetMessages(self, files): any dependent messages as well as any messages defined in the same file as a specified message. """ - result = {} - for file_name in files: - file_desc = self.pool.FindFileByName(file_name) - for desc in file_desc.message_types_by_name.values(): - result[desc.full_name] = self.GetPrototype(desc) - - # While the extension FieldDescriptors are created by the descriptor pool, - # the python classes created in the factory need them to be registered - # explicitly, which is done below. - # - # The call to RegisterExtension will specifically check if the - # extension was already registered on the object and either - # ignore the registration if the original was the same, or raise - # an error if they were different. - - for extension in file_desc.extensions_by_name.values(): - if extension.containing_type not in self._classes: - self.GetPrototype(extension.containing_type) - extended_class = self._classes[extension.containing_type] - extended_class.RegisterExtension(extension) - if extension.message_type: - self.GetPrototype(extension.message_type) - return result - - -_FACTORY = MessageFactory() - - -def GetMessages(file_protos): + # TODO(b/258832141): add this warning + # warnings.warn('MessageFactory class is deprecated. Please use ' + # 'GetMessageClassesForFiles() instead of ' + # 'MessageFactory.GetMessages(). MessageFactory class ' + # 'will be removed after 2024.') + return GetMessageClassesForFiles(files, self.pool) + + +def GetMessages(file_protos, pool=None): """Builds a dictionary of all the messages available in a set of files. Args: file_protos: Iterable of FileDescriptorProto to build messages out of. + pool: The descriptor pool to add the file protos. Returns: A dictionary mapping proto names to the message classes. This will include @@ -177,13 +223,15 @@ def GetMessages(file_protos): """ # The cpp implementation of the protocol buffer library requires to add the # message in topological order of the dependency graph. + des_pool = pool or descriptor_pool.DescriptorPool() file_by_name = {file_proto.name: file_proto for file_proto in file_protos} def _AddFile(file_proto): for dependency in file_proto.dependency: if dependency in file_by_name: # Remove from elements to be visited, in order to cut cycles. _AddFile(file_by_name.pop(dependency)) - _FACTORY.pool.Add(file_proto) + des_pool.Add(file_proto) while file_by_name: _AddFile(file_by_name.popitem()[1]) - return _FACTORY.GetMessages([file_proto.name for file_proto in file_protos]) + return GetMessageClassesForFiles( + [file_proto.name for file_proto in file_protos], des_pool) diff --git a/ext/protobuf/Python/google/protobuf/proto_builder.py b/ext/protobuf/Python/google/protobuf/proto_builder.py index a4667ce63..8dab8b3ee 100644 --- a/ext/protobuf/Python/google/protobuf/proto_builder.py +++ b/ext/protobuf/Python/google/protobuf/proto_builder.py @@ -36,22 +36,23 @@ from google.protobuf import descriptor_pb2 from google.protobuf import descriptor +from google.protobuf import descriptor_pool from google.protobuf import message_factory -def _GetMessageFromFactory(factory, full_name): +def _GetMessageFromFactory(pool, full_name): """Get a proto class from the MessageFactory by name. Args: - factory: a MessageFactory instance. + pool: a descriptor pool. full_name: str, the fully qualified name of the proto type. Returns: A class, for the type identified by full_name. Raises: KeyError, if the proto is not found in the factory's descriptor pool. """ - proto_descriptor = factory.pool.FindMessageTypeByName(full_name) - proto_cls = factory.GetPrototype(proto_descriptor) + proto_descriptor = pool.FindMessageTypeByName(full_name) + proto_cls = message_factory.GetMessageClass(proto_descriptor) return proto_cls @@ -69,11 +70,10 @@ def MakeSimpleProtoClass(fields, full_name=None, pool=None): Returns: a class, the new protobuf class with a FileDescriptor. """ - factory = message_factory.MessageFactory(pool=pool) - + pool_instance = pool or descriptor_pool.DescriptorPool() if full_name is not None: try: - proto_cls = _GetMessageFromFactory(factory, full_name) + proto_cls = _GetMessageFromFactory(pool_instance, full_name) return proto_cls except KeyError: # The factory's DescriptorPool doesn't know about this class yet. @@ -99,16 +99,16 @@ def MakeSimpleProtoClass(fields, full_name=None, pool=None): full_name = ('net.proto2.python.public.proto_builder.AnonymousProto_' + fields_hash.hexdigest()) try: - proto_cls = _GetMessageFromFactory(factory, full_name) + proto_cls = _GetMessageFromFactory(pool_instance, full_name) return proto_cls except KeyError: # The factory's DescriptorPool doesn't know about this class yet. pass # This is the first time we see this proto: add a new descriptor to the pool. - factory.pool.Add( + pool_instance.Add( _MakeFileDescriptorProto(proto_file_name, full_name, field_items)) - return _GetMessageFromFactory(factory, full_name) + return _GetMessageFromFactory(pool_instance, full_name) def _MakeFileDescriptorProto(proto_file_name, full_name, field_items): diff --git a/ext/protobuf/Python/google/protobuf/reflection.py b/ext/protobuf/Python/google/protobuf/reflection.py index 81e18859a..1627669b9 100644 --- a/ext/protobuf/Python/google/protobuf/reflection.py +++ b/ext/protobuf/Python/google/protobuf/reflection.py @@ -92,4 +92,4 @@ def MakeClass(descriptor): # Original implementation leads to duplicate message classes, which won't play # well with extensions. Message factory info is also missing. # Redirect to message_factory. - return symbol_database.Default().GetPrototype(descriptor) + return message_factory.GetMessageClass(descriptor) diff --git a/ext/protobuf/Python/google/protobuf/source_context_pb2.py b/ext/protobuf/Python/google/protobuf/source_context_pb2.py index 7a6d0c1aa..51d91e7fd 100644 --- a/ext/protobuf/Python/google/protobuf/source_context_pb2.py +++ b/ext/protobuf/Python/google/protobuf/source_context_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$google/protobuf/source_context.proto\x12\x0fgoogle.protobuf\",\n\rSourceContext\x12\x1b\n\tfile_name\x18\x01 \x01(\tR\x08\x66ileNameB\x8a\x01\n\x13\x63om.google.protobufB\x12SourceContextProtoP\x01Z6google.golang.org/protobuf/types/known/sourcecontextpb\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.source_context_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.source_context_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\022SourceContextProtoP\001Z6google.golang.org/protobuf/types/known/sourcecontextpb\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _SOURCECONTEXT._serialized_start=57 - _SOURCECONTEXT._serialized_end=101 + _globals['_SOURCECONTEXT']._serialized_start=57 + _globals['_SOURCECONTEXT']._serialized_end=101 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/struct_pb2.py b/ext/protobuf/Python/google/protobuf/struct_pb2.py index 981de9614..84ad3bbde 100644 --- a/ext/protobuf/Python/google/protobuf/struct_pb2.py +++ b/ext/protobuf/Python/google/protobuf/struct_pb2.py @@ -15,22 +15,23 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1cgoogle/protobuf/struct.proto\x12\x0fgoogle.protobuf\"\x98\x01\n\x06Struct\x12;\n\x06\x66ields\x18\x01 \x03(\x0b\x32#.google.protobuf.Struct.FieldsEntryR\x06\x66ields\x1aQ\n\x0b\x46ieldsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x16.google.protobuf.ValueR\x05value:\x02\x38\x01\"\xb2\x02\n\x05Value\x12;\n\nnull_value\x18\x01 \x01(\x0e\x32\x1a.google.protobuf.NullValueH\x00R\tnullValue\x12#\n\x0cnumber_value\x18\x02 \x01(\x01H\x00R\x0bnumberValue\x12#\n\x0cstring_value\x18\x03 \x01(\tH\x00R\x0bstringValue\x12\x1f\n\nbool_value\x18\x04 \x01(\x08H\x00R\tboolValue\x12<\n\x0cstruct_value\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructH\x00R\x0bstructValue\x12;\n\nlist_value\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.ListValueH\x00R\tlistValueB\x06\n\x04kind\";\n\tListValue\x12.\n\x06values\x18\x01 \x03(\x0b\x32\x16.google.protobuf.ValueR\x06values*\x1b\n\tNullValue\x12\x0e\n\nNULL_VALUE\x10\x00\x42\x7f\n\x13\x63om.google.protobufB\x0bStructProtoP\x01Z/google.golang.org/protobuf/types/known/structpb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.struct_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.struct_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\013StructProtoP\001Z/google.golang.org/protobuf/types/known/structpb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' _STRUCT_FIELDSENTRY._options = None _STRUCT_FIELDSENTRY._serialized_options = b'8\001' - _NULLVALUE._serialized_start=574 - _NULLVALUE._serialized_end=601 - _STRUCT._serialized_start=50 - _STRUCT._serialized_end=202 - _STRUCT_FIELDSENTRY._serialized_start=121 - _STRUCT_FIELDSENTRY._serialized_end=202 - _VALUE._serialized_start=205 - _VALUE._serialized_end=511 - _LISTVALUE._serialized_start=513 - _LISTVALUE._serialized_end=572 + _globals['_NULLVALUE']._serialized_start=574 + _globals['_NULLVALUE']._serialized_end=601 + _globals['_STRUCT']._serialized_start=50 + _globals['_STRUCT']._serialized_end=202 + _globals['_STRUCT_FIELDSENTRY']._serialized_start=121 + _globals['_STRUCT_FIELDSENTRY']._serialized_end=202 + _globals['_VALUE']._serialized_start=205 + _globals['_VALUE']._serialized_end=511 + _globals['_LISTVALUE']._serialized_start=513 + _globals['_LISTVALUE']._serialized_end=572 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/symbol_database.py b/ext/protobuf/Python/google/protobuf/symbol_database.py index fdcf8cf06..390c49810 100644 --- a/ext/protobuf/Python/google/protobuf/symbol_database.py +++ b/ext/protobuf/Python/google/protobuf/symbol_database.py @@ -57,15 +57,41 @@ my_message_instance = db.GetSymbol('MyMessage')() """ +import warnings from google.protobuf.internal import api_implementation from google.protobuf import descriptor_pool from google.protobuf import message_factory -class SymbolDatabase(message_factory.MessageFactory): +class SymbolDatabase(): """A database of Python generated symbols.""" + # local cache of registered classes. + _classes = {} + + def __init__(self, pool=None): + """Initializes a new SymbolDatabase.""" + self.pool = pool or descriptor_pool.DescriptorPool() + + def GetPrototype(self, descriptor): + warnings.warn('SymbolDatabase.GetPrototype() is deprecated. Please ' + 'use message_factory.GetMessageClass() instead. ' + 'SymbolDatabase.GetPrototype() will be removed soon.') + return message_factory.GetMessageClass(descriptor) + + def CreatePrototype(self, descriptor): + warnings.warn('Directly call CreatePrototype() is wrong. Please use ' + 'message_factory.GetMessageClass() instead. ' + 'SymbolDatabase.CreatePrototype() will be removed soon.') + return message_factory._InternalCreateMessageClass(descriptor) + + def GetMessages(self, files): + warnings.warn('SymbolDatabase.GetMessages() is deprecated. Please use ' + 'message_factory.GetMessageClassedForFiles() instead. ' + 'SymbolDatabase.GetMessages() will be removed soon.') + return message_factory.GetMessageClassedForFiles(files, self.pool) + def RegisterMessage(self, message): """Registers the given message type in the local database. diff --git a/ext/protobuf/Python/google/protobuf/text_encoding.py b/ext/protobuf/Python/google/protobuf/text_encoding.py index 759cf11f6..1955b6a3c 100644 --- a/ext/protobuf/Python/google/protobuf/text_encoding.py +++ b/ext/protobuf/Python/google/protobuf/text_encoding.py @@ -53,8 +53,7 @@ del byte, string -def CEscape(text, as_utf8): - # type: (...) -> str +def CEscape(text, as_utf8) -> str: """Escape a bytes string for use in an text protocol buffer. Args: @@ -83,8 +82,7 @@ def CEscape(text, as_utf8): _CUNESCAPE_HEX = re.compile(r'(\\+)x([0-9a-fA-F])(?![0-9a-fA-F])') -def CUnescape(text): - # type: (str) -> bytes +def CUnescape(text: str) -> bytes: """Unescape a text string with C-style escape sequences to UTF-8 bytes. Args: diff --git a/ext/protobuf/Python/google/protobuf/text_format.py b/ext/protobuf/Python/google/protobuf/text_format.py index a6d8bcf64..e1a5ad544 100644 --- a/ext/protobuf/Python/google/protobuf/text_format.py +++ b/ext/protobuf/Python/google/protobuf/text_format.py @@ -67,6 +67,7 @@ _FLOAT_NAN = re.compile('nanf?$', re.IGNORECASE) _QUOTES = frozenset(("'", '"')) _ANY_FULL_TYPE_NAME = 'google.protobuf.Any' +_DEBUG_STRING_SILENT_MARKER = '\t ' class Error(Exception): @@ -125,8 +126,7 @@ def MessageToString( indent=0, message_formatter=None, print_unknown_fields=False, - force_colon=False): - # type: (...) -> str + force_colon=False) -> str: """Convert protobuf message to text format. Double values can be formatted compactly with 15 digits of @@ -191,8 +191,7 @@ def MessageToString( return result -def MessageToBytes(message, **kwargs): - # type: (...) -> bytes +def MessageToBytes(message, **kwargs) -> bytes: """Convert protobuf message to encoded text format. See MessageToString.""" text = MessageToString(message, **kwargs) if isinstance(text, bytes): @@ -331,17 +330,16 @@ def _BuildMessageFromTypeName(type_name, descriptor_pool): if descriptor_pool is None: from google.protobuf import descriptor_pool as pool_mod descriptor_pool = pool_mod.Default() - from google.protobuf import symbol_database - database = symbol_database.Default() + from google.protobuf import message_factory try: message_descriptor = descriptor_pool.FindMessageTypeByName(type_name) except KeyError: return None - message_type = database.GetPrototype(message_descriptor) + message_type = message_factory.GetMessageClass(message_descriptor) return message_type() -# These values must match WireType enum in google/protobuf/wire_format.h. +# These values must match WireType enum in //google/protobuf/wire_format.h. WIRETYPE_LENGTH_DELIMITED = 2 WIRETYPE_START_GROUP = 3 @@ -558,7 +556,7 @@ def _PrintFieldName(self, field): # For groups, use the capitalized name. out.write(field.message_type.name) else: - out.write(field.name) + out.write(field.name) if (self.force_colon or field.cpp_type != descriptor.FieldDescriptor.CPPTYPE_MESSAGE): @@ -856,10 +854,15 @@ def _ParseOrMerge(self, lines, message): ParseError: On text parsing problems. """ # Tokenize expects native str lines. - str_lines = ( - line if isinstance(line, str) else line.decode('utf-8') - for line in lines) - tokenizer = Tokenizer(str_lines) + try: + str_lines = ( + line if isinstance(line, str) else line.decode('utf-8') + for line in lines) + tokenizer = Tokenizer(str_lines) + except UnicodeDecodeError as e: + raise ParseError from e + if message: + self.root_type = message.DESCRIPTOR.full_name while not tokenizer.AtEnd(): self._MergeField(tokenizer, message) @@ -879,6 +882,8 @@ def _MergeField(self, tokenizer, message): type_url_prefix, packed_type_name = self._ConsumeAnyTypeUrl(tokenizer) tokenizer.Consume(']') tokenizer.TryConsume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + type_url_prefix + '/' + packed_type_name) if tokenizer.TryConsume('<'): expanded_any_end_token = '>' else: @@ -917,8 +922,6 @@ def _MergeField(self, tokenizer, message): # pylint: disable=protected-access field = message.Extensions._FindExtensionByName(name) # pylint: enable=protected-access - - if not field: if self.allow_unknown_extension: field = None @@ -978,9 +981,13 @@ def _MergeField(self, tokenizer, message): if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE: tokenizer.TryConsume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + field.full_name) merger = self._MergeMessageField else: tokenizer.Consume(':') + self._DetectSilentMarker(tokenizer, message_descriptor.full_name, + field.full_name) merger = self._MergeScalarField if (field.label == descriptor.FieldDescriptor.LABEL_REPEATED and @@ -998,13 +1005,19 @@ def _MergeField(self, tokenizer, message): else: # Proto field is unknown. assert (self.allow_unknown_extension or self.allow_unknown_field) - _SkipFieldContents(tokenizer) + self._SkipFieldContents(tokenizer, name, message_descriptor.full_name) # For historical reasons, fields may optionally be separated by commas or # semicolons. if not tokenizer.TryConsume(','): tokenizer.TryConsume(';') + def _LogSilentMarker(self, immediate_message_type, field_name): + pass + + def _DetectSilentMarker(self, tokenizer, immediate_message_type, field_name): + if tokenizer.contains_silent_marker_before_current_token: + self._LogSilentMarker(immediate_message_type, field_name) def _ConsumeAnyTypeUrl(self, tokenizer): """Consumes a google.protobuf.Any type URL and returns the type name.""" @@ -1079,12 +1092,6 @@ def _MergeMessageField(self, tokenizer, message, field): else: getattr(message, field.name)[sub_message.key] = sub_message.value - @staticmethod - def _IsProto3Syntax(message): - message_descriptor = message.DESCRIPTOR - return (hasattr(message_descriptor, 'syntax') and - message_descriptor.syntax == 'proto3') - def _MergeScalarField(self, tokenizer, message, field): """Merges a single scalar field into a message. @@ -1136,7 +1143,7 @@ def _MergeScalarField(self, tokenizer, message, field): else: if field.is_extension: if (not self._allow_multiple_scalars and - not self._IsProto3Syntax(message) and + field.has_presence and message.HasExtension(field)): raise tokenizer.ParseErrorPreviousToken( 'Message type "%s" should not have multiple "%s" extensions.' % @@ -1146,12 +1153,12 @@ def _MergeScalarField(self, tokenizer, message, field): else: duplicate_error = False if not self._allow_multiple_scalars: - if self._IsProto3Syntax(message): - # Proto3 doesn't represent presence so we try best effort to check - # multiple scalars by compare to default values. - duplicate_error = bool(getattr(message, field.name)) - else: + if field.has_presence: duplicate_error = message.HasField(field.name) + else: + # For field that doesn't represent presence, try best effort to + # check multiple scalars by compare to default values. + duplicate_error = bool(getattr(message, field.name)) if duplicate_error: raise tokenizer.ParseErrorPreviousToken( @@ -1160,105 +1167,117 @@ def _MergeScalarField(self, tokenizer, message, field): else: setattr(message, field.name, value) + def _SkipFieldContents(self, tokenizer, field_name, immediate_message_type): + """Skips over contents (value or message) of a field. -def _SkipFieldContents(tokenizer): - """Skips over contents (value or message) of a field. - - Args: - tokenizer: A tokenizer to parse the field name and values. - """ - # Try to guess the type of this field. - # If this field is not a message, there should be a ":" between the - # field name and the field value and also the field value should not - # start with "{" or "<" which indicates the beginning of a message body. - # If there is no ":" or there is a "{" or "<" after ":", this field has - # to be a message or the input is ill-formed. - if tokenizer.TryConsume( - ':') and not tokenizer.LookingAt('{') and not tokenizer.LookingAt('<'): - if tokenizer.LookingAt('['): - _SkipRepeatedFieldValue(tokenizer) + Args: + tokenizer: A tokenizer to parse the field name and values. + field_name: The field name currently being parsed. + immediate_message_type: The type of the message immediately containing + the silent marker. + """ + # Try to guess the type of this field. + # If this field is not a message, there should be a ":" between the + # field name and the field value and also the field value should not + # start with "{" or "<" which indicates the beginning of a message body. + # If there is no ":" or there is a "{" or "<" after ":", this field has + # to be a message or the input is ill-formed. + if tokenizer.TryConsume( + ':') and not tokenizer.LookingAt('{') and not tokenizer.LookingAt('<'): + self._DetectSilentMarker(tokenizer, immediate_message_type, field_name) + if tokenizer.LookingAt('['): + self._SkipRepeatedFieldValue(tokenizer) + else: + self._SkipFieldValue(tokenizer) else: - _SkipFieldValue(tokenizer) - else: - _SkipFieldMessage(tokenizer) - - -def _SkipField(tokenizer): - """Skips over a complete field (name and value/message). - - Args: - tokenizer: A tokenizer to parse the field name and values. - """ - if tokenizer.TryConsume('['): - # Consume extension name. - tokenizer.ConsumeIdentifier() - while tokenizer.TryConsume('.'): - tokenizer.ConsumeIdentifier() - tokenizer.Consume(']') - else: - tokenizer.ConsumeIdentifierOrNumber() - - _SkipFieldContents(tokenizer) + self._DetectSilentMarker(tokenizer, immediate_message_type, field_name) + self._SkipFieldMessage(tokenizer, immediate_message_type) - # For historical reasons, fields may optionally be separated by commas or - # semicolons. - if not tokenizer.TryConsume(','): - tokenizer.TryConsume(';') + def _SkipField(self, tokenizer, immediate_message_type): + """Skips over a complete field (name and value/message). + Args: + tokenizer: A tokenizer to parse the field name and values. + immediate_message_type: The type of the message immediately containing + the silent marker. + """ + field_name = '' + if tokenizer.TryConsume('['): + # Consume extension or google.protobuf.Any type URL + field_name += '[' + tokenizer.ConsumeIdentifier() + num_identifiers = 1 + while tokenizer.TryConsume('.'): + field_name += '.' + tokenizer.ConsumeIdentifier() + num_identifiers += 1 + # This is possibly a type URL for an Any message. + if num_identifiers == 3 and tokenizer.TryConsume('/'): + field_name += '/' + tokenizer.ConsumeIdentifier() + while tokenizer.TryConsume('.'): + field_name += '.' + tokenizer.ConsumeIdentifier() + tokenizer.Consume(']') + field_name += ']' + else: + field_name += tokenizer.ConsumeIdentifierOrNumber() -def _SkipFieldMessage(tokenizer): - """Skips over a field message. - - Args: - tokenizer: A tokenizer to parse the field name and values. - """ + self._SkipFieldContents(tokenizer, field_name, immediate_message_type) - if tokenizer.TryConsume('<'): - delimiter = '>' - else: - tokenizer.Consume('{') - delimiter = '}' + # For historical reasons, fields may optionally be separated by commas or + # semicolons. + if not tokenizer.TryConsume(','): + tokenizer.TryConsume(';') - while not tokenizer.LookingAt('>') and not tokenizer.LookingAt('}'): - _SkipField(tokenizer) + def _SkipFieldMessage(self, tokenizer, immediate_message_type): + """Skips over a field message. - tokenizer.Consume(delimiter) + Args: + tokenizer: A tokenizer to parse the field name and values. + immediate_message_type: The type of the message immediately containing + the silent marker + """ + if tokenizer.TryConsume('<'): + delimiter = '>' + else: + tokenizer.Consume('{') + delimiter = '}' + while not tokenizer.LookingAt('>') and not tokenizer.LookingAt('}'): + self._SkipField(tokenizer, immediate_message_type) -def _SkipFieldValue(tokenizer): - """Skips over a field value. + tokenizer.Consume(delimiter) - Args: - tokenizer: A tokenizer to parse the field name and values. + def _SkipFieldValue(self, tokenizer): + """Skips over a field value. - Raises: - ParseError: In case an invalid field value is found. - """ - # String/bytes tokens can come in multiple adjacent string literals. - # If we can consume one, consume as many as we can. - if tokenizer.TryConsumeByteString(): - while tokenizer.TryConsumeByteString(): - pass - return + Args: + tokenizer: A tokenizer to parse the field name and values. - if (not tokenizer.TryConsumeIdentifier() and - not _TryConsumeInt64(tokenizer) and not _TryConsumeUint64(tokenizer) and - not tokenizer.TryConsumeFloat()): - raise ParseError('Invalid field value: ' + tokenizer.token) + Raises: + ParseError: In case an invalid field value is found. + """ + # String/bytes tokens can come in multiple adjacent string literals. + # If we can consume one, consume as many as we can. + if tokenizer.TryConsumeByteString(): + while tokenizer.TryConsumeByteString(): + pass + return + if (not tokenizer.TryConsumeIdentifier() and + not _TryConsumeInt64(tokenizer) and not _TryConsumeUint64(tokenizer) and + not tokenizer.TryConsumeFloat()): + raise ParseError('Invalid field value: ' + tokenizer.token) -def _SkipRepeatedFieldValue(tokenizer): - """Skips over a repeated field value. + def _SkipRepeatedFieldValue(self, tokenizer): + """Skips over a repeated field value. - Args: - tokenizer: A tokenizer to parse the field value. - """ - tokenizer.Consume('[') - if not tokenizer.LookingAt(']'): - _SkipFieldValue(tokenizer) - while tokenizer.TryConsume(','): - _SkipFieldValue(tokenizer) - tokenizer.Consume(']') + Args: + tokenizer: A tokenizer to parse the field value. + """ + tokenizer.Consume('[') + if not tokenizer.LookingAt(']'): + self._SkipFieldValue(tokenizer) + while tokenizer.TryConsume(','): + self._SkipFieldValue(tokenizer) + tokenizer.Consume(']') class Tokenizer(object): @@ -1299,6 +1318,8 @@ def __init__(self, lines, skip_comments=True): self._skip_comments = skip_comments self._whitespace_pattern = (skip_comments and self._WHITESPACE_OR_COMMENT or self._WHITESPACE) + self.contains_silent_marker_before_current_token = False + self._SkipWhitespace() self.NextToken() @@ -1331,6 +1352,8 @@ def _SkipWhitespace(self): match = self._whitespace_pattern.match(self._current_line, self._column) if not match: break + self.contains_silent_marker_before_current_token = match.group(0) == ( + ' ' + _DEBUG_STRING_SILENT_MARKER) length = len(match.group(0)) self._column += length @@ -1583,6 +1606,7 @@ def NextToken(self): """Reads the next meaningful token.""" self._previous_line = self._line self._previous_column = self._column + self.contains_silent_marker_before_current_token = False self._column += len(self.token) self._SkipWhitespace() @@ -1829,12 +1853,8 @@ def ParseEnum(field, value): raise ValueError('Enum type "%s" has no value named %s.' % (enum_descriptor.full_name, value)) else: - # Numeric value. - if hasattr(field.file, 'syntax'): - # Attribute is checked for compatibility. - if field.file.syntax == 'proto3': - # Proto3 accept numeric unknown enums. - return number + if not field.enum_type.is_closed: + return number enum_value = enum_descriptor.values_by_number.get(number, None) if enum_value is None: raise ValueError('Enum type "%s" has no value with number %d.' % diff --git a/ext/protobuf/Python/google/protobuf/timestamp_pb2.py b/ext/protobuf/Python/google/protobuf/timestamp_pb2.py index 1def5d33b..81aec8776 100644 --- a/ext/protobuf/Python/google/protobuf/timestamp_pb2.py +++ b/ext/protobuf/Python/google/protobuf/timestamp_pb2.py @@ -15,12 +15,13 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fgoogle/protobuf/timestamp.proto\x12\x0fgoogle.protobuf\";\n\tTimestamp\x12\x18\n\x07seconds\x18\x01 \x01(\x03R\x07seconds\x12\x14\n\x05nanos\x18\x02 \x01(\x05R\x05nanosB\x85\x01\n\x13\x63om.google.protobufB\x0eTimestampProtoP\x01Z2google.golang.org/protobuf/types/known/timestamppb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.timestamp_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.timestamp_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\016TimestampProtoP\001Z2google.golang.org/protobuf/types/known/timestamppb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _TIMESTAMP._serialized_start=52 - _TIMESTAMP._serialized_end=111 + _globals['_TIMESTAMP']._serialized_start=52 + _globals['_TIMESTAMP']._serialized_end=111 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/type_pb2.py b/ext/protobuf/Python/google/protobuf/type_pb2.py index 0764ca2a0..ee7ee569a 100644 --- a/ext/protobuf/Python/google/protobuf/type_pb2.py +++ b/ext/protobuf/Python/google/protobuf/type_pb2.py @@ -17,26 +17,27 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1agoogle/protobuf/type.proto\x12\x0fgoogle.protobuf\x1a\x19google/protobuf/any.proto\x1a$google/protobuf/source_context.proto\"\x8d\x02\n\x04Type\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12.\n\x06\x66ields\x18\x02 \x03(\x0b\x32\x16.google.protobuf.FieldR\x06\x66ields\x12\x16\n\x06oneofs\x18\x03 \x03(\tR\x06oneofs\x12\x31\n\x07options\x18\x04 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x45\n\x0esource_context\x18\x05 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12/\n\x06syntax\x18\x06 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"\xb4\x06\n\x05\x46ield\x12/\n\x04kind\x18\x01 \x01(\x0e\x32\x1b.google.protobuf.Field.KindR\x04kind\x12\x44\n\x0b\x63\x61rdinality\x18\x02 \x01(\x0e\x32\".google.protobuf.Field.CardinalityR\x0b\x63\x61rdinality\x12\x16\n\x06number\x18\x03 \x01(\x05R\x06number\x12\x12\n\x04name\x18\x04 \x01(\tR\x04name\x12\x19\n\x08type_url\x18\x06 \x01(\tR\x07typeUrl\x12\x1f\n\x0boneof_index\x18\x07 \x01(\x05R\noneofIndex\x12\x16\n\x06packed\x18\x08 \x01(\x08R\x06packed\x12\x31\n\x07options\x18\t \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x1b\n\tjson_name\x18\n \x01(\tR\x08jsonName\x12#\n\rdefault_value\x18\x0b \x01(\tR\x0c\x64\x65\x66\x61ultValue\"\xc8\x02\n\x04Kind\x12\x10\n\x0cTYPE_UNKNOWN\x10\x00\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"t\n\x0b\x43\x61rdinality\x12\x17\n\x13\x43\x41RDINALITY_UNKNOWN\x10\x00\x12\x18\n\x14\x43\x41RDINALITY_OPTIONAL\x10\x01\x12\x18\n\x14\x43\x41RDINALITY_REQUIRED\x10\x02\x12\x18\n\x14\x43\x41RDINALITY_REPEATED\x10\x03\"\xff\x01\n\x04\x45num\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x38\n\tenumvalue\x18\x02 \x03(\x0b\x32\x1a.google.protobuf.EnumValueR\tenumvalue\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\x12\x45\n\x0esource_context\x18\x04 \x01(\x0b\x32\x1e.google.protobuf.SourceContextR\rsourceContext\x12/\n\x06syntax\x18\x05 \x01(\x0e\x32\x17.google.protobuf.SyntaxR\x06syntax\"j\n\tEnumValue\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06number\x18\x02 \x01(\x05R\x06number\x12\x31\n\x07options\x18\x03 \x03(\x0b\x32\x17.google.protobuf.OptionR\x07options\"H\n\x06Option\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.AnyR\x05value*.\n\x06Syntax\x12\x11\n\rSYNTAX_PROTO2\x10\x00\x12\x11\n\rSYNTAX_PROTO3\x10\x01\x42{\n\x13\x63om.google.protobufB\tTypeProtoP\x01Z-google.golang.org/protobuf/types/known/typepb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.type_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.type_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\tTypeProtoP\001Z-google.golang.org/protobuf/types/known/typepb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _SYNTAX._serialized_start=1647 - _SYNTAX._serialized_end=1693 - _TYPE._serialized_start=113 - _TYPE._serialized_end=382 - _FIELD._serialized_start=385 - _FIELD._serialized_end=1205 - _FIELD_KIND._serialized_start=759 - _FIELD_KIND._serialized_end=1087 - _FIELD_CARDINALITY._serialized_start=1089 - _FIELD_CARDINALITY._serialized_end=1205 - _ENUM._serialized_start=1208 - _ENUM._serialized_end=1463 - _ENUMVALUE._serialized_start=1465 - _ENUMVALUE._serialized_end=1571 - _OPTION._serialized_start=1573 - _OPTION._serialized_end=1645 + _globals['_SYNTAX']._serialized_start=1647 + _globals['_SYNTAX']._serialized_end=1693 + _globals['_TYPE']._serialized_start=113 + _globals['_TYPE']._serialized_end=382 + _globals['_FIELD']._serialized_start=385 + _globals['_FIELD']._serialized_end=1205 + _globals['_FIELD_KIND']._serialized_start=759 + _globals['_FIELD_KIND']._serialized_end=1087 + _globals['_FIELD_CARDINALITY']._serialized_start=1089 + _globals['_FIELD_CARDINALITY']._serialized_end=1205 + _globals['_ENUM']._serialized_start=1208 + _globals['_ENUM']._serialized_end=1463 + _globals['_ENUMVALUE']._serialized_start=1465 + _globals['_ENUMVALUE']._serialized_end=1571 + _globals['_OPTION']._serialized_start=1573 + _globals['_OPTION']._serialized_end=1645 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/google/protobuf/wrappers_pb2.py b/ext/protobuf/Python/google/protobuf/wrappers_pb2.py index 6f850dc79..b11eddf27 100644 --- a/ext/protobuf/Python/google/protobuf/wrappers_pb2.py +++ b/ext/protobuf/Python/google/protobuf/wrappers_pb2.py @@ -15,28 +15,29 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1egoogle/protobuf/wrappers.proto\x12\x0fgoogle.protobuf\"#\n\x0b\x44oubleValue\x12\x14\n\x05value\x18\x01 \x01(\x01R\x05value\"\"\n\nFloatValue\x12\x14\n\x05value\x18\x01 \x01(\x02R\x05value\"\"\n\nInt64Value\x12\x14\n\x05value\x18\x01 \x01(\x03R\x05value\"#\n\x0bUInt64Value\x12\x14\n\x05value\x18\x01 \x01(\x04R\x05value\"\"\n\nInt32Value\x12\x14\n\x05value\x18\x01 \x01(\x05R\x05value\"#\n\x0bUInt32Value\x12\x14\n\x05value\x18\x01 \x01(\rR\x05value\"!\n\tBoolValue\x12\x14\n\x05value\x18\x01 \x01(\x08R\x05value\"#\n\x0bStringValue\x12\x14\n\x05value\x18\x01 \x01(\tR\x05value\"\"\n\nBytesValue\x12\x14\n\x05value\x18\x01 \x01(\x0cR\x05valueB\x83\x01\n\x13\x63om.google.protobufB\rWrappersProtoP\x01Z1google.golang.org/protobuf/types/known/wrapperspb\xf8\x01\x01\xa2\x02\x03GPB\xaa\x02\x1eGoogle.Protobuf.WellKnownTypesb\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.wrappers_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google.protobuf.wrappers_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\023com.google.protobufB\rWrappersProtoP\001Z1google.golang.org/protobuf/types/known/wrapperspb\370\001\001\242\002\003GPB\252\002\036Google.Protobuf.WellKnownTypes' - _DOUBLEVALUE._serialized_start=51 - _DOUBLEVALUE._serialized_end=86 - _FLOATVALUE._serialized_start=88 - _FLOATVALUE._serialized_end=122 - _INT64VALUE._serialized_start=124 - _INT64VALUE._serialized_end=158 - _UINT64VALUE._serialized_start=160 - _UINT64VALUE._serialized_end=195 - _INT32VALUE._serialized_start=197 - _INT32VALUE._serialized_end=231 - _UINT32VALUE._serialized_start=233 - _UINT32VALUE._serialized_end=268 - _BOOLVALUE._serialized_start=270 - _BOOLVALUE._serialized_end=303 - _STRINGVALUE._serialized_start=305 - _STRINGVALUE._serialized_end=340 - _BYTESVALUE._serialized_start=342 - _BYTESVALUE._serialized_end=376 + _globals['_DOUBLEVALUE']._serialized_start=51 + _globals['_DOUBLEVALUE']._serialized_end=86 + _globals['_FLOATVALUE']._serialized_start=88 + _globals['_FLOATVALUE']._serialized_end=122 + _globals['_INT64VALUE']._serialized_start=124 + _globals['_INT64VALUE']._serialized_end=158 + _globals['_UINT64VALUE']._serialized_start=160 + _globals['_UINT64VALUE']._serialized_end=195 + _globals['_INT32VALUE']._serialized_start=197 + _globals['_INT32VALUE']._serialized_end=231 + _globals['_UINT32VALUE']._serialized_start=233 + _globals['_UINT32VALUE']._serialized_end=268 + _globals['_BOOLVALUE']._serialized_start=270 + _globals['_BOOLVALUE']._serialized_end=303 + _globals['_STRINGVALUE']._serialized_start=305 + _globals['_STRINGVALUE']._serialized_end=340 + _globals['_BYTESVALUE']._serialized_start=342 + _globals['_BYTESVALUE']._serialized_end=376 # @@protoc_insertion_point(module_scope) diff --git a/ext/protobuf/Python/six.py b/ext/protobuf/Python/six.py index 83f69783d..4e15675d8 100644 --- a/ext/protobuf/Python/six.py +++ b/ext/protobuf/Python/six.py @@ -29,7 +29,7 @@ import types __author__ = "Benjamin Peterson " -__version__ = "1.15.0" +__version__ = "1.16.0" # Useful for very coarse version differentiation. @@ -71,6 +71,11 @@ def __len__(self): MAXSIZE = int((1 << 63) - 1) del X +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + def _add_doc(func, doc): """Add documentation to a function.""" @@ -186,6 +191,11 @@ def find_module(self, fullname, path=None): return self return None + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + def __get_module(self, fullname): try: return self.known_modules[fullname] @@ -223,6 +233,12 @@ def get_code(self, fullname): return None get_source = get_code # same as get_code + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + _importer = _SixMetaPathImporter(__name__) diff --git a/src/Tools/BlenderScripts/addons/io_export_fus/requirements.txt b/src/Tools/BlenderScripts/addons/io_export_fus/requirements.txt index 5d63f1f5b..7c30f52f8 100644 --- a/src/Tools/BlenderScripts/addons/io_export_fus/requirements.txt +++ b/src/Tools/BlenderScripts/addons/io_export_fus/requirements.txt @@ -1,2 +1,2 @@ -protobuf==3.21.9 +protobuf==3.22.0 ptvsd==4.3.2 diff --git a/src/Tools/CmdLine/Fusee.Tools.CmdLine.csproj b/src/Tools/CmdLine/Fusee.Tools.CmdLine.csproj index 912622829..9caab5c54 100644 --- a/src/Tools/CmdLine/Fusee.Tools.CmdLine.csproj +++ b/src/Tools/CmdLine/Fusee.Tools.CmdLine.csproj @@ -50,7 +50,7 @@ - + From dca0af34fe42fbf7754d603b5dee62402c80a559 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 15:46:27 +0100 Subject: [PATCH 087/294] Initial point picking --- .../Core/PointCloudPotree2Core.cs | 8 +- .../Core/PointRenderParams.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 121 +++++++++++++++--- 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index d039eb3a0..ae91bf1be 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -166,7 +166,7 @@ public void Init() { _debugTransform, MakeEffect.FromDiffuse(new float4(1,0,0,1)), - new Cube() + new Sphere(10, 10) } }, _pointCloudNode @@ -264,15 +264,15 @@ public void Update(bool allowInput) if (!_keys && Input.Mouse.LeftButton) { _debugTransform.Translation = float3.Zero; - _debugTransform.Scale = float3.One * 0.001f; + _debugTransform.Scale = float3.One * 0.05f; var width = _rc.ViewportWidth; var height = _rc.ViewportHeight; var result = _picker?.Pick(Input.Mouse.Position, width, height).ToList(); if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) { - _debugTransform.Translation = (float3)ppr.Octant.Center; - _debugTransform.Scale = new float3((float)ppr.Octant.Size); + _debugTransform.Translation = (float3)ppr.Mesh.Vertices[ppr.VertIdx]; + //_debugTransform.Scale = new float3((float)ppr.Octant.Size); } } diff --git a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs index c69eaa1fc..a1bbe998b 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs @@ -15,7 +15,7 @@ public sealed class PointRenderingParams public PointThresholdHandler PointThresholdHandler; public ProjectedSizeModifierHandler ProjectedSizeModifierHandler; - public string PathToOocFile = Path.Combine("Assets", "Cube1030301", "Potree"); + public string PathToOocFile = Path.Combine("D:", "potree_out"/*"Assets", "Cube1030301", "Potree"*/); public ShaderEffect DepthPassEf; public SurfaceEffectPointCloud ColorPassEf; diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 98848c67d..8c8ef54d3 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -13,13 +13,20 @@ using Fusee.PointCloud.Common; using Fusee.Base.Core; using Fusee.Structures; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Diagnostics; namespace Fusee.PointCloud.Core.Scene { public class PointCloudPickResult : PickResult { public OctantId OctandID; - public PointCloudOctant Octant; + public Mesh Mesh; + public int VertIdx; } public class PointCloudPickerModule : IPickerModule @@ -30,6 +37,16 @@ public class PointCloudPickerModule : IPickerModule public PickResult PickResult { get; set; } + internal struct MinPickValue + { + internal float2 Distance; + internal Mesh Mesh; + internal int VertIdx; + internal OctantId OctantId; + } + + private Stopwatch sw = new Stopwatch(); + /// /// Determines visible points of a point cloud (using the components ) and renders them. /// @@ -37,42 +54,108 @@ public class PointCloudPickerModule : IPickerModule [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { + sw.Start(); if (!pointCloud.Active) return; - var proj = (double4x4)State.CurrentCameraResult.Camera.GetProjectionMat(State.ScreenSize.x, State.ScreenSize.y, out _); - var view = (double4x4)State.CurrentCameraResult.View; - var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), view, proj); + var proj = State.CurrentCameraResult.Camera.GetProjectionMat(State.ScreenSize.x, State.ScreenSize.y, out _); + var view = State.CurrentCameraResult.View; + var rayD = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), (double4x4)view, (double4x4)proj); + + Diagnostics.Info($"Setup took: {sw.ElapsedTicks}"); + sw.Restart(); var tmpList = new List(); - var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).ToList(); + var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, rayD, tmpList).ToList(); + + Diagnostics.Info($"PickOctantRecursively took: {sw.ElapsedTicks}"); + sw.Restart(); + if (allHitBoxes == null || allHitBoxes.Count == 0) return; - foreach (var box in allHitBoxes) - box.ComputeScreenProjectedSize(view.Translation(), State.ScreenSize.y, State.CurrentCameraResult.Camera.Fov, (float3)box.Center, new float3((float)box.Size)); - var octant = allHitBoxes.OrderBy(x => x.ProjectedScreenSize).ToList()[0]; - //foreach (var box in allHitBoxes) - // if ((float)box.Center.x - State.View.Translation().x < (float)octant.Center.x - State.View.Translation().x - // && (float)box.Center.y - State.View.Translation().y < (float)octant.Center.y - State.View.Translation().y - // && (float)box.Center.z - State.View.Translation().z < (float)octant.Center.z - State.View.Translation().z) - // octant = box; + var currentRes = new ConcurrentBag(); + + Parallel.ForEach(_pcImp.GpuDataToRender, (mesh) => + //foreach(var mesh in _pcImp.GpuDataToRender) + { + foreach (var box in allHitBoxes) + { + if (!mesh.BoundingBox.Intersects(new AABBf((float3)box.Min, (float3)box.Max))) continue; + + var currentMin = new MinPickValue + { + Distance = float2.One * float.MaxValue + }; + + for (var i = 0; i < mesh.Vertices.Length; i++) + { + var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], 0.01f); + if (dist == float2.One * -1) continue; + + if (dist.x <= currentMin.Distance.x && dist.y <= currentMin.Distance.y) + { + currentMin.Distance = dist; + currentMin.Mesh = mesh; + currentMin.VertIdx = i; + currentMin.OctantId = box.OctId; + //break; // <- check if break after first result is enough even for sparse point clouds + } + } + + if (currentMin.Mesh == null) continue; + currentRes.Add(currentMin); + } + }); + + Diagnostics.Info($"foreach took: {sw.ElapsedTicks}"); + sw.Restart(); + if (currentRes == null || currentRes.Count == 0) return; + + var minElement = currentRes.First(); + + foreach (var r in currentRes) + { + if (r.Distance.x < minElement.Distance.x && r.Distance.y < minElement.Distance.y) + { + minElement = r; + } + } + + Diagnostics.Info($"Min element took: {sw.ElapsedTicks}"); + sw.Restart(); if (allHitBoxes != null && allHitBoxes.Any()) { - var mvp = (float4x4)proj * (float4x4)view * State.Model; + var mvp = proj * view * State.Model; PickResult = new PointCloudPickResult { Node = null, - Projection = (float4x4)proj, - View = (float4x4)view, + Projection = proj, + View = view, Model = State.Model, - ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), - OctandID = octant.OctId, - Octant = octant + ClipPos = float4x4.TransformPerspective(mvp, minElement.Mesh.Vertices[minElement.VertIdx]), + Mesh = minElement.Mesh, + VertIdx = minElement.VertIdx, + OctantId = minElement.OctantId }; } } + + // sphere of size ra centered at point ce + [MethodImpl(MethodImplOptions.AggressiveInlining)] + float2 SphereRayIntersection(float3 ro, float3 rd, float3 ce, float ra) + { + var oc = ro - ce; + float b = float3.Dot(oc, rd); + var qc = oc - b * rd; + float h = ra * ra - float3.Dot(qc, qc); + if (h < 0.0) new float2(-1.0f); // no intersection + h = MathF.Sqrt(h); + if (float.IsNaN(h)) return new float2(-1.0f); + return new float2(-b - h, -b + h); + } + private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) { if (node?.IsVisible == true && node.IntersectRay(ray)) From af5264818fabf431fe4f1e9875e3171a4f7d44a8 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 2 Mar 2023 16:10:20 +0100 Subject: [PATCH 088/294] Revert "First rudimentary point cloud picking" This reverts commit ef4e374770ca4a148490d983e8bad99c5aab17a5. --- .../Core/PointCloudPotree2Core.cs | 27 ++------- src/Engine/Core/ScenePicker.cs | 18 +----- .../Core/Scene/PointCloudPickerModule.cs | 56 ++++++------------- 3 files changed, 23 insertions(+), 78 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index d039eb3a0..0d57e8489 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -1,7 +1,6 @@ using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Core; -using Fusee.Engine.Core.Primitives; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; @@ -84,8 +83,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { + _potreeData = new PotreeData(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); _potreeReader = new Potree2Reader(); - _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile), out _potreeData); _rc = rc; } @@ -160,15 +159,6 @@ public void Init() Children = new List() { _camNode, - new SceneNode - { - Components = new List() - { - _debugTransform, - MakeEffect.FromDiffuse(new float4(1,0,0,1)), - new Cube() - } - }, _pointCloudNode } }; @@ -216,8 +206,6 @@ public void RenderAFrame() _sceneRenderer.Render(_rc); } - private Transform _debugTransform = new(); - public void Update(bool allowInput) { if (!allowInput) return; @@ -263,18 +251,11 @@ public void Update(bool allowInput) if (!_keys && Input.Mouse.LeftButton) { - _debugTransform.Translation = float3.Zero; - _debugTransform.Scale = float3.One * 0.001f; - var width = _rc.ViewportWidth; var height = _rc.ViewportHeight; - var result = _picker?.Pick(Input.Mouse.Position, width, height).ToList(); - if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) - { - _debugTransform.Translation = (float3)ppr.Octant.Center; - _debugTransform.Scale = new float3((float)ppr.Octant.Size); - } - + var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); + var result = _picker?.Pick(pickPosClip, width, height).ToList(); + if (result != null) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); } } diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 9e72df61e..8b67f9974 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -111,7 +111,7 @@ public class PickerState : VisitorState /// /// The current pick position in clip coordinate space. /// - public float2 PickPosClip { get; internal set; } + public static float2 PickPosClip { get; internal set; } /// /// The registered model. @@ -122,16 +122,6 @@ public float4x4 Model get => _model.Tos; } - /// - /// The currently used camera result - /// - public CameraResult CurrentCameraResult { get; internal set; } - - /// - /// The current screen size - /// - public int2 ScreenSize { get; internal set; } - /// /// The registered UI rectangle. /// @@ -282,11 +272,7 @@ protected override void InitState() pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); PickPosClip = pickPosClip; - - // Update state values for VisitorModules - State.PickPosClip = pickPosClip; - State.CurrentCameraResult = pickCam; - State.ScreenSize = new int2(pickCamRect.Width, pickCamRect.Height); + PickerState.PickPosClip = pickPosClip; SetState(); var res = Viserate().ToList(); diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 98848c67d..5598aa12e 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -12,14 +12,12 @@ using Microsoft.Extensions.Options; using Fusee.PointCloud.Common; using Fusee.Base.Core; -using Fusee.Structures; namespace Fusee.PointCloud.Core.Scene { public class PointCloudPickResult : PickResult { public OctantId OctandID; - public PointCloudOctant Octant; } public class PointCloudPickerModule : IPickerModule @@ -39,47 +37,27 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (!pointCloud.Active) return; - var proj = (double4x4)State.CurrentCameraResult.Camera.GetProjectionMat(State.ScreenSize.x, State.ScreenSize.y, out _); - var view = (double4x4)State.CurrentCameraResult.View; - var ray = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), view, proj); - - var tmpList = new List(); - var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).ToList(); - - if (allHitBoxes == null || allHitBoxes.Count == 0) return; - foreach (var box in allHitBoxes) - box.ComputeScreenProjectedSize(view.Translation(), State.ScreenSize.y, State.CurrentCameraResult.Camera.Fov, (float3)box.Center, new float3((float)box.Size)); - var octant = allHitBoxes.OrderBy(x => x.ProjectedScreenSize).ToList()[0]; - - //foreach (var box in allHitBoxes) - // if ((float)box.Center.x - State.View.Translation().x < (float)octant.Center.x - State.View.Translation().x - // && (float)box.Center.y - State.View.Translation().y < (float)octant.Center.y - State.View.Translation().y - // && (float)box.Center.z - State.View.Translation().z < (float)octant.Center.z - State.View.Translation().z) - // octant = box; - - if (allHitBoxes != null && allHitBoxes.Any()) - { - var mvp = (float4x4)proj * (float4x4)view * State.Model; - PickResult = new PointCloudPickResult - { - Node = null, - Projection = (float4x4)proj, - View = (float4x4)view, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), - OctandID = octant.OctId, - Octant = octant - }; - } + //var ray = new RayD(new double2(PickerState.PickPosClip.x, PickerState.PickPosClip.y), (double4x4)State.View, (double4x4)State.Projection); + //var tmpList = new List(); + //var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, ray, tmpList).OrderBy(x => x.ProjectedScreenSize); + //if (allHitBoxes != null && allHitBoxes.Any()) + //{ + // var mvp = State.Projection * State.View * State.Model; + // PickResult = new PointCloudPickResult + // { + // Node = null, + // Projection = State.Projection, + // View = State.View, + // Model = State.Model, + // ClipPos = float4x4.TransformPerspective(mvp, (float3)allHitBoxes.ElementAt(0).Center), + // OctandID = allHitBoxes.ElementAt(0).OctId, + // }; + //} } private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) { - if (node?.IsVisible == true && node.IntersectRay(ray)) - { - list.Add(node); - } - + list.Add(node); if (node.Children[0] != null) { foreach (var child in node.Children.Cast()) From 1495123239c34d4e9a70ff8bdd93cb5c92296b4b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 16:27:26 +0100 Subject: [PATCH 089/294] Fixed AdvancedUI --- .../Complete/AdvancedUI/Core/AdvancedUI.cs | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs index 05a1c4373..00f31ee63 100644 --- a/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs +++ b/Examples/Complete/AdvancedUI/Core/AdvancedUI.cs @@ -1,4 +1,4 @@ -using Fusee.Base.Common; +using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core; @@ -156,14 +156,13 @@ public override void Init() _gui = CreateGui(); - - //Create a scene picker for performing visibility tests - _scenePicker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); - // Wrap a SceneRenderer around the model. _sceneRenderer = new SceneRendererForward(_scene); _guiRenderer = new SceneRendererForward(_gui); + //Create a scene picker for performing visibility tests + _scenePicker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); + // Create the interaction handler _sih = new SceneInteractionHandler(_gui, _guiRenderer.PrePassVisitor.CameraPrepassResults); } @@ -236,6 +235,11 @@ public override void RenderAFrame() float4x4 mvpMonkey = projection * view * model; float3 clipPos = float4x4.TransformPerspective(mvpMonkey, uiInput.Position); + // go from clip pos to pixel coordinates + var pixelPos = clipPos * 0.5f + 0.5f; // shift from [-1,1] to [0,1] + pixelPos.y = 1f - pixelPos.y; // invert y + pixelPos.x = pixelPos.x * Width; + pixelPos.y = pixelPos.y * Height; float2 canvasPosCircle = new float2(clipPos.x, clipPos.y) * 0.5f + 0.5f; canvasPosCircle.x *= _canvasWidth; @@ -248,23 +252,23 @@ public override void RenderAFrame() circle.GetComponent().Offsets = GuiElementPosition.CalcOffsets(AnchorPos.Middle, pos, _canvasHeight, _canvasWidth, uiInput.Size); //1.1 Check if circle is visible - PickResult newPick = _scenePicker.Pick(new float2(clipPos.x, clipPos.y), Width, Height).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); - - // TODO(mr): Fix and introduce new PickResult for TriangleMeshes, use it here - //if (newPick != null && uiInput.AffectedTriangles[0] == newPick.Triangle) //VISIBLE - //{ - // uiInput.IsVisible = true; - // - // var effect = circle.GetComponent(); - // effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaVis); - //} - //else - //{ - // uiInput.IsVisible = false; - // var effect = circle.GetComponent(); - // effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaInv); - // - //} + MeshPickResult newPick = (MeshPickResult)_scenePicker.Pick(pixelPos.xy, Width, Height).ToList().OrderBy(pr => pr.ClipPos.z).FirstOrDefault(); + + + if (newPick != null && uiInput.AffectedTriangles[0] == newPick.Triangle) //VISIBLE + { + uiInput.IsVisible = true; + + var effect = circle.GetComponent(); + effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaVis); + } + else + { + uiInput.IsVisible = false; + var effect = circle.GetComponent(); + effect.SetDiffuseAlphaInShaderEffect(UserInterfaceHelper.alphaInv); + + } //1.2 Calculate annotation positions without intersections. if (!uiInput.CircleCanvasPos.Equals(uiInput.CircleCanvasPosCache)) From 87208535f3d10c9713490969fc61afd2e2b6adc2 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 16:45:14 +0100 Subject: [PATCH 090/294] Fixed V1 Serialization & tests --- src/Engine/Core/FusSceneConverter.cs | 2 +- src/Math/Core/Rayf.cs | 4 ---- src/Serialization/V1/FusMesh.cs | 2 +- src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs | 8 ++++---- src/Tests/Serialization/V1/SimpleSerialization.cs | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/Engine/Core/FusSceneConverter.cs b/src/Engine/Core/FusSceneConverter.cs index fad015f3c..8d6bc62a5 100644 --- a/src/Engine/Core/FusSceneConverter.cs +++ b/src/Engine/Core/FusSceneConverter.cs @@ -1369,7 +1369,7 @@ public void ConvMesh(Mesh m) Name = m.Name, Normals = m.Normals?.ToArray(), Tangents = m.Tangents?.ToArray(), - Triangles = m.Triangles?.ToArray().Select(x => (int)x).ToArray(), + Triangles = m.Triangles?.ToArray().Select(x => (ushort)x).ToArray(), UVs = m.UVs?.ToArray(), Vertices = m.Vertices?.ToArray() }; diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index e762259f9..06828453b 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -42,9 +42,6 @@ public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) { float4x4 invViewProjection = float4x4.Invert(projection * view); - var noTP = invViewProjection * new float3(pickPosClip.x, pickPosClip.y, 1); - var noTP2 = invViewProjection * new float3(pickPosClip.x, pickPosClip.y, 0); - var pickPosFarWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); var pickPosNearWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, -1)); @@ -52,7 +49,6 @@ public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) Origin = pickPosNearWorld; Inverse = new float3(1 / Direction.x, 1 / Direction.y, 1 / Direction.z); - //Inverse = new float3(float.IsInfinity(Inverse.x) ? 0 : Inverse.x, float.IsInfinity(Inverse.y) ? 0 : Inverse.y, float.IsInfinity(Inverse.z) ? 0 : Inverse.z); } } } \ No newline at end of file diff --git a/src/Serialization/V1/FusMesh.cs b/src/Serialization/V1/FusMesh.cs index 8fc0eb702..10bc0f1f3 100644 --- a/src/Serialization/V1/FusMesh.cs +++ b/src/Serialization/V1/FusMesh.cs @@ -71,7 +71,7 @@ public class FusMesh : FusComponent /// The triangles. /// [ProtoMember(7)] - public int[] Triangles; + public ushort[] Triangles; /// /// The bounding box of this geometry chunk. diff --git a/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs b/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs index 3a4943fb8..c56f6dad3 100644 --- a/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs +++ b/src/Tests/Serialization/V1/SimpleConvertSceneGraph.cs @@ -127,15 +127,15 @@ public void V1_SimpleScene_Convert() } } - if (gtComp is Mesh mesh) + if (gtComp is FusMesh mesh) { Assert.Equal(mesh.Name, ((FusMesh)fusFileComp).Name); // Assert.Equal(mesh.BoundingBox, ((FusMesh)fusFileComp).BoundingBox); <- not yet calculated, is done after first frame - Assert.Equal(mesh.Colors0?.ToArray(), ((FusMesh)fusFileComp).Colors); + Assert.Equal(mesh.Colors?.ToArray(), ((FusMesh)fusFileComp).Colors); Assert.Equal(mesh.Vertices?.ToArray(), ((FusMesh)fusFileComp).Vertices); - Assert.Equal(mesh.Triangles?.ToArray().Select(x => (int)x).ToArray(), ((FusMesh)fusFileComp).Triangles); + Assert.Equal(mesh.Triangles?.ToArray().Select(x => x).ToArray(), ((FusMesh)fusFileComp).Triangles); Assert.Equal(mesh.UVs?.ToArray(), ((FusMesh)fusFileComp).UVs); - Assert.Equal((int)mesh.MeshType, ((FusMesh)fusFileComp).MeshType); + Assert.Equal(mesh.MeshType, ((FusMesh)fusFileComp).MeshType); Assert.Equal(mesh.Tangents?.ToArray(), ((FusMesh)fusFileComp).Tangents); Assert.Equal(mesh.BiTangents?.ToArray(), ((FusMesh)fusFileComp).BiTangents); } diff --git a/src/Tests/Serialization/V1/SimpleSerialization.cs b/src/Tests/Serialization/V1/SimpleSerialization.cs index a5be91b77..1a4bca4da 100644 --- a/src/Tests/Serialization/V1/SimpleSerialization.cs +++ b/src/Tests/Serialization/V1/SimpleSerialization.cs @@ -105,7 +105,7 @@ public static FusMesh CreateCuboid(float3 size) new float3 {x = -0.5f * size.x, y = -0.5f * size.y, z = -0.5f * size.z} }, - Triangles = new int[] + Triangles = new ushort[] { // front face 0, 2, 1, 0, 3, 2, From a5fc3bc12d47c495bf74d06fc83422aaada10ec9 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 16:46:47 +0100 Subject: [PATCH 091/294] Fixed additional space --- src/Xene/Viserator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xene/Viserator.cs b/src/Xene/Viserator.cs index b66d78007..89ffe9bd6 100644 --- a/src/Xene/Viserator.cs +++ b/src/Xene/Viserator.cs @@ -241,7 +241,7 @@ protected internal override void Init(IEnumerable rootList) } /// - /// Initializes a new instanc e of the class. + /// Initializes a new instance of the class. /// /// The root list. /// Optional custom . Needs to be passed to base.ctor, From 38c71dfc76d02fcc7920a43276c2263a9779349c Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:00:07 +0100 Subject: [PATCH 092/294] Removed obsolete PickComponent in FuseeGUIHelper --- src/Engine/GUI/FuseeGuiHelper.cs | 45 -------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/Engine/GUI/FuseeGuiHelper.cs b/src/Engine/GUI/FuseeGuiHelper.cs index 06988bf99..b3a4d410a 100644 --- a/src/Engine/GUI/FuseeGuiHelper.cs +++ b/src/Engine/GUI/FuseeGuiHelper.cs @@ -115,51 +115,6 @@ public static async Task CreateDefaultGuiAsync(RenderCanvas rc, } } }, - //new SceneNode() - //{ - // Components = new List - // { - // new PickComponent() - // { - // CustomPickMethod = (mesh, currentNode, model, view, projection, pickPosClip) => - // { - // var mvp = projection * view * model; - - // for (var i = 0; i < mesh.Triangles.Length; i += 3) - // { - // // a, b c: current triangle's vertices in clip coordinates - // var a = new float4(mesh.Vertices[(int)mesh.Triangles[i + 0]], 1); - // a = float4x4.TransformPerspective(mvp, a); - - // var b = new float4(mesh.Vertices[(int)mesh.Triangles[i + 1]], 1); - // b = float4x4.TransformPerspective(mvp, b); - - // var c = new float4(mesh.Vertices[(int)mesh.Triangles[i + 2]], 1); - // c = float4x4.TransformPerspective(mvp, c); - - // // Point-in-Triangle-Test - // if (float2.PointInTriangle(a.xy, b.xy, c.xy, pickPosClip, out var u, out var v)) - // { - // var pickPos = float3.Barycentric(a.xyz, b.xyz, c.xyz, u, v); - - // if (pickPos.z >= -1 && pickPos.z <= 1) - // { - // return new PickResult - // { - // Mesh = mesh, - // Node = currentNode, - // Model = model, - // View = view, - // Projection = projection - // }; - // } - // } - // } - // return null; - // } - // } - // } - //}, canvas } }; From 2fca503006dac94046d4997239c27e8cf7686168 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:06:41 +0100 Subject: [PATCH 093/294] Fixed project files --- .../Common/Fusee.PointCloud.Common.csproj | 7 ++--- .../Core/Fusee.PointCloud.Core.csproj | 28 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj index 7fca6ba19..d67dfe2cf 100644 --- a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj +++ b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj @@ -10,16 +10,13 @@ - - + + - - - diff --git a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj index 2dfe7178d..8bb29b19d 100644 --- a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj +++ b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj @@ -1,20 +1,18 @@  - - netstandard2.1;net7.0 - $(OutputPath)\$(RootNamespace).xml - + + netstandard2.1;net7.0 + $(OutputPath)\$(RootNamespace).xml + - - - - - - - - - - - + + + + + + + + + From 49afdf51c94196128308319959a03eb0f9946dc4 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:07:43 +0100 Subject: [PATCH 094/294] Removed all Pointcloud.LAS files --- .../Fusee.PointCloud.Las.Desktop.csproj | 24 -- src/PointCloud/Las/Desktop/LasPointReader.cs | 257 ------------------ .../Fusee.PointCloud.Las.Shared.projitems | 16 -- .../Shared/Fusee.PointCloud.Las.Shared.shproj | 13 - .../Las/Shared/LasInternalHeader.cs | 18 -- src/PointCloud/Las/Shared/LasInternalPoint.cs | 19 -- src/PointCloud/Las/Shared/LasMetaInfo.cs | 19 -- 7 files changed, 366 deletions(-) delete mode 100644 src/PointCloud/Las/Desktop/Fusee.PointCloud.Las.Desktop.csproj delete mode 100644 src/PointCloud/Las/Desktop/LasPointReader.cs delete mode 100644 src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.projitems delete mode 100644 src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.shproj delete mode 100644 src/PointCloud/Las/Shared/LasInternalHeader.cs delete mode 100644 src/PointCloud/Las/Shared/LasInternalPoint.cs delete mode 100644 src/PointCloud/Las/Shared/LasMetaInfo.cs diff --git a/src/PointCloud/Las/Desktop/Fusee.PointCloud.Las.Desktop.csproj b/src/PointCloud/Las/Desktop/Fusee.PointCloud.Las.Desktop.csproj deleted file mode 100644 index 9a7450b5d..000000000 --- a/src/PointCloud/Las/Desktop/Fusee.PointCloud.Las.Desktop.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - net7.0 - $(DefineConstants);PLATFORM_DESKTOP - $(OutputPath)\$(RootNamespace).xml - - - - - - - - - - - - - - - - - - diff --git a/src/PointCloud/Las/Desktop/LasPointReader.cs b/src/PointCloud/Las/Desktop/LasPointReader.cs deleted file mode 100644 index ac9d58d32..000000000 --- a/src/PointCloud/Las/Desktop/LasPointReader.cs +++ /dev/null @@ -1,257 +0,0 @@ -using Fusee.Base.Imp.Desktop; -using Fusee.Math.Core; -using Fusee.PointCloud.Common; -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Fusee.PointCloud.Las.Desktop -{ - /// - /// A reader for points in LAZ or LAS format - /// - public class LasPointReader : IDisposable, IPointReader - { - /// - /// The point cloud meta data, usually stored in the header of the las file. - /// - public LasMetaInfo MetaInfo { get; private set; } - - private IntPtr _ptrToLASClass = new(); - private string _filePath; - - public IPointCloud GetPointCloudComponent(RenderMode renderMode) - { - OpenFile(_filePath); - - var pointType = PointType; - switch (pointType) - { - case PointType.Undefined: - case PointType.PosD3: - case PointType.PosD3ColF3InUs: - case PointType.PosD3InUs: - case PointType.PosD3ColF3: - case PointType.PosD3LblB: - case PointType.PosD3NorF3ColF3InUs: - case PointType.PosD3NorF3InUs: - case PointType.PosD3NorF3ColF3: - case PointType.PosD3ColF3LblB: - case PointType.PosD3ColF3InUsLblB: - default: - break; - } - - throw new NotImplementedException(); - } - - public IPointCloudOctree GetOctree() - { - //convert to potree octree - throw new NotImplementedException(); - } - - public TPoint[] LoadNodeData(OctantId id) where TPoint : new() - { - throw new NotImplementedException(); - } - - private void OpenFile(string filename) - { - EmbeddedResourcesDllHandler.LoadEmbeddedDll("libLASlib.dll", "Fusee.PointCloud.Las.Desktop.Natives.libLASlib.dll"); - - // Open file - OpenLASFile(filename, ref _ptrToLASClass); - - if (_ptrToLASClass == IntPtr.Zero) - throw new FileNotFoundException($"{filename} not found!"); - - // Read header - var header = new LasInternalHeader(); - - GetHeader(_ptrToLASClass, ref header); - - MetaInfo = new LasMetaInfo - { - Filename = filename, - OffsetX = header.OffsetX, - OffsetY = header.OffsetY, - OffsetZ = header.OffsetZ, - PointCount = header.PointCnt, - PointDataFormat = header.PointDataFormat, - ScaleFactorX = header.ScaleFactorX, - ScaleFactorY = header.ScaleFactorY, - ScaleFactorZ = header.ScaleFactorZ - }; - } - - /// - /// A LASPointReader can open point files encoded by the las format v. 1.4 with the following extensions: - /// - *.asc - /// - *.bil - /// - *.bin - /// - *.dtm - /// - *.las - /// - *.ply - /// - *.qfit - /// - *.shp - /// - *.txt - /// - *.laz - /// - /// The path to a las encoded file. - public LasPointReader(string filePath) - { - throw new NotImplementedException(""); - _filePath = filePath; - OpenFile(filePath); - } - - /// - /// A LASPointReader can open point files encoded by the las format v. 1.4 with the following extensions: - /// - *.asc - /// - *.bil - /// - *.bin - /// - *.dtm - /// - *.las - /// - *.ply - /// - *.qfit - /// - *.shp - /// - *.txt - /// - *.laz - /// - public LasPointReader() { } - - /// - /// Reads the given amount of points from stream - /// - /// - /// - /// - public TPoint[] ReadNPoints(int n) where TPoint : new() - { - if (_ptrToLASClass == IntPtr.Zero) - throw new FileNotFoundException("No file was specified yet. Call 'OpenFile' first"); - var points = new TPoint[n]; - for (var i = 0; i < points.Length; i++) - { - if (!ReadNextPoint(ref points[i])) break; - } - return points; - } - - /// - /// Reads the next point and writes it to the given point - /// - /// - /// - /// - public bool ReadNextPoint(ref TPoint point) where TPoint : new() - { - if (point == null) - throw new ArgumentOutOfRangeException("No writable point found!"); - - var hasNextPoint = true; - ReadNextPoint(_ptrToLASClass, ref hasNextPoint); - if (!hasNextPoint) return false; - - var currentPoint = new LasInternalPoint(); - GetPoint(_ptrToLASClass, ref currentPoint); - - var pos = new double3(currentPoint.X * MetaInfo.ScaleFactorX, currentPoint.Y * MetaInfo.ScaleFactorY, currentPoint.Z * MetaInfo.ScaleFactorZ); - var intensity = currentPoint.Intensity; - var color = new float3(currentPoint.R, currentPoint.G, currentPoint.B); - - - // -> never the case right now! - //if (currentFormat.HasClassification && typedAccessor.LabelType == PointLabelType.UInt_8) - //{ - // //TODO: HACK!! label was somehow written to UserData and not to classification - // if (currentPoint.Classification != 0) - // typedAccessor.SetLabelUInt_8(ref point, currentPoint.Classification); - // else - // typedAccessor.SetLabelUInt_8(ref point, currentPoint.UserData); - //} - - return true; - } - - public PointType PointType - { - get - { - //TODO: Complete - switch (MetaInfo.PointDataFormat) - { - case 0: - case 1: - return PointType.PosD3InUs; - case 2: - case 3: - return PointType.PosD3ColF3InUs; - default: - throw new ArgumentException($"Point data format with byte {MetaInfo.PointDataFormat} not recognized!"); - } - } - } - - public Task LoadPointsForNodeAsync(string guid) where TPoint : new() - { - throw new NotImplementedException(); - } - - public TPoint[] LoadNodeData(string id) where TPoint : new() - { - throw new NotImplementedException(); - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // dispose managed state (managed objects). - } - - Delete(ref _ptrToLASClass); - _ptrToLASClass = IntPtr.Zero; - - disposedValue = true; - } - } - - ~LasPointReader() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(false); - } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - GC.SuppressFinalize(this); - } - #endregion - - [DllImport("libLASlib", EntryPoint = "CS_OpenLasFile")] - private static extern IntPtr OpenLASFile(string filename, ref IntPtr lasFileHandle); - - [DllImport("libLASlib", EntryPoint = "CS_GetHeader")] - private static extern void GetHeader(IntPtr lasFileHandle, ref LasInternalHeader header); - - [DllImport("libLASlib", EntryPoint = "CS_ReadNextPoint")] - private static extern void ReadNextPoint(IntPtr lasFileHandle, ref bool nextPoint); - - [DllImport("libLASlib", EntryPoint = "CS_GetPoint")] - private static extern void GetPoint(IntPtr lasFileHandle, ref LasInternalPoint csPoint); - - [DllImport("libLASlib", EntryPoint = "CS_Delete")] - private static extern void Delete(ref IntPtr lasFileHandle); - } -} \ No newline at end of file diff --git a/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.projitems b/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.projitems deleted file mode 100644 index b3fe29c14..000000000 --- a/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.projitems +++ /dev/null @@ -1,16 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - dcc7da71-3e2e-476c-8391-1f9651637503 - - - Fusee.PointCloud.FileReader.Las.Shared - - - - - - - \ No newline at end of file diff --git a/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.shproj b/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.shproj deleted file mode 100644 index 9fb031a46..000000000 --- a/src/PointCloud/Las/Shared/Fusee.PointCloud.Las.Shared.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - dcc7da71-3e2e-476c-8391-1f9651637503 - 14.0 - - - - - - - - diff --git a/src/PointCloud/Las/Shared/LasInternalHeader.cs b/src/PointCloud/Las/Shared/LasInternalHeader.cs deleted file mode 100644 index 28c2a3efa..000000000 --- a/src/PointCloud/Las/Shared/LasInternalHeader.cs +++ /dev/null @@ -1,18 +0,0 @@ - -namespace Fusee.PointCloud.Las.Desktop -{ - internal struct LasInternalHeader - { - public byte PointDataFormat; - - public long PointCnt; - - public double ScaleFactorX; - public double ScaleFactorY; - public double ScaleFactorZ; - - public double OffsetX; - public double OffsetY; - public double OffsetZ; - } -} \ No newline at end of file diff --git a/src/PointCloud/Las/Shared/LasInternalPoint.cs b/src/PointCloud/Las/Shared/LasInternalPoint.cs deleted file mode 100644 index 31f468dcb..000000000 --- a/src/PointCloud/Las/Shared/LasInternalPoint.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Fusee.PointCloud.Las.Desktop -{ - internal struct LasInternalPoint - { - public int X; - public int Y; - public int Z; - - public ushort Intensity; - - public byte Classification; - public byte UserData; - - public ushort R; - public ushort G; - public ushort B; - } - -} \ No newline at end of file diff --git a/src/PointCloud/Las/Shared/LasMetaInfo.cs b/src/PointCloud/Las/Shared/LasMetaInfo.cs deleted file mode 100644 index 2f32d5a4f..000000000 --- a/src/PointCloud/Las/Shared/LasMetaInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Fusee.PointCloud.Las.Desktop -{ - public struct LasMetaInfo - { - public string Filename; - - public byte PointDataFormat; - - public long PointCount { get; set; } - - public double ScaleFactorX; - public double ScaleFactorY; - public double ScaleFactorZ; - - public double OffsetX; - public double OffsetY; - public double OffsetZ; - } -} \ No newline at end of file From 10adf44e440bd94ce5467594d3d61140619fd321 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:10:51 +0100 Subject: [PATCH 095/294] Fixed proj & usings --- .../Potree/Fusee.PointCloud.Potree.csproj | 32 +++++++++---------- src/Xene/Visitor.cs | 1 - 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index 5f8aa3d3c..cd9dddb14 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -1,22 +1,20 @@ - - netstandard2.1;net7.0 - $(OutputPath)\$(RootNamespace).xml - enable - + + netstandard2.1;net7.0 + $(OutputPath)\$(RootNamespace).xml + enable + - - - - - - - - - - - - + + + + + + + + + + diff --git a/src/Xene/Visitor.cs b/src/Xene/Visitor.cs index 99f7fbe36..a14ddecde 100644 --- a/src/Xene/Visitor.cs +++ b/src/Xene/Visitor.cs @@ -1,7 +1,6 @@ using Fusee.Engine.Common; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Reflection; namespace Fusee.Xene From 47193724140e794270e807fb6c182abcc1d7112b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:14:32 +0100 Subject: [PATCH 096/294] Fixed picking result in PCPotree2Core --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 84dcf81fe..bdf3fddad 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -254,7 +254,7 @@ public void Update(bool allowInput) var height = _rc.ViewportHeight; var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); var result = _picker?.Pick(pickPosClip, width, height).ToList(); - if (result != null) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); + if (result != null && result.Count != 0) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); } } From 97c7105b718cb5b8205e683bcc127dffc4c45474 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:18:23 +0100 Subject: [PATCH 097/294] Fixed project files / merge errors --- src/Engine/Core/Fusee.Engine.Core.csproj | 3 +-- src/Math/Core/Fusee.Math.Core.csproj | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 13965c324..22d12099d 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -9,9 +9,8 @@ - - + diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index bd95e890b..2f5b45251 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -1,20 +1,19 @@  - + netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml true Core Math implementation for the Fusee Project - + enable true - - + \ No newline at end of file From 4d8e39b44cdd1147fdcb3e75c322aced8084688d Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Mon, 6 Mar 2023 16:26:22 +0000 Subject: [PATCH 098/294] Linting --- Examples/Complete/Picking/Desktop/Main.cs | 2 +- src/Engine/Core/IPickerModule.cs | 2 +- src/Engine/Core/Scene/PickComponent.cs | 2 +- src/Engine/Core/SceneRayCaster.cs | 2 +- src/Math/Core/float4x4.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 14 +++++++------- src/Serialization/V2/FusPickComponent.cs | 2 +- src/Xene/Viserator.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Examples/Complete/Picking/Desktop/Main.cs b/Examples/Complete/Picking/Desktop/Main.cs index 472fad234..a562cfcdc 100644 --- a/Examples/Complete/Picking/Desktop/Main.cs +++ b/Examples/Complete/Picking/Desktop/Main.cs @@ -4,8 +4,8 @@ using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Serialization; -using System.Collections.Generic; using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs index 57d20d694..abc595e0c 100644 --- a/src/Engine/Core/IPickerModule.cs +++ b/src/Engine/Core/IPickerModule.cs @@ -16,4 +16,4 @@ public interface IPickerModule : IVisitorModule public PickResult PickResult { get; set; } } -} +} \ No newline at end of file diff --git a/src/Engine/Core/Scene/PickComponent.cs b/src/Engine/Core/Scene/PickComponent.cs index 7e7936fbe..2746f70a0 100644 --- a/src/Engine/Core/Scene/PickComponent.cs +++ b/src/Engine/Core/Scene/PickComponent.cs @@ -22,4 +22,4 @@ public class PickComponent : SceneComponent /// public Func? CustomPickMethod; } -} +} \ No newline at end of file diff --git a/src/Engine/Core/SceneRayCaster.cs b/src/Engine/Core/SceneRayCaster.cs index bfd82a48b..5e916b4c1 100644 --- a/src/Engine/Core/SceneRayCaster.cs +++ b/src/Engine/Core/SceneRayCaster.cs @@ -83,7 +83,7 @@ public SceneRayCaster(SceneContainer scene, IEnumerable prePassCam { CullMode = cullMode; _prePassResults = prePassCameraResults; - _sc = scene; + _sc = scene; } /// diff --git a/src/Math/Core/float4x4.cs b/src/Math/Core/float4x4.cs index 160a610cb..e66dd2abe 100644 --- a/src/Math/Core/float4x4.cs +++ b/src/Math/Core/float4x4.cs @@ -2146,7 +2146,7 @@ public static float4x4 RotationDecomposition(float4x4 mat) var scalevector = GetScale(mat); var rotationMtx = float4x4.Identity; - if(scalevector.x <= 0 || scalevector.y <= 0 || scalevector.z <= 0) + if (scalevector.x <= 0 || scalevector.y <= 0 || scalevector.z <= 0) { throw new ArgumentException("Scale vector <= 0!"); } diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 5598aa12e..2b9bb83fc 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -1,17 +1,17 @@ -using Fusee.Engine.Common; +using Fusee.Base.Core; +using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; +using Fusee.PointCloud.Common; +using Fusee.PointCloud.Core; using Fusee.Xene; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; +using System.Linq; using System.Text; -using Fusee.PointCloud.Core; using static Fusee.Engine.Core.ScenePicker; -using System.Linq; -using Microsoft.Extensions.Options; -using Fusee.PointCloud.Common; -using Fusee.Base.Core; namespace Fusee.PointCloud.Core.Scene { @@ -85,4 +85,4 @@ public void SetState(PickerState state) State = state; } } -} +} \ No newline at end of file diff --git a/src/Serialization/V2/FusPickComponent.cs b/src/Serialization/V2/FusPickComponent.cs index 07efcce8e..7984baf1c 100644 --- a/src/Serialization/V2/FusPickComponent.cs +++ b/src/Serialization/V2/FusPickComponent.cs @@ -17,4 +17,4 @@ public class FusPickComponent : V1.FusComponent [ProtoMember(1)] public int PickLayer; } -} +} \ No newline at end of file diff --git a/src/Xene/Viserator.cs b/src/Xene/Viserator.cs index 89ffe9bd6..52c7362a4 100644 --- a/src/Xene/Viserator.cs +++ b/src/Xene/Viserator.cs @@ -248,7 +248,7 @@ protected internal override void Init(IEnumerable rootList) /// as the initialization and discovery of potential methods to visit is being done in protected Viserator(IEnumerable rootList, IEnumerable customVisitorModules = null) { - if(customVisitorModules != null) + if (customVisitorModules != null) VisitorModules.AddRange(customVisitorModules); Init(rootList); } From 81289914095e8dd62f53442015e0e44f1652e051 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 17:51:55 +0100 Subject: [PATCH 099/294] Fixed broken SceneStates after merge --- .../Core/PointCloudPotree2Core.cs | 2 ++ src/Engine/Core/ScenePicker.cs | 16 ++++++++++++++-- .../Core/Scene/PointCloudPickerModule.cs | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 92525f524..1d0273bce 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -1,6 +1,7 @@ using Fusee.Base.Common; using Fusee.Base.Core; using Fusee.Engine.Core; +using Fusee.Engine.Core.Primitives; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; @@ -56,6 +57,7 @@ public bool ClosingRequested private readonly RenderContext _rc; + private Transform _debugTransform = new(); public void OnLoadNewFile(object sender, EventArgs e) { var path = PointRenderingParams.Instance.PathToOocFile; diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 8b67f9974..8a040f464 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -111,7 +111,17 @@ public class PickerState : VisitorState /// /// The current pick position in clip coordinate space. /// - public static float2 PickPosClip { get; internal set; } + public float2 PickPosClip { get; internal set; } + + /// + /// The current camera used for picking + /// + public CameraResult CurrentCameraResult { get; internal set; } + + /// + /// The current canvas screen size + /// + public int2 ScreenSize { get; internal set; } /// /// The registered model. @@ -272,7 +282,9 @@ protected override void InitState() pickPosClip = ((pickPos - new float2(pickCamRect.Left, pickCamRect.Top)) * new float2(2.0f / pickCamRect.Width, -2.0f / pickCamRect.Height)) + new float2(-1, 1); PickPosClip = pickPosClip; - PickerState.PickPosClip = pickPosClip; + State.PickPosClip = pickPosClip; + State.CurrentCameraResult = pickCam; + State.ScreenSize = new int2(pickCamRect.Width, pickCamRect.Height); SetState(); var res = Viserate().ToList(); diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 9086951af..8d08fe99d 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -28,9 +28,9 @@ namespace Fusee.PointCloud.Core.Scene { public class PointCloudPickResult : PickResult { - public OctantId OctandID; public Mesh Mesh; public int VertIdx; + public OctantId OctantId; } public class PointCloudPickerModule : IPickerModule From 3272ad9dda0b4a7e2b5b6fba50cc3d9c144aca7f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 6 Mar 2023 18:09:26 +0100 Subject: [PATCH 100/294] First working point picking --- .../Core/PointCloudPotree2Core.cs | 4 ++- .../Core/PointRenderParams.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 29 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 1d0273bce..c66a3168c 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -259,7 +259,7 @@ public void Update(bool allowInput) _camTransform.FpsView(_angleHorz, _angleVert, Input.Keyboard.WSAxis, Input.Keyboard.ADAxis, Time.DeltaTimeUpdate * 20); - if (!_keys && Input.Mouse.LeftButton) + if (!_keys && Input.Mouse.RightButton) { _debugTransform.Translation = float3.Zero; _debugTransform.Scale = float3.One * 0.05f; @@ -271,6 +271,8 @@ public void Update(bool allowInput) { _debugTransform.Translation = (float3)ppr.Mesh.Vertices[ppr.VertIdx]; //_debugTransform.Scale = new float3((float)ppr.Octant.Size); + + //ppr.Mesh.Colors0[ppr.VertIdx] = (uint)ColorUint.Red; } } diff --git a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs index a1bbe998b..c69eaa1fc 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs @@ -15,7 +15,7 @@ public sealed class PointRenderingParams public PointThresholdHandler PointThresholdHandler; public ProjectedSizeModifierHandler ProjectedSizeModifierHandler; - public string PathToOocFile = Path.Combine("D:", "potree_out"/*"Assets", "Cube1030301", "Potree"*/); + public string PathToOocFile = Path.Combine("Assets", "Cube1030301", "Potree"); public ShaderEffect DepthPassEf; public SurfaceEffectPointCloud ColorPassEf; diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 8d08fe99d..d0463eb09 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -93,7 +93,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) for (var i = 0; i < mesh.Vertices.Length; i++) { - var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], 0.01f); + var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], 0.017f); // spacing * 0.1? if (dist == float2.One * -1) continue; if (dist.x <= currentMin.Distance.x && dist.y <= currentMin.Distance.y) @@ -128,21 +128,20 @@ public void RenderPointCloud(PointCloudComponent pointCloud) Diagnostics.Info($"Min element took: {sw.ElapsedTicks}"); sw.Restart(); - if (allHitBoxes != null && allHitBoxes.Any()) + + var mvp = proj * view * State.Model; + PickResult = new PointCloudPickResult { - var mvp = proj * view * State.Model; - PickResult = new PointCloudPickResult - { - Node = null, - Projection = proj, - View = view, - Model = State.Model, - ClipPos = float4x4.TransformPerspective(mvp, minElement.Mesh.Vertices[minElement.VertIdx]), - Mesh = minElement.Mesh, - VertIdx = minElement.VertIdx, - OctantId = minElement.OctantId - }; - } + Node = null, + Projection = proj, + View = view, + Model = State.Model, + ClipPos = float4x4.TransformPerspective(mvp, minElement.Mesh.Vertices[minElement.VertIdx]), + Mesh = minElement.Mesh, + VertIdx = minElement.VertIdx, + OctantId = minElement.OctantId + }; + } From b63c0c84374193ef8ce6e71345af641d71a95126 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 7 Mar 2023 10:29:50 +0100 Subject: [PATCH 101/294] FilePicker: folder-only handling --- .../Templates/ImGuiFilePicker.cs | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index ce13ddb80..06487a2b1 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -66,8 +66,8 @@ public class ImGuiFilePicker public string ParentFolderTxt = "Parent"; public string BackTxt = "Back"; - public readonly bool OnlyAllowFolders; - public readonly List? AllowedExtensions; + public bool OnlyAllowFolders; + public List? AllowedExtensions; public string? SelectedFile { get; protected set; } public string RootFolder { get; protected set; } @@ -344,7 +344,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) if (ImGui.Selectable(name, SelectedFile == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { - SelectedFile = name; + if (SelectedFile == name) + SelectedFile = ""; + else + SelectedFile = name; + if (ImGui.IsMouseDoubleClicked(0) && (SelectedFile != null && SelectedFile != "") && ImGui.GetIO().WantCaptureMouse) { if (HandlePickedFile(SelectedFile)) @@ -391,7 +395,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) _sizeOfInputText = ImGui.GetItemRectSize(); var sameLineOffset = WinSize.X - WindowPadding.X - (BottomButtonSize.X * 2 + ImGui.GetStyle().ItemSpacing.X * 4); - if (!string.IsNullOrWhiteSpace(SelectedFile)) + if (!OnlyAllowFolders && !string.IsNullOrWhiteSpace(SelectedFile)) { var fi = new FileInfo(Path.Combine(CurrentOpenFolder, SelectedFile)); if (AllowedExtensions != null && AllowedExtensions.Contains(fi.Extension)) @@ -414,6 +418,15 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.EndDisabled(); } } + else if (OnlyAllowFolders && !string.IsNullOrWhiteSpace(CurrentlySelectedFolder) && string.IsNullOrWhiteSpace(SelectedFile)) + { + ImGui.SameLine(sameLineOffset); + if (ImGui.Button($"{PickedFileTxt}##{_filePickerCount}", BottomButtonSize)) + { + OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, selectedFile)); + filePickerOpen = false; + } + } else { ImGui.SameLine(sameLineOffset); @@ -452,13 +465,20 @@ private List GetFileSystemEntries(string fullName) { dirs.Add(fse); } - else if (!OnlyAllowFolders) + else { - if (AllowedExtensions != null) + if (!OnlyAllowFolders) { - var ext = Path.GetExtension(fse); - if (AllowedExtensions.Contains(ext)) + if (AllowedExtensions != null) + { + var ext = Path.GetExtension(fse); + if (AllowedExtensions.Contains(ext)) + files.Add(fse); + } + else + { files.Add(fse); + } } else { From 9d2394cee4e5270ea8baebae428c15386461f29c Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 7 Mar 2023 13:57:18 +0100 Subject: [PATCH 102/294] PotreeData: fixed guard for File.Exists(hierarchyFilePath) --- src/PointCloud/Potree/V2/PotreeData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/V2/PotreeData.cs b/src/PointCloud/Potree/V2/PotreeData.cs index 16b3aa9f6..08eb11c22 100644 --- a/src/PointCloud/Potree/V2/PotreeData.cs +++ b/src/PointCloud/Potree/V2/PotreeData.cs @@ -37,7 +37,7 @@ private void LoadHierarchy(string folderPath) var hierarchyFilePath = Path.Combine(folderPath, Potree2Consts.HierarchyFileName); Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); - Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); + Guard.IsTrue(File.Exists(hierarchyFilePath), hierarchyFilePath); Metadata = LoadPotreeMetadata(metadataFilePath); Hierarchy = new() From 94b94b8e58999a67ddd78f9dfda57699bb394322 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 7 Mar 2023 14:55:44 +0100 Subject: [PATCH 103/294] Housekeeping, wire pointSpacing, prevent visitor map instancing when more than one ScenePicker is being used --- .../Core/Scene/PointCloudPickerModule.cs | 99 ++++++++++++------- src/Xene/Visitor.cs | 2 +- 2 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index d0463eb09..bababe2da 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -26,19 +26,38 @@ namespace Fusee.PointCloud.Core.Scene { + /// + /// Result of a point cloud pick operation + /// public class PointCloudPickResult : PickResult { + /// + /// The point mesh. + /// public Mesh Mesh; + /// + /// The index of the hit vertex, in this case the point index. + /// public int VertIdx; + /// + /// The of the in which the found point lies. + /// public OctantId OctantId; } + /// + /// Point cloud picker module. Inject to pick s + /// public class PointCloudPickerModule : IPickerModule { - private PickerState State; - private PointCloudOctree _octree; - private IPointCloudImp _pcImp; + private PickerState _state; + private readonly PointCloudOctree _octree; + private readonly IPointCloudImp _pcImp; + private readonly float _pointSpacing; + /// + /// The pick result after picking. + /// public PickResult PickResult { get; set; } internal struct MinPickValue @@ -49,8 +68,6 @@ internal struct MinPickValue internal OctantId OctantId; } - private Stopwatch sw = new Stopwatch(); - /// /// Determines visible points of a point cloud (using the components ) and renders them. /// @@ -58,23 +75,15 @@ internal struct MinPickValue [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { - sw.Start(); if (!pointCloud.Active) return; - var proj = State.CurrentCameraResult.Camera.GetProjectionMat(State.ScreenSize.x, State.ScreenSize.y, out _); - var view = State.CurrentCameraResult.View; - var rayD = new RayD(new double2(State.PickPosClip.x, State.PickPosClip.y), (double4x4)view, (double4x4)proj); - - Diagnostics.Info($"Setup took: {sw.ElapsedTicks}"); - sw.Restart(); + var proj = _state.CurrentCameraResult.Camera.GetProjectionMat(_state.ScreenSize.x, _state.ScreenSize.y, out _); + var view = _state.CurrentCameraResult.View; + var rayD = new RayD(new double2(_state.PickPosClip.x, _state.PickPosClip.y), (double4x4)view, (double4x4)proj); var tmpList = new List(); var allHitBoxes = PickOctantRecursively((PointCloudOctant)_octree.Root, rayD, tmpList).ToList(); - Diagnostics.Info($"PickOctantRecursively took: {sw.ElapsedTicks}"); - sw.Restart(); - - if (allHitBoxes == null || allHitBoxes.Count == 0) return; var currentRes = new ConcurrentBag(); @@ -93,7 +102,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) for (var i = 0; i < mesh.Vertices.Length; i++) { - var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], 0.017f); // spacing * 0.1? + var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], _pointSpacing); if (dist == float2.One * -1) continue; if (dist.x <= currentMin.Distance.x && dist.y <= currentMin.Distance.y) @@ -111,9 +120,6 @@ public void RenderPointCloud(PointCloudComponent pointCloud) } }); - Diagnostics.Info($"foreach took: {sw.ElapsedTicks}"); - sw.Restart(); - if (currentRes == null || currentRes.Count == 0) return; var minElement = currentRes.First(); @@ -126,16 +132,13 @@ public void RenderPointCloud(PointCloudComponent pointCloud) } } - Diagnostics.Info($"Min element took: {sw.ElapsedTicks}"); - sw.Restart(); - - var mvp = proj * view * State.Model; + var mvp = proj * view * _state.Model; PickResult = new PointCloudPickResult { Node = null, Projection = proj, View = view, - Model = State.Model, + Model = _state.Model, ClipPos = float4x4.TransformPerspective(mvp, minElement.Mesh.Vertices[minElement.VertIdx]), Mesh = minElement.Mesh, VertIdx = minElement.VertIdx, @@ -144,21 +147,37 @@ public void RenderPointCloud(PointCloudComponent pointCloud) } - - // sphere of size ra centered at point ce + /// + /// Calculates the intersection distance between a ray and a sphere. + /// + /// + /// + /// Center point of sphere/point + /// Radius of sphere with center point of + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] float2 SphereRayIntersection(float3 ro, float3 rd, float3 ce, float ra) { var oc = ro - ce; - float b = float3.Dot(oc, rd); - var qc = oc - b * rd; - float h = ra * ra - float3.Dot(qc, qc); - if (h < 0.0) new float2(-1.0f); // no intersection + var b = float3.Dot(oc, rd); + var c = float3.Dot(oc, oc) - ra * ra; + var h = b * b - c; + if (h < 0.0f) return new float2(-1.0f); // no intersection h = MathF.Sqrt(h); - if (float.IsNaN(h)) return new float2(-1.0f); + if (float.IsNaN(h) || float.IsInfinity(h)) return new float2(-1.0f); return new float2(-b - h, -b + h); + + //var oc = ro - ce; + //float b = float3.Dot(oc, rd); + //var qc = oc - b * rd; + //float h = ra * ra - float3.Dot(qc, qc); + //if (h < 0.0) new float2(-1.0f); // no intersection + //h = MathF.Sqrt(h); + //if (float.IsNaN(h)) return new float2(-1.0f); + //return new float2(-b - h, -b + h); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private List PickOctantRecursively(PointCloudOctant node, RayD ray, List list) { list.Add(node); @@ -175,18 +194,30 @@ private List PickOctantRecursively(PointCloudOctant node, RayD return list; } - public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp) + /// + /// Inject this to a to be able to pick s. + /// The actual point and data needs to be present a priori, however it's type is polymorph, therefore we need to inject those data, too. + /// + /// The of the . + /// The , needs to be of type + /// The spacing between points. For Potree use the metadata spacing component * 0.1f
e. g. Spacing = 2.18f, pass 0.218f to this ctor. + public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp, float pointSpacing) { if (pcImp == null) Diagnostics.Warn("No per point picking possible, no PointCloud type loaded"); _octree = (PointCloudOctree)octree; _pcImp = pcImp; + _pointSpacing = pointSpacing; } + /// + /// Set the current from external (this is done automatically by the method. + /// + /// public void SetState(PickerState state) { - State = state; + _state = state; } } } \ No newline at end of file diff --git a/src/Xene/Visitor.cs b/src/Xene/Visitor.cs index a14ddecde..f58483d9f 100644 --- a/src/Xene/Visitor.cs +++ b/src/Xene/Visitor.cs @@ -111,7 +111,7 @@ internal class VisitorSet // The static list of all known sets of visitor methods // This is kept to avoid building the visitor map again and again different instances of the same visitor. - private static Dictionary _visitorMap; + private /*static*/ Dictionary _visitorMap; #endregion #region Public Traversal Methods From 8a14240126979f3727d022b80781e4b4b3cb71a3 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 7 Mar 2023 15:19:25 +0100 Subject: [PATCH 104/294] Only positive distance values -> front of camera, get absolute min element --- src/PointCloud/Core/Scene/PointCloudPickerModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index bababe2da..72ee4c31b 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -103,7 +103,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) for (var i = 0; i < mesh.Vertices.Length; i++) { var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], _pointSpacing); - if (dist == float2.One * -1) continue; + if (dist.x < 0 || dist.y < 0) continue; if (dist.x <= currentMin.Distance.x && dist.y <= currentMin.Distance.y) { @@ -128,6 +128,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (r.Distance.x < minElement.Distance.x && r.Distance.y < minElement.Distance.y) { + // TODO: Test if a offset > e. g. 0.1 is necessary that we do not spawn a box inside the cull / near clipping plane :) minElement = r; } } @@ -147,6 +148,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) } + /// /// Calculates the intersection distance between a ray and a sphere. /// From f915a44dce72cfe0a079948fc9e23725e67eaeb2 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 7 Mar 2023 15:28:34 +0100 Subject: [PATCH 105/294] Add AABBs for Plane and Sphere, disable ScenePicker Warnings --- src/Engine/Core/Primitives/Plane.cs | 6 +++++- src/Engine/Core/Primitives/Sphere.cs | 5 ++++- src/Engine/Core/ScenePicker.cs | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Engine/Core/Primitives/Plane.cs b/src/Engine/Core/Primitives/Plane.cs index d2cb3e87f..7d8fe9b86 100644 --- a/src/Engine/Core/Primitives/Plane.cs +++ b/src/Engine/Core/Primitives/Plane.cs @@ -1,4 +1,5 @@ -using Fusee.Engine.Core.Scene; +using CommunityToolkit.Diagnostics; +using Fusee.Engine.Core.Scene; using Fusee.Math.Core; namespace Fusee.Engine.Core.Primitives @@ -47,6 +48,9 @@ public Plane() new float2(1, 0), }); + + Guard.IsNotNull(Vertices); + BoundingBox = new AABBf(Vertices.AsReadOnlySpan); #endregion } diff --git a/src/Engine/Core/Primitives/Sphere.cs b/src/Engine/Core/Primitives/Sphere.cs index 45578d0b2..c3ae29624 100644 --- a/src/Engine/Core/Primitives/Sphere.cs +++ b/src/Engine/Core/Primitives/Sphere.cs @@ -1,4 +1,5 @@ -using Fusee.Engine.Core.Scene; +using CommunityToolkit.Diagnostics; +using Fusee.Engine.Core.Scene; using Fusee.Math.Core; namespace Fusee.Engine.Core.Primitives @@ -15,6 +16,8 @@ public class Sphere : Mesh public Sphere(int segments, int rings) { BuildSphere(segments, rings); + Guard.IsNotNull(Vertices); + BoundingBox = new AABBf(Vertices.AsReadOnlySpan); } private void BuildSphere(int segments, int rings)// segments: Longitude ||| - rings: Latitude --- diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 8b67f9974..6093097e6 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -756,13 +756,13 @@ private void PickTriangleGeometry(Mesh mesh) if (mesh.BoundingBox == default) { - Diagnostics.Warn($"Current bounding box of {mesh} is default while mesh is being picked. Generating box ..."); + //Diagnostics.Warn($"Current bounding box of {mesh} is default while mesh is being picked. Generating box ..."); mesh.BoundingBox = new(mesh.Vertices.AsReadOnlySpan); } if (mesh.BoundingBox.Size.x <= 0 || mesh.BoundingBox.Size.y <= 0 || mesh.BoundingBox.Size.z <= 0) { - Diagnostics.Warn($"Current bounding box of {mesh} is smaller or equal to zero. Forcing a thickness in zero direction of >= float.Epsilon"); + //Diagnostics.Warn($"Current bounding box of {mesh} is smaller or equal to zero. Forcing a thickness in zero direction of >= float.Epsilon"); var maxX = mesh.BoundingBox.Size.x <= 0 ? 0.1f : mesh.BoundingBox.max.x; var maxY = mesh.BoundingBox.Size.y <= 0 ? 0.1f : mesh.BoundingBox.max.y; var maxZ = mesh.BoundingBox.Size.z <= 0 ? 0.1f : mesh.BoundingBox.max.z; From 2c42032c461a51a8abbd4a5a5ea4a905bb7a90bd Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Tue, 7 Mar 2023 16:19:19 +0000 Subject: [PATCH 106/294] Linting --- .../Core/Scene/PointCloudPickerModule.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 72ee4c31b..b40ca1004 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -1,28 +1,28 @@ using Fusee.Base.Core; +using Fusee.Base.Core; using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; +using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; +using Fusee.Structures; using Fusee.Xene; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; -using System.Text; -using static Fusee.Engine.Core.ScenePicker; using System.Linq; -using Microsoft.Extensions.Options; -using Fusee.PointCloud.Common; -using Fusee.Base.Core; -using Fusee.Structures; -using System.Threading.Tasks; -using System.Collections.Concurrent; using System.Numerics; using System.Runtime.CompilerServices; +using System.Text; using System.Threading; -using System.Diagnostics; +using System.Threading.Tasks; +using static Fusee.Engine.Core.ScenePicker; namespace Fusee.PointCloud.Core.Scene { From 79726e2e64bd47445da3ba7a59f1cf634c8700b4 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 7 Mar 2023 18:41:32 +0100 Subject: [PATCH 107/294] Initial head writer method --- src/PointCloud/Common/IPointWriter.cs | 35 ++-- src/PointCloud/Potree/Potree2LAS.cs | 155 +++++++++++++++++- .../Potree/V2/Data/PotreeMetadata.cs | 1 + 3 files changed, 169 insertions(+), 22 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index d9d1d6ecc..c0c40224a 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -59,7 +59,7 @@ public interface IPointWriterMetadata public string Projection { get; set; } /// - /// The point cloud hierarchy information + /// The point cloud hierarchy information /// public IPointWriterHierarchy Hierarchy { get; set; } @@ -69,7 +69,7 @@ public interface IPointWriterMetadata public double3 Offset { get; set; } /// - /// Global scale value. Points are being converted to int + /// Global scale value. Points are being converted to int /// During load this scale factor is being applied to convert int to double /// public double3 Scale { get; set; } @@ -99,7 +99,7 @@ public interface IPointWriterMetadata /// /// Every point writer (e. g. Potree, LAS, etc.) implements this interface /// - public interface IPointWriter + public interface IPointWriter { /// /// Returns the point type. @@ -107,22 +107,29 @@ public interface IPointWriter public PointType PointType { get; } /// - /// This methods takes a list s and converts it to the desired output format and writes the file - /// to disk at given + /// Path to save to + /// + public FileInfo SavePath {get;} + + /// + /// Necessary metadata, e. g. global scale, etc. + /// + public IPointWriterMetadata Metadata { get; } + + /// + /// This methods takes a list s and converts it to the desired output format and writes the file + /// to disk at given /// - /// Path to save to /// The point data as - /// Necessary metadata, e. g. global scale, etc. - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata); + public void WritePointcloudPoints(ReadOnlySpan points); /// - /// This methods takes a list of s and converts it to the desired output format and writes the file - /// to disk at given in an async manner + /// This methods takes a list of s and converts it to the desired output format and writes the file + /// to disk at given in an async manner /// - /// Path to save to - /// The point data as , no as ref types are not allowed + /// The point data as , no as ref types are not allowed /// during async operations - /// Necessary metadata, e. g. global scale, etc. - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); + + public Task WritePointcloudPointsAsync(ReadOnlyMemory points); } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6d0e7fe08..ce35a9b90 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,8 +1,14 @@ +using CommunityToolkit.Diagnostics; +using CommunityToolkit.HighPerformance; +using Fusee.Base.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; namespace Fusee.PointCloud.Potree @@ -36,10 +42,10 @@ public LASHeader() { } internal ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; internal ushort FileCreationYear = (ushort)DateTime.Now.Year; internal ushort HeaderSize = 375; - internal uint OffsetToPointData = 375; // sizeof(LASHeader) + internal uint OffsetToPointData = 375; internal uint NumberOfVariableLengthRecords = 0; internal byte PointDataRecordFormat = 2; - internal ushort PointDataRecordLength = 26; // sizeof(LASPoint) + internal ushort PointDataRecordLength = 26; internal uint LegacyNbrOfPoints = 0; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] @@ -97,25 +103,158 @@ public LASPoint() { } } /// - /// This class provides methods to convert and save clouds to LAS 1.4 + /// This class provides methods to convert and saves clouds to LAS 1.4 /// - public class Potree2LAS : IPointWriter + public class Potree2LAS : IPointWriter, IDisposable where PotreePoint : struct { /// /// Returns the point type. /// - public PointType PointType => PointType.PosD3ColF3InUsLblB; + public PointType PointType => PointType.Raw; - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata) + /// + /// The path the file is being saved to + /// + public FileInfo SavePath => _savePath; + + /// + /// The necessary metadata information + /// + public IPointWriterMetadata Metadata => _metadata; + + private readonly FileInfo _savePath; + private readonly Stream _fileStream; + private readonly IPointWriterMetadata _metadata; + + private bool disposedValue; + + // these information are filled during the first write call + private LASHeader _header; + + /// + /// Generate a writer instance. + /// Use the write methods to fill the file with points. + /// + /// Path to save the file (make sure the extension is .las!) + /// The metadata needed for the header (offset, scale) + public Potree2LAS(FileInfo savePath/*, IPointWriterMetadata metadata*/) { - throw new NotImplementedException(); + Guard.IsNotNull(savePath); + //Guard.IsNotNull(metadata); + Guard.IsTrue(savePath.Extension == ".las"); + if (savePath.Exists) + { + Diagnostics.Warn($"{savePath.FullName} does already exists. Overwriting ..."); + } + + _savePath = savePath; + _fileStream = _savePath.OpenWrite(); + //_metadata = metadata; + ParseAndFillHeader(); } - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata) + private void ParseAndFillHeader() + { + var doy = DateTime.Now.DayOfYear; + var year = DateTime.Now.Year; + var size = Marshal.SizeOf(); + + // make sure we can cast to and do not lose information + Guard.IsLessThan(doy, ushort.MaxValue); + Guard.IsLessThan(year, ushort.MaxValue); + Guard.IsLessThan(size, ushort.MaxValue); + + _header = new LASHeader + { + PointDataRecordLength = (ushort)size, + FileCreationDayOfYear = (ushort)doy, + FileCreationYear = (ushort)year, + //MaxX = _metadata.BoundingBox.max.x, + //MaxY = _metadata.BoundingBox.max.y, + //MaxZ = _metadata.BoundingBox.max.z, + //MinX = _metadata.BoundingBox.min.x, + //MinY = _metadata.BoundingBox.min.y, + //MinZ = _metadata.BoundingBox.min.z, + //OffsetX = _metadata.Offset.x, + //OffsetY = _metadata.Offset.y, + //OffsetZ = _metadata.Offset.z, + //ScaleFactorX = _metadata.Scale.x, + //ScaleFactorY = _metadata.Scale.y, + //ScaleFactorZ = _metadata.Scale.z + }; + + var generatingSoftware = Encoding.UTF8.GetBytes($"POLAR v.{Assembly.GetExecutingAssembly().GetName().Version}"); + Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); + Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); + + // Initialize unmanged memory to hold the struct. + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + try + { + // Copy the struct to unmanaged memory. + Marshal.StructureToPtr(_header, ptr, false); + var dest = new byte[Marshal.SizeOf()]; + Marshal.Copy(ptr, dest, 0, Marshal.SizeOf()); + _fileStream.Write(dest); + + } + finally + { + // Free the unmanaged memory. + Marshal.FreeHGlobal(ptr); + } + } + + /// + /// This methods takes a list s and converts it to the desired output format and appends it to the file + /// to disk at given + /// + /// The point data as + public void WritePointcloudPoints(ReadOnlySpan points) + { + Guard.IsNotEmpty(points); + } + + /// + /// This methods takes a list s and converts it to the desired output format and appends it to the file + /// to disk at given in an manner. + /// + /// The point data as + public Task WritePointcloudPointsAsync(ReadOnlyMemory points) { throw new NotImplementedException(); } + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // close the file stream + _fileStream.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~Potree2LAS() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + // old code, replace //private static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) //{ diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index 99f762a57..8b5245530 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -1,4 +1,5 @@ using Fusee.Math.Core; +using Fusee.PointCloud.Common; using Newtonsoft.Json; using System.Collections.Generic; From ad2faf81fa6917a4c92d92f62475f24a246904d6 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 8 Mar 2023 12:26:32 +0100 Subject: [PATCH 108/294] Ongoing PotreeReading, LAS writing --- src/PointCloud/Common/IPointWriter.cs | 9 +- .../Potree/Fusee.PointCloud.Potree.csproj | 2 +- src/PointCloud/Potree/Potree2LAS.cs | 111 ++++++++++++++---- src/PointCloud/Potree/V2/Potree2Reader.cs | 77 +++++++++++- 4 files changed, 167 insertions(+), 32 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index c0c40224a..7bcdb493b 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -120,16 +120,15 @@ public interface IPointWriter /// This methods takes a list s and converts it to the desired output format and writes the file /// to disk at given /// - /// The point data as - public void WritePointcloudPoints(ReadOnlySpan points); + /// The point data as + public void WritePointcloudPoints(Memory points); /// /// This methods takes a list of s and converts it to the desired output format and writes the file /// to disk at given in an async manner /// - /// The point data as , no as ref types are not allowed - /// during async operations + /// The point data as - public Task WritePointcloudPointsAsync(ReadOnlyMemory points); + public Task WritePointcloudPointsAsync(Memory points); } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index cd9dddb14..c0588c9a9 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -1,7 +1,7 @@ - netstandard2.1;net7.0 + net7.0 $(OutputPath)\$(RootNamespace).xml enable diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index ce35a9b90..8fd1d20ed 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,12 +1,14 @@ using CommunityToolkit.Diagnostics; using CommunityToolkit.HighPerformance; using Fusee.Base.Core; +using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using CommunityToolkit.HighPerformance; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -84,9 +86,9 @@ internal struct LASPoint { public LASPoint() { } - internal uint X = 0; - internal uint Y = 0; - internal uint Z = 0; + internal int X = 0; + internal int Y = 0; + internal int Z = 0; internal ushort Intensity = 0; internal byte ReturnNbrOfScanDirAndEdgeByte = 0; @@ -102,10 +104,26 @@ public LASPoint() { } internal ushort B = 0; } + public class LASPointWriterMetadata : IPointWriterMetadata + { + public string Version { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public int PointCount { get; set; } + public string Projection { get; set; } + public IPointWriterHierarchy Hierarchy { get; set; } + public double3 Offset { get; set; } + public double3 Scale { get; set; } + public double Spacing { get; set; } + public AABBd BoundingBox { get; set; } + public string Encoding { get; set; } + public int PointSize { get; set; } + } + /// /// This class provides methods to convert and saves clouds to LAS 1.4 /// - public class Potree2LAS : IPointWriter, IDisposable where PotreePoint : struct + public class Potree2LAS : IPointWriter, IDisposable { /// /// Returns the point type. @@ -124,7 +142,7 @@ public class Potree2LAS : IPointWriter, IDisposable wh private readonly FileInfo _savePath; private readonly Stream _fileStream; - private readonly IPointWriterMetadata _metadata; + private readonly LASPointWriterMetadata _metadata; private bool disposedValue; @@ -137,10 +155,11 @@ public class Potree2LAS : IPointWriter, IDisposable wh /// /// Path to save the file (make sure the extension is .las!) /// The metadata needed for the header (offset, scale) - public Potree2LAS(FileInfo savePath/*, IPointWriterMetadata metadata*/) + public Potree2LAS(FileInfo savePath, LASPointWriterMetadata metadata) { + Guard.IsNotNull(savePath); - //Guard.IsNotNull(metadata); + Guard.IsNotNull(metadata); Guard.IsTrue(savePath.Extension == ".las"); if (savePath.Exists) { @@ -149,7 +168,7 @@ public Potree2LAS(FileInfo savePath/*, IPointWriterMetadata metadata*/) _savePath = savePath; _fileStream = _savePath.OpenWrite(); - //_metadata = metadata; + _metadata = metadata; ParseAndFillHeader(); } @@ -169,18 +188,18 @@ private void ParseAndFillHeader() PointDataRecordLength = (ushort)size, FileCreationDayOfYear = (ushort)doy, FileCreationYear = (ushort)year, - //MaxX = _metadata.BoundingBox.max.x, - //MaxY = _metadata.BoundingBox.max.y, - //MaxZ = _metadata.BoundingBox.max.z, - //MinX = _metadata.BoundingBox.min.x, - //MinY = _metadata.BoundingBox.min.y, - //MinZ = _metadata.BoundingBox.min.z, - //OffsetX = _metadata.Offset.x, - //OffsetY = _metadata.Offset.y, - //OffsetZ = _metadata.Offset.z, - //ScaleFactorX = _metadata.Scale.x, - //ScaleFactorY = _metadata.Scale.y, - //ScaleFactorZ = _metadata.Scale.z + MaxX = _metadata.BoundingBox.max.x, + MaxY = _metadata.BoundingBox.max.y, + MaxZ = _metadata.BoundingBox.max.z, + MinX = _metadata.BoundingBox.min.x, + MinY = _metadata.BoundingBox.min.y, + MinZ = _metadata.BoundingBox.min.z, + OffsetX = _metadata.Offset.x, + OffsetY = _metadata.Offset.y, + OffsetZ = _metadata.Offset.z, + ScaleFactorX = _metadata.Scale.x, + ScaleFactorY = _metadata.Scale.y, + ScaleFactorZ = _metadata.Scale.z }; var generatingSoftware = Encoding.UTF8.GetBytes($"POLAR v.{Assembly.GetExecutingAssembly().GetName().Version}"); @@ -210,9 +229,57 @@ private void ParseAndFillHeader() /// to disk at given ///
/// The point data as - public void WritePointcloudPoints(ReadOnlySpan points) + public void WritePointcloudPoints(Memory points) { Guard.IsNotEmpty(points); + Parallel.For(0, points.Length, (i) => + { + // restore int, TODO: provide the possibility to skip the offset application by user (always use scalefactor) + points.Span[i].Position.x = (points.Span[i].Position.x - _header.OffsetX) / _header.ScaleFactorX; + points.Span[i].Position.y = (points.Span[i].Position.y - _header.OffsetY) / _header.ScaleFactorY; + points.Span[i].Position.z = (points.Span[i].Position.z - _header.OffsetZ) / _header.ScaleFactorZ; + }); + + for(var i = 0; i < points.Length; i++) + { + var pt = points.Span[i]; + // re-interpret the first bytes as int + // we need to shorten the first 8 bytes to 4 bytes + // internal int X = 0; + // internal int Y = 0; + // internal int Z = 0; + var intArr = new int[] { (int)pt.Position.x, (int)pt.Position.y, (int)pt.Position.z }; + var posInt = MemoryMarshal.Cast(intArr); + _fileStream.Write(posInt); + + + //internal ushort Intensity = 0; + _fileStream.Write((ushort)pt.Intensity); + //internal byte ReturnNbrOfScanDirAndEdgeByte = 0; + _fileStream.Write(pt.ReturnNumber); + //internal byte Classification = 0; + _fileStream.Write(pt.Classification); + //internal byte ScanAngleRank = 0; + _fileStream.Write(pt.ScanAngleRank); + //internal byte UserData = 0; + _fileStream.Write(pt.UserData); + + //internal ushort PtSrcID = 0 + _fileStream.Write(pt.PointSourceId); + + // internal ushort R = 0; + // internal ushort G = 0; + // internal ushort B = 0; + // scale from float range to ushort range + // reinterpret as ushort + pt.Color.x *= ushort.MaxValue; + pt.Color.y *= ushort.MaxValue; + pt.Color.z *= ushort.MaxValue; + var colorArr = new ushort[] { (ushort)pt.Color.x, (ushort)pt.Color.y, (ushort)pt.Color.z }; + var colorBytes = MemoryMarshal.Cast(colorArr); + _fileStream.Write(colorBytes); + } + } /// @@ -220,7 +287,7 @@ public void WritePointcloudPoints(ReadOnlySpan points) /// to disk at given in an manner. /// /// The point data as - public Task WritePointcloudPointsAsync(ReadOnlyMemory points) + public Task WritePointcloudPointsAsync(Memory points) { throw new NotImplementedException(); } diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 3278edd34..d4c04ab27 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -9,6 +9,7 @@ using Fusee.PointCloud.Potree.V2.Data; using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace Fusee.PointCloud.Potree.V2 { @@ -103,15 +104,13 @@ public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) ///
/// Id of the octant. /// - public MemoryOwner LoadNodeDataPotreePoint(OctantId id) + public MemoryOwner LoadRawNodeData(OctantId id) { Guard.IsNotNull(PotreeData); var node = FindNode(ref PotreeData.Hierarchy, id); - // if node is null the hierarchy is broken and we look for an octant that isn't there... Guard.IsNotNull(node); - - return LoadNodeData(node, PointType.Raw); + return ReadNodeData(node); } private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointType type) where TPoint : struct @@ -127,6 +126,76 @@ private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointTyp }; } + private MemoryOwner ReadNodeData(PotreeNode node) + { + Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + + var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; + + var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); + + OctreeMappedViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); + + var pointCount = 0; + + for (var i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) + { + var pt = pointArray.AsSpan(i, PotreeData.Metadata.PointSize); + var posSlice = pt.Slice(offsetPosition, Marshal.SizeOf() * 3); + var pos = MemoryMarshal.Cast(posSlice); + + double x = pos[0] * PotreeData.Metadata.Scale.x + PotreeData.Metadata.Offset.x; + double y = pos[1] * PotreeData.Metadata.Scale.y + PotreeData.Metadata.Offset.y; + double z = pos[2] * PotreeData.Metadata.Scale.z + PotreeData.Metadata.Offset.z; + + double3 position = new(x, y, z); + position = Potree2Consts.YZflip * position; + + /* public short Intensity; + public byte ReturnNumber; + public byte NumberOfReturns; + public byte Classification; + public byte ScanAngleRank; + public byte UserData; + public byte PointSourceId; + public float3 Color; + */ + + var intensity = MemoryMarshal.Cast(pt.Slice(offsetIntensity, Marshal.SizeOf()))[0]; + var returnNumber = pt.Slice(offsetReturnNumber, 1)[0]; + var numberOfReturns = pt.Slice(offsetNumberOfReturns, 1)[0]; + var classification = pt.Slice(offsetClassification, 1)[0]; + var scanAngle = pt.Slice(offsetScanAngleRank, 1)[0]; + var userData = pt.Slice(offsetUserData, 1)[0]; + var ptSourceId = pt.Slice(offsetPointSourceId, 1)[0]; + var r = MemoryMarshal.Cast(pt.Slice(offsetColor, Marshal.SizeOf()))[0]; + var g = MemoryMarshal.Cast(pt.Slice(offsetColor + Marshal.SizeOf(), Marshal.SizeOf()))[0]; + var b = MemoryMarshal.Cast(pt.Slice(offsetColor + Marshal.SizeOf() + Marshal.SizeOf(), Marshal.SizeOf()))[0]; + + var color = new float3 + { + x = ((float)r / ushort.MaxValue), + y = ((float)g / ushort.MaxValue), + z = ((float)b / ushort.MaxValue) + }; + + returnMemory.Span[pointCount].Position = position; + returnMemory.Span[pointCount].Intensity = intensity; + returnMemory.Span[pointCount].ReturnNumber = returnNumber; + returnMemory.Span[pointCount].NumberOfReturns = numberOfReturns; + returnMemory.Span[pointCount].Classification = classification; + returnMemory.Span[pointCount].ScanAngleRank = scanAngle; + returnMemory.Span[pointCount].UserData = userData; + returnMemory.Span[pointCount].PointSourceId = ptSourceId; + returnMemory.Span[pointCount].Color = color; + + pointCount++; + } + + return returnMemory; + } + private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) where TPoint : struct { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); From 92aeb3bac10b55a772b07547e78d77afe80ede1c Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Fri, 10 Mar 2023 16:01:28 +0100 Subject: [PATCH 109/294] Intitial conversion --- .../Core/PointCloudPotree2Core.cs | 34 +-- src/PointCloud/Common/IPointReader.cs | 5 - src/PointCloud/Common/IPointWriter.cs | 9 +- src/PointCloud/Common/PointTypes.cs | 263 ------------------ src/PointCloud/Core/MeshMaker.cs | 86 ++---- src/PointCloud/Core/VisualizationPoint.cs | 15 + src/PointCloud/Potree/Potree2LAS.cs | 9 +- src/PointCloud/Potree/V2/Data/PotreePoint.cs | 17 -- src/PointCloud/Potree/V2/Potree2Reader.cs | 84 +++--- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 5 - src/PointCloud/Potree/V2/Potree2Writer.cs | 184 ++++++------ src/PointCloud/Potree/V2/Potree2WriterBase.cs | 6 - 12 files changed, 173 insertions(+), 544 deletions(-) delete mode 100644 src/PointCloud/Common/PointTypes.cs create mode 100644 src/PointCloud/Core/VisualizationPoint.cs delete mode 100644 src/PointCloud/Potree/V2/Data/PotreePoint.cs diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index bdf3fddad..7f2c7c2c9 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -26,7 +26,7 @@ public bool ClosingRequested } private bool _closingRequested; - public RenderMode PointRenderMode = RenderMode.DynamicMesh; + public RenderMode PointRenderMode = RenderMode.Instanced; public string AssetsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); private static float _angleHorz, _angleVert, _angleVelHorz, _angleVelVert; @@ -52,8 +52,6 @@ public bool ClosingRequested private Potree2Reader _potreeReader; private PotreeData _potreeData; - private ScenePicker _picker; - private readonly RenderContext _rc; public void OnLoadNewFile(object sender, EventArgs e) @@ -65,19 +63,13 @@ public void OnLoadNewFile(object sender, EventArgs e) _potreeReader.ReadNewFile(path, out _potreeData); - _pointCloud = (PointCloudComponent)_potreeReader.GetPointCloudComponent(RenderMode.DynamicMesh); + _pointCloud = (PointCloudComponent)_potreeReader.GetPointCloudComponent(PointRenderMode); _pointCloud.PointCloudImp.MinProjSizeModifier = PointRenderingParams.Instance.ProjectedSizeModifier; _pointCloud.PointCloudImp.PointThreshold = PointRenderingParams.Instance.PointThreshold; _pointCloud.Camera = _cam; _pointCloudNode.Components[3] = _pointCloud; - - // re-generate picker and octree - _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() - { - new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) - }); } public PointCloudPotree2Core(RenderContext rc) @@ -165,18 +157,7 @@ public void Init() _sceneRenderer = new SceneRendererForward(_scene); _sceneRenderer.VisitorModules.Add(new PointCloudRenderModule(_sceneRenderer.GetType() == typeof(SceneRendererForward))); - _pointCloud.Camera = _cam; - - //_picker = new ScenePicker(_scene, Engine.Common.Cull.None, new List() - //{ - // new PointCloudPickerModule(((PointCloud.Potree.Potree2Cloud)_pointCloud.PointCloudImp).VisibilityTester.Octree, null) - //}); - - _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() - { - new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp) - }); - + _pointCloud.Camera = _cam; } // RenderAFrame is called once a frame @@ -247,15 +228,6 @@ public void Update(bool allowInput) _angleVelVert = 0; _camTransform.FpsView(_angleHorz, _angleVert, Input.Keyboard.WSAxis, Input.Keyboard.ADAxis, Time.DeltaTimeUpdate * 20); - - if (!_keys && Input.Mouse.LeftButton) - { - var width = _rc.ViewportWidth; - var height = _rc.ViewportHeight; - var pickPosClip = (Input.Mouse.Position * new float2(2.0f / width, -2.0f / height)) + new float2(-1, 1); - var result = _picker?.Pick(pickPosClip, width, height).ToList(); - if (result != null && result.Count != 0) Diagnostics.Debug(((PointCloudPickResult)result[0]).OctandID); - } } private void OnThresholdChanged(int newValue) diff --git a/src/PointCloud/Common/IPointReader.cs b/src/PointCloud/Common/IPointReader.cs index 8a33d4fee..3693d48f8 100644 --- a/src/PointCloud/Common/IPointReader.cs +++ b/src/PointCloud/Common/IPointReader.cs @@ -11,11 +11,6 @@ public interface IPointReader /// Determines which is used to display the returned point cloud."/> public IPointCloud GetPointCloudComponent(RenderMode renderMode); - /// - /// Returns the point type. - /// - public PointType PointType { get; } - /// /// Reads the Potree file and returns an octree. /// diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index d9d1d6ecc..58af9b063 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -101,11 +101,6 @@ public interface IPointWriterMetadata ///
public interface IPointWriter { - /// - /// Returns the point type. - /// - public PointType PointType { get; } - /// /// This methods takes a list s and converts it to the desired output format and writes the file /// to disk at given @@ -113,7 +108,7 @@ public interface IPointWriter /// Path to save to /// The point data as /// Necessary metadata, e. g. global scale, etc. - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata); + public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata); /// /// This methods takes a list of s and converts it to the desired output format and writes the file @@ -123,6 +118,6 @@ public interface IPointWriter /// The point data as , no as ref types are not allowed /// during async operations /// Necessary metadata, e. g. global scale, etc. - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); + public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); } } \ No newline at end of file diff --git a/src/PointCloud/Common/PointTypes.cs b/src/PointCloud/Common/PointTypes.cs deleted file mode 100644 index 8582dacf7..000000000 --- a/src/PointCloud/Common/PointTypes.cs +++ /dev/null @@ -1,263 +0,0 @@ -using Fusee.Math.Core; -using System.Runtime.InteropServices; - -namespace Fusee.PointCloud.Common -{ - /// - /// Enum that contains all available point types. - /// Abbreviations: - /// F3: float - /// D3: double - /// Us: ushort - /// B: byte - /// Pos: position - /// Col: Color - /// In: Intensity - /// Nor: Normal - /// Lbl: Label / Classification - /// - public enum PointType - { - /// - /// Point type is not set yet. - /// - Undefined, - - /// - /// Position only (double) - /// - PosD3, - /// - /// Position (double), Color (float), Intensity (short) - /// - PosD3ColF3InUs, - /// - /// Position (double), Intensity (ushort) - /// - PosD3InUs, - /// - /// Position (double), Color (float) - /// - PosD3ColF3, - /// - /// Position (double), Label (byte) - /// - PosD3LblB, - /// - /// Position (double), Normal (float), Color (float), Intensity (ushort) - /// - PosD3NorF3ColF3InUs, - /// - /// Position (double), Normal (float), Intensity (short) - /// - PosD3NorF3InUs, - /// - /// Position (double), Normal (float), Color (float) - /// - PosD3NorF3ColF3, - /// - /// Position (double), Color (float), Classification (byte) - /// - PosD3ColF3LblB, - /// - /// Position (double), Color (float), Classification (byte), Intensity (ushort) - /// - PosD3ColF3InUsLblB, - - /// - /// Everything.... - /// - Raw - } - - /// - /// Point type: Position double3. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3 - { - /// - /// The points coordinate in 3D space. - /// - public double3 Position; - } - - /// - /// Point type: Position, color, intensity. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3ColF3InUs - { - /// - /// The points coordinate in 3D space. - /// - public double3 Position; - /// - /// The points rgb color. - /// - public float3 Color; - /// - /// The points intensity (gray scale). - /// - public ushort Intensity; - } - - /// - /// Point type: Position, intensity. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3InUs - { - /// - /// The points coordinate in 3D space. - /// - public double3 Position; - /// - /// The points intensity (gray scale). - /// - public ushort Intensity; - } - - /// - /// Point type: Position, color. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3ColF3 - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's rgb color. - /// - public float3 Color; - } - - /// - /// Point type: Position and label color. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3LblB - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's struct label. - /// - public byte Label; - } - - /// - /// Point type: Position, normal, color, intensity. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3NorF3ColF3InUs - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's normal vector. - /// - public float3 Normal; - /// - /// The point's rgb color. - /// - public float3 Color; - /// - /// The point's intensity (gray scale). - /// - public ushort Intensity; - } - - // - - /// - /// Point type: Position, normal, intensity. - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3NorF3InUs - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's normal vector. - /// - public float3 Normal; - /// - /// The point's intensity (gray scale). - /// - public ushort Intensity; - } - - /// - /// Point type: , , . - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3NorF3ColF3 - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's normal vector. - /// - public float3 Normal; - /// - /// The point's rgb color. - /// - public float3 Color; - } - - - /// - /// Point type: , , . - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3ColF3LblB - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's rgb color. - /// - public float3 Color; - /// - /// The point's classification. - /// - public byte Label; - } - - /// - /// Point type: , , and . - /// - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct PosD3ColF3InUsLblB - { - /// - /// The point's coordinate in 3D space. - /// - public double3 Position; - /// - /// The point's rgb color. - /// - public float3 Color; - /// - /// The point's classification. - /// - public byte Label; - /// - /// The point's intensity (gray scale). - /// - public ushort Intensity; - } -} \ No newline at end of file diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index bc2e9bec1..272571b02 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -4,6 +4,7 @@ using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; +using Fusee.PointCloud.Potree.V2.Data; using System.Collections.Generic; using System.Runtime.InteropServices; @@ -73,49 +74,16 @@ public static IEnumerable CreateInstanceData(MemoryO } /// - /// Returns meshes for point clouds of type . - /// - /// The lists of "raw" points. - public static GpuMesh CreateMeshPosD3(MemoryOwner points) where TPoint : struct - { - int numberOfPointsInMesh; - numberOfPointsInMesh = points.Length; - - var pointsCasted = MemoryMarshal.Cast(points.Span); - - - var firstPos = (float3)pointsCasted[0].Position; - var vertices = new float3[numberOfPointsInMesh]; - var triangles = new uint[numberOfPointsInMesh]; - var boundingBox = new AABBf(firstPos, firstPos); - - for (int i = 0; i < points.Length; i++) - { - var pos = (float3)pointsCasted[i].Position; - - vertices[i] = pos; - boundingBox |= pos; - triangles[i] = (uint)i; - } - var mesh = ModuleExtensionPoint.CreateGpuMesh(PrimitiveType.Points, vertices, triangles); - mesh.BoundingBox = boundingBox; - return mesh; - } - - /// - /// Returns meshes for point clouds of type . + /// Returns meshes for point clouds of type . /// /// The lists of "raw" points. /// The id of the octant. - public static GpuMesh CreateMeshPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct + public static GpuMesh CreateMeshVisualizationPoint(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - // we know that we have a MeshPosD3ColF3LblB - var pointsCasted = MemoryMarshal.Cast(points.Span); - - var firstPos = (float3)pointsCasted[0].Position; + var firstPos = points.Span[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new uint[numberOfPointsInMesh]; var colors = new uint[numberOfPointsInMesh]; @@ -124,18 +92,15 @@ public static GpuMesh CreateMeshPosD3ColF3LblB(MemoryOwner point for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointsCasted[i].Position; + var pos = points.Span[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (uint)i; - var col = pointsCasted[i].Color; + var col = points.Span[i].Color; colors[i] = ColorToUInt((int)col.r, (int)col.g, (int)col.b, 255); - flags[i] = 1 << 30; - - //TODO: add labels correctly - var label = pointsCasted[i].Label; + flags[i] = points.Span[i].Flags; } var mesh = ModuleExtensionPoint.CreateGpuMesh(PrimitiveType.Points, vertices, triangles, null, colors, null, null, null, null, null, null, null, flags); mesh.BoundingBox = boundingBox; @@ -143,19 +108,16 @@ public static GpuMesh CreateMeshPosD3ColF3LblB(MemoryOwner point } /// - /// Returns meshes for point clouds of type . + /// Returns meshes for point clouds of type . /// /// The lists of "raw" points. /// The id of the octant. - public static Mesh CreateDynamicMeshPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct + public static Mesh CreateDynamicMeshVisualizationPoint(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - // we know that we have a MeshPosD3ColF3LblB - var pointsCasted = MemoryMarshal.Cast(points.Span); - - var firstPos = (float3)pointsCasted[0].Position; + var firstPos = points.Span[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new uint[numberOfPointsInMesh]; var colors = new uint[numberOfPointsInMesh]; @@ -164,18 +126,15 @@ public static Mesh CreateDynamicMeshPosD3ColF3LblB(MemoryOwner p for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointsCasted[i].Position; + var pos = points.Span[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (uint)i; - var col = pointsCasted[i].Color; + var col = points.Span[i].Color; colors[i] = ColorToUInt((int)col.r, (int)col.g, (int)col.b, 255); - flags[i] = 1 << 30; - - //TODO: add labels correctly - var label = pointsCasted[i].Label; + flags[i] = points.Span[i].Flags; } return new Mesh(triangles, vertices, null, null, null, null, null, null, colors, null, null, flags) @@ -190,33 +149,31 @@ public static Mesh CreateDynamicMeshPosD3ColF3LblB(MemoryOwner p /// /// The lists of "raw" points. /// The id of the octant. - public static InstanceData CreateInstanceDataPosD3ColF3LblB(MemoryOwner points, OctantId octantId) where TPoint : struct + public static InstanceData CreateInstanceDataVisualizationPoint(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; - var pointsCasted = MemoryMarshal.Cast(points.Span); - - var firstPos = (float3)pointsCasted[0].Position; + var firstPos = points.Span[0].Position; var vertices = new float3[numberOfPointsInMesh]; var triangles = new ushort[numberOfPointsInMesh]; var colors = new float4[numberOfPointsInMesh]; var boundingBox = new AABBf(firstPos, firstPos); + var flags = new uint[numberOfPointsInMesh]; for (int i = 0; i < points.Length; i++) { - var pos = (float3)pointsCasted[i].Position; + var pos = points.Span[i].Position; vertices[i] = pos; boundingBox |= vertices[i]; triangles[i] = (ushort)i; - colors[i] = new float4(pointsCasted[i].Color / 256, 1.0f); - - //TODO: add labels correctly - var label = pointsCasted[i].Label; + colors[i] = new float4(points.Span[i].Color.xyz / 256, points.Span[i].Color.w); + flags[i] = points.Span[i].Flags; } + // TODO: Add flags to InstanceData return new InstanceData(points.Length, vertices, null, null, colors) { Name = octantId.ToString() @@ -281,4 +238,5 @@ private static uint ColorToUint(float4 col) #endregion } -} \ No newline at end of file +} + \ No newline at end of file diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs new file mode 100644 index 000000000..48375dea0 --- /dev/null +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -0,0 +1,15 @@ +using Fusee.Math.Core; + +namespace Fusee.PointCloud.Potree.V2.Data +{ + /// + /// This point is used for visualization purposes. + /// It is read by and converted to mesh data. + /// + public struct VisualizationPoint + { + public float3 Position; + public float4 Color; + public uint Flags; + } +} \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6d0e7fe08..e6c112f5f 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -101,17 +101,12 @@ public LASPoint() { } /// public class Potree2LAS : IPointWriter { - /// - /// Returns the point type. - /// - public PointType PointType => PointType.PosD3ColF3InUsLblB; - - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata) + public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata) { throw new NotImplementedException(); } - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata) + public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata) { throw new NotImplementedException(); } diff --git a/src/PointCloud/Potree/V2/Data/PotreePoint.cs b/src/PointCloud/Potree/V2/Data/PotreePoint.cs deleted file mode 100644 index a35c76e4d..000000000 --- a/src/PointCloud/Potree/V2/Data/PotreePoint.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Fusee.Math.Core; - -namespace Fusee.PointCloud.Potree.V2.Data -{ - public struct PotreePoint - { - public double3 Position; - public short Intensity; - public byte ReturnNumber; - public byte NumberOfReturns; - public byte Classification; - public byte ScanAngleRank; - public byte UserData; - public byte PointSourceId; - public float3 Color; - } -} \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 3278edd34..7d5187484 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Diagnostics; +using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance.Buffers; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; @@ -17,6 +18,12 @@ namespace Fusee.PointCloud.Potree.V2 ///
public class Potree2Reader : Potree2ReaderBase, IPointReader { + public readonly int OffsetToExtraBytes; + public Func? HandleExtraBytes { get; set; } + + public Potree2Reader(int offsetToExtraBytes = 0) : base() => (OffsetToExtraBytes) = (offsetToExtraBytes); + + /// /// Returns a renderable point cloud component. /// @@ -28,22 +35,22 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta default: case RenderMode.StaticMesh: { - var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshPosD3ColF3LblB, - LoadNodeDataPosD3ColF3LblB); + var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshVisualizationPoint, + LoadVisualizationPointData); var imp = new Potree2Cloud(dataHandler, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.Instanced: { - var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataPosD3ColF3LblB, - LoadNodeDataPosD3ColF3LblB, true); + var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataVisualizationPoint, + LoadVisualizationPointData, true); var imp = new Potree2CloudInstanced(dataHandlerInstanced, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.DynamicMesh: { - var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshPosD3ColF3LblB, - LoadNodeDataPosD3ColF3LblB); + var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshVisualizationPoint, + LoadVisualizationPointData); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); } @@ -83,27 +90,11 @@ public IPointCloudOctree GetOctree() } /// - /// Reads the points for a specific octant of type . - /// - /// Id of the octant. - /// - public MemoryOwner LoadNodeDataPosD3ColF3LblB(OctantId id) - { - Guard.IsNotNull(PotreeData); - var node = FindNode(ref PotreeData.Hierarchy, id); - - // if node is null the hierarchy is broken and we look for an octant that isn't there... - Guard.IsNotNull(node); - - return LoadNodeData(node, PointType.PosD3ColF3LblB); - } - - /// - /// Reads the points for a specific octant of type . + /// Reads the points for a specific octant of type . /// /// Id of the octant. /// - public MemoryOwner LoadNodeDataPotreePoint(OctantId id) + public MemoryOwner LoadVisualizationPointData(OctantId id) { Guard.IsNotNull(PotreeData); var node = FindNode(ref PotreeData.Hierarchy, id); @@ -111,30 +102,20 @@ public MemoryOwner LoadNodeDataPotreePoint(OctantId id) // if node is null the hierarchy is broken and we look for an octant that isn't there... Guard.IsNotNull(node); - return LoadNodeData(node, PointType.Raw); - } - - private MemoryOwner LoadNodeData(PotreeNode potreeNode, PointType type) where TPoint : struct - { - // if the potree node is null #nullable doesn't work! - Guard.IsNotNull(potreeNode); - potreeNode.IsLoaded = true; - return type switch - { - // TODO: add missing cases - PointType.PosD3ColF3LblB => ReadNodeDataPosD3ColF3LblB(potreeNode), - _ => ThrowHelper.ThrowArgumentOutOfRangeException>(nameof(type)), - }; + return LoadVisualizationPoint(node); } - private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) where TPoint : struct + private MemoryOwner LoadVisualizationPoint(PotreeNode node) { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + if (HandleExtraBytes != null) + Guard.IsGreaterThan(OffsetToExtraBytes, 0); + Guard.IsNotNull(PotreeData); var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; var pointArray = new byte[potreePointSize]; - var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); + var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); OctreeMappedViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); @@ -149,28 +130,37 @@ private MemoryOwner ReadNodeDataPosD3ColF3LblB(PotreeNode node) double y = pos[1] * PotreeData.Metadata.Scale.y; double z = pos[2] * PotreeData.Metadata.Scale.z; - double3 position = new(x, y, z); - position = Potree2Consts.YZflip * position; + float3 position = new((float)x, (float)y, (float)z); + position = (float4x4)Potree2Consts.YZflip * position; - var posSpan = MemoryMarshal.Cast(position.ToArray()); + var posSpan = MemoryMarshal.Cast(position.ToArray()); var colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); var rgb = MemoryMarshal.Cast(colorSlice); - var color = float3.Zero; + var color = float4.Zero; color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); + color.a = 1; var colorSpan = MemoryMarshal.Cast(color.ToArray()); - byte label = new Span(pointArray).Slice(i + offsetClassification, Marshal.SizeOf())[0]; + var extraByteSize = PotreeData.Metadata.PointSize - OffsetToExtraBytes; + var extraBytesSpan = pointArray.AsSpan().Slice(i + OffsetToExtraBytes, extraByteSize); + + uint flags = 0; + if (HandleExtraBytes != null) + { + flags = HandleExtraBytes(extraBytesSpan.ToArray()); + } + var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); - var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); + var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); posSpan.CopyTo(currentMemoryPt[..]); colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); - currentMemoryPt[^1] = label; + flagsSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length + colorSpan.Length, Marshal.SizeOf())); pointCount++; } diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index ee5f4f14e..84b5cf280 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -112,11 +112,6 @@ private set } } - /// - /// Returns the point type. - /// - public PointType PointType => PointType.PosD3ColF3LblB; - /// /// Reads a potree file. /// diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index ca00e9be8..3d37fae30 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -24,7 +24,7 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } /// /// /// - public (long octants, long points) Write(Predicate nodeSelector, Predicate pointSelector, Action action, bool dryrun = false) + public (long octants, long points) Write(Predicate nodeSelector, Predicate pointSelector, Action action, bool dryrun = false) { long octantCount = 0; long pointsCount = 0; @@ -262,96 +262,96 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } return (octantCount, pointsCount); } - public void WriteRawPoints(OctantId oid, PotreePoint[] points) - { - var node = FindNode(ref _potreeData.Hierarchy, oid); - - if (points.Length != node.NumPoints) - { - //TODO: (throw) correct error - throw new Exception(); - } - - using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - { - BinaryWriter binaryWriter = new BinaryWriter(writeStream); - - for (int i = 0; i < points.Length; i++) - { - var point = points[i]; - - if (offsetPosition > -1) - { - // TODO: Fix position conversion - //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - //var position = Potree2Consts.YZflip * point.Position; - - //binaryWriter.Write(position.x); - //binaryWriter.Write(position.y); - //binaryWriter.Write(position.z); - } - - if (offsetIntensity > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Intensity); - } - - if (offsetReturnNumber > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ReturnNumber); - } - - if (offsetNumberOfReturns > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.NumberOfReturns); - } - - if (offsetClassification > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.Classification); - } - - if (offsetScanAngleRank > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.ScanAngleRank); - } - - if (offsetUserData > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.UserData); - } - - if (offsetPointSourceId > -1) - { - binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - binaryWriter.Write(point.PointSourceId); - } - - if (offsetColor > -1) - { - // TODO: Fix color conversion - //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); - //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); - //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); - - //binaryWriter.Write(r); - //binaryWriter.Write(g); - //binaryWriter.Write(b); - } - } - - binaryWriter.Close(); - binaryWriter.Dispose(); - } - } + //public void WriteRawPoints(OctantId oid, byte[] points) + //{ + // var node = FindNode(ref _potreeData.Hierarchy, oid); + + // if (points.Length != node.NumPoints) + // { + // //TODO: (throw) correct error + // throw new Exception(); + // } + + // using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + // { + // BinaryWriter binaryWriter = new BinaryWriter(writeStream); + + // for (int i = 0; i < points.Length; i++) + // { + // var point = points[i]; + + // if (offsetPosition > -1) + // { + // // TODO: Fix position conversion + // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; + + // //var position = Potree2Consts.YZflip * point.Position; + + // //binaryWriter.Write(position.x); + // //binaryWriter.Write(position.y); + // //binaryWriter.Write(position.z); + // } + + // if (offsetIntensity > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Intensity); + // } + + // if (offsetReturnNumber > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ReturnNumber); + // } + + // if (offsetNumberOfReturns > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.NumberOfReturns); + // } + + // if (offsetClassification > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.Classification); + // } + + // if (offsetScanAngleRank > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.ScanAngleRank); + // } + + // if (offsetUserData > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.UserData); + // } + + // if (offsetPointSourceId > -1) + // { + // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; + // binaryWriter.Write(point.PointSourceId); + // } + + // if (offsetColor > -1) + // { + // // TODO: Fix color conversion + // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; + + // //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); + // //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); + // //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); + + // //binaryWriter.Write(r); + // //binaryWriter.Write(g); + // //binaryWriter.Write(b); + // } + // } + + // binaryWriter.Close(); + // binaryWriter.Dispose(); + // } + //} } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2WriterBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs index 45ee7332e..27a5aa101 100644 --- a/src/PointCloud/Potree/V2/Potree2WriterBase.cs +++ b/src/PointCloud/Potree/V2/Potree2WriterBase.cs @@ -122,12 +122,6 @@ public Potree2WriterBase(PotreeData potreeData) PotreeData = potreeData; } - /// - /// Returns the point type. - /// - public PointType PointType => PointType.PosD3ColF3LblB; - - /// /// Read and cache metadata from the metadata.json file /// From 8eb90274f75e6b014fe64126452815ebf54af2aa Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Fri, 10 Mar 2023 16:10:03 +0100 Subject: [PATCH 110/294] Removed redundant color conversion --- src/PointCloud/Core/MeshMaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 272571b02..028172798 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -169,7 +169,7 @@ public static InstanceData CreateInstanceDataVisualizationPoint(MemoryOwner Date: Fri, 10 Mar 2023 16:41:49 +0100 Subject: [PATCH 111/294] PointCloudInstanced shaders: updated to version 460 core & removed unused var --- src/PointCloud/Common/Assets/PointCloudInstanced.vert | 3 +-- src/PointCloud/Common/Assets/PointDepthInstanced.frag | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PointCloud/Common/Assets/PointCloudInstanced.vert b/src/PointCloud/Common/Assets/PointCloudInstanced.vert index d03cccbde..7c2ce14d3 100644 --- a/src/PointCloud/Common/Assets/PointCloudInstanced.vert +++ b/src/PointCloud/Common/Assets/PointCloudInstanced.vert @@ -1,4 +1,4 @@ -#version 300 es +#version 460 core precision highp float; @@ -16,7 +16,6 @@ out float vWorldSpacePointRad; out vec2 vPointCoord; //equivalent to gl_pointCoord in vec3 fuVertex; -in vec3 fuColor; in mat4 fuInstanceModelMat; //in vec3 fuInstanceColor; diff --git a/src/PointCloud/Common/Assets/PointDepthInstanced.frag b/src/PointCloud/Common/Assets/PointDepthInstanced.frag index 454300221..1a39b15d0 100644 --- a/src/PointCloud/Common/Assets/PointDepthInstanced.frag +++ b/src/PointCloud/Common/Assets/PointDepthInstanced.frag @@ -1,4 +1,4 @@ -#version 330 core +#version 460 core uniform mat4 FUSEE_P; uniform int PointShape; From 5c1a465714a6f46b3005df740093410df80272eb Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 13 Mar 2023 08:35:45 +0100 Subject: [PATCH 112/294] Fixed instanced point cloud color --- src/PointCloud/Core/MeshMaker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 028172798..5fb5bc4cf 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -169,7 +169,7 @@ public static InstanceData CreateInstanceDataVisualizationPoint(MemoryOwner Date: Mon, 13 Mar 2023 08:40:53 +0100 Subject: [PATCH 113/294] Removed PoinT, added comments --- src/PointCloud/Core/MeshMaker.cs | 11 +++++------ src/PointCloud/Core/PointCloudDataHandler.cs | 18 +++++++++--------- src/PointCloud/Potree/V2/Potree2Reader.cs | 17 ++++++++++++++--- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 5fb5bc4cf..2e5e3ecf7 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -21,7 +21,7 @@ public static class MeshMaker /// The generic point cloud points. /// The method that defines how to create a GpuMesh from the point cloud points. /// - public static IEnumerable CreateMeshes(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) + public static IEnumerable CreateMeshes(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { List meshes; @@ -40,11 +40,11 @@ public static IEnumerable CreateMeshes(MemoryOwner pointsPerMesh; + MemoryOwner pointsPerMesh; if (ptCnt > maxVertCount) { - pointsPerMesh = MemoryOwner.Allocate(numberOfPointsInMesh); - points.Span.Slice(i, numberOfPointsInMesh).CopyTo(pointsPerMesh.Span.Slice(0)); + pointsPerMesh = MemoryOwner.Allocate(numberOfPointsInMesh); + points.Span.Slice(i, numberOfPointsInMesh).CopyTo(pointsPerMesh.Span[..]); } else { @@ -61,11 +61,10 @@ public static IEnumerable CreateMeshes(MemoryOwner /// Can be of type or . The latter is used when rendering instanced. - /// The generic point type. /// The generic point cloud points. /// The method that defines how to create a InstanceData from the point cloud points. /// - public static IEnumerable CreateInstanceData(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) + public static IEnumerable CreateInstanceData(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { return new List { diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 071158cb4..6bc79a077 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -4,6 +4,7 @@ using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.PointCloud.Common; +using Fusee.PointCloud.Potree.V2.Data; using Microsoft.Extensions.Caching.Memory; using System; using System.Buffers; @@ -23,7 +24,7 @@ namespace Fusee.PointCloud.Core /// Delegate that allows to inject the loading method of the PointReader - loads the points from file. ///
/// Unique ID of an octant. - public delegate MemoryOwner LoadPointsHandler(OctantId guid); + public delegate MemoryOwner LoadPointsHandler(OctantId guid); /// /// Delegate for a method that tries to get the mesh(es) of an octant. If they are not cached yet, they should be created an added to the _gpuDataCache. @@ -50,30 +51,29 @@ namespace Fusee.PointCloud.Core /// Generic delegate to inject a method that nows how to actually create a GpuMesh or InstanceData for the given point type. /// /// - /// Generic that describes the point type. /// The point cloud points as generic array. /// - public delegate TGpuData CreateGpuData(MemoryOwner points, OctantId octantId); + public delegate TGpuData CreateGpuData(MemoryOwner points, OctantId octantId); /// /// Manages the caching and loading of point and mesh data. /// /// /// - public class PointCloudDataHandler : PointCloudDataHandlerBase, IDisposable where TPoint : new() where TGpuData : IDisposable + public class PointCloudDataHandler : PointCloudDataHandlerBase, IDisposable where TGpuData : IDisposable { /// /// Caches loaded points. /// - private MemoryCache> _pointCache; + private MemoryCache> _pointCache; /// /// Caches loaded points. /// private MemoryCache> _gpuDataCache; - private readonly CreateGpuData _createGpuDataHandler; - private readonly LoadPointsHandler _loadPointsHandler; + private readonly CreateGpuData _createGpuDataHandler; + private readonly LoadPointsHandler _loadPointsHandler; private const int _maxNumberOfDisposals = 1; private float _deltaTimeSinceLastDisposal; private readonly bool _doRenderInstanced; @@ -86,7 +86,7 @@ namespace Fusee.PointCloud.Core /// Method that knows how to create a mesh for the explicit point type (see ). /// The method that is able to load the points from the hard drive/file. /// - public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPointsHandler loadPointsHandler, bool doRenderInstanced = false) + public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPointsHandler loadPointsHandler, bool doRenderInstanced = false) { _pointCache = new(); _gpuDataCache = new() @@ -107,7 +107,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, _pointCache.HandleEvictedItem += (object key, object? value, EvictionReason reason, object? state) => { - if (value != null && value is MemoryOwner mo) + if (value != null && value is MemoryOwner mo) { mo.Dispose(); } diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 7d5187484..ce6e76283 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -18,9 +18,20 @@ namespace Fusee.PointCloud.Potree.V2 ///
public class Potree2Reader : Potree2ReaderBase, IPointReader { + /// + /// Specify the byte offset for one point until the extra byte data is reached + /// public readonly int OffsetToExtraBytes; + + /// + /// Pass method how to handle the extra bytes, resulting uint will be passed into . + /// public Func? HandleExtraBytes { get; set; } + /// + /// Generate a new instance of . + /// + /// public Potree2Reader(int offsetToExtraBytes = 0) : base() => (OffsetToExtraBytes) = (offsetToExtraBytes); @@ -35,21 +46,21 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta default: case RenderMode.StaticMesh: { - var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshVisualizationPoint, + var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshVisualizationPoint, LoadVisualizationPointData); var imp = new Potree2Cloud(dataHandler, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.Instanced: { - var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataVisualizationPoint, + var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataVisualizationPoint, LoadVisualizationPointData, true); var imp = new Potree2CloudInstanced(dataHandlerInstanced, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.DynamicMesh: { - var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshVisualizationPoint, + var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshVisualizationPoint, LoadVisualizationPointData); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); From 5a433ccc31e481aa6499c8bb0c84b32e4276ea86 Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Mon, 13 Mar 2023 07:47:34 +0000 Subject: [PATCH 114/294] Linting --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 2 +- src/PointCloud/Core/MeshMaker.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 7f2c7c2c9..a37ff7cf9 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -157,7 +157,7 @@ public void Init() _sceneRenderer = new SceneRendererForward(_scene); _sceneRenderer.VisitorModules.Add(new PointCloudRenderModule(_sceneRenderer.GetType() == typeof(SceneRendererForward))); - _pointCloud.Camera = _cam; + _pointCloud.Camera = _cam; } // RenderAFrame is called once a frame diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 2e5e3ecf7..f651c9f6b 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -237,5 +237,4 @@ private static uint ColorToUint(float4 col) #endregion } -} - \ No newline at end of file +} \ No newline at end of file From 9ef80189a5c7cac433a0d31f1f64a5fac817a844 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 13 Mar 2023 09:32:29 +0100 Subject: [PATCH 115/294] Moved memory mapped file instance to PotreeData, and all JSON reading to Potree2ReaderBase. --- src/PointCloud/Potree/V2/Data/PotreeData.cs | 69 +++++ src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 259 +++++++++++++++--- src/PointCloud/Potree/V2/Potree2Writer.cs | 10 +- src/PointCloud/Potree/V2/Potree2WriterBase.cs | 31 +-- src/PointCloud/Potree/V2/PotreeData.cs | 209 -------------- 5 files changed, 301 insertions(+), 277 deletions(-) create mode 100644 src/PointCloud/Potree/V2/Data/PotreeData.cs delete mode 100644 src/PointCloud/Potree/V2/PotreeData.cs diff --git a/src/PointCloud/Potree/V2/Data/PotreeData.cs b/src/PointCloud/Potree/V2/Data/PotreeData.cs new file mode 100644 index 000000000..ab063804d --- /dev/null +++ b/src/PointCloud/Potree/V2/Data/PotreeData.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.IO.MemoryMappedFiles; + +namespace Fusee.PointCloud.Potree.V2.Data +{ + /// + /// Contains information about the Potree file's meta data and hierarchy/octree. + /// + public class PotreeData : IDisposable + { + /// + /// The hierarchy as linear list of s. + /// + public PotreeHierarchy Hierarchy; + + /// + /// The meta data of the file. + /// + public PotreeMetadata Metadata; + + /// + /// Returns a reference to the memory mapped file for octree.bin. + /// + internal MemoryMappedFile OctreeMappedFile { get; } + + /// + /// Creats a new instance of PotreeData + /// + /// + /// + public PotreeData(PotreeHierarchy potreeHierarchy, PotreeMetadata potreeMetadata) + { + Hierarchy = potreeHierarchy; + Metadata = potreeMetadata; + + var path = Path.Combine(Metadata.FolderPath, Potree2Consts.OctreeFileName); + + OctreeMappedFile = MemoryMappedFile.CreateFromFile(path, FileMode.Open); + } + + private bool disposedValue; + + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + OctreeMappedFile.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index ee5f4f14e..cc3367696 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -1,9 +1,13 @@ using CommunityToolkit.Diagnostics; +using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; +using Newtonsoft.Json; using System; +using System.Collections.Generic; using System.IO; using System.IO.MemoryMappedFiles; +using System.Linq; namespace Fusee.PointCloud.Potree.V2 { @@ -13,7 +17,7 @@ namespace Fusee.PointCloud.Potree.V2 public abstract class Potree2ReaderBase : IDisposable { /// - /// The + /// The /// public PotreeData? PotreeData { @@ -67,48 +71,25 @@ private set ///
protected int offsetColor = -1; - private string? _octreeFilePath; - private MemoryMappedFile? _octreeMappedFile; private MemoryMappedViewAccessor? _octreeViewAccessor; private bool disposedValue; /// - /// The to the underlying octree.bin file + /// The to the underlying octree.bin file. This is threadsafe since there is never any overlapping access per our design. /// protected MemoryMappedViewAccessor OctreeMappedViewAccessor { get { Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); - Guard.IsNotNull(_octreeMappedFile, nameof(_octreeMappedFile)); return _octreeViewAccessor; } - } - - /// - /// The path to the octree.bin file - /// On assign the and the is being created. - /// - protected string OctreeFilePath - { - private set + set { - Guard.IsNotNullOrEmpty(value, nameof(value)); - Guard.IsTrue(File.Exists(value)); - - _octreeFilePath = value; - - _octreeMappedFile?.Dispose(); _octreeViewAccessor?.Dispose(); - _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); - _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); - } - get - { - Guard.IsNotNullOrEmpty(_octreeFilePath); - return _octreeFilePath; + _octreeViewAccessor = value; } } @@ -121,27 +102,52 @@ private set /// Reads a potree file. ///
/// Path to the file. + /// Meta and octree data of the potree file. + public PotreeData ReadNewFile(string path) + { + (var Metadata, var Hierarchy)= LoadHierarchy(path); + + PotreeData = new PotreeData(Hierarchy, Metadata); + + CacheMetadata(true); + + OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); + + return PotreeData; + } + + /// + /// Changes the potree data package that is currently bound to the reader. So a reader can be used for multiple data packages, this avoids rereading the potree data like in . + /// /// Meta and octree data of the potree file. - public void ReadNewFile(string path, out PotreeData potreeData) + public void ReadFile(PotreeData potreeData) { - _isMetadataCached = false; - PotreeData = new PotreeData(path); + PotreeData = potreeData; - CacheMetadata(); - OctreeFilePath = Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName); + CacheMetadata(true); - potreeData = PotreeData; + OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); } /// /// Read and cache metadata from the metadata.json file /// - protected void CacheMetadata() + protected void CacheMetadata(bool force = false) { Guard.IsNotNull(_potreeData); - if (!_isMetadataCached) + if (!_isMetadataCached || force) { + offsetPosition = -1; + offsetIntensity = -1; + offsetReturnNumber = -1; + offsetNumberOfReturns = -1; + offsetClassification = -1; + offsetScanAngleRank = -1; + offsetUserData = -1; + offsetPointSourceId = -1; + offsetColor = -1; + if (_potreeData.Metadata.Attributes.ContainsKey("position")) { offsetPosition = _potreeData.Metadata.Attributes["position"].AttributeOffset; @@ -206,6 +212,188 @@ public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); } + #region LoadHierarchy + + private (PotreeMetadata, PotreeHierarchy) LoadHierarchy(string folderPath) + { + var metadataFilePath = Path.Combine(folderPath, Potree2Consts.MetadataFileName); + var hierarchyFilePath = Path.Combine(folderPath, Potree2Consts.HierarchyFileName); + + Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); + Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); + + var Metadata = LoadPotreeMetadata(metadataFilePath); + var Hierarchy = new PotreeHierarchy() + { + Root = new() + { + Name = "r", + } + }; + + Metadata.Attributes = GetAttributesDict(Metadata.AttributesList); + + Metadata.FolderPath = folderPath; + + CalculateAttributeOffsets(ref Metadata); + + Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); + + var data = File.ReadAllBytes(hierarchyFilePath); + + Guard.IsNotNull(data, nameof(data)); + + LoadHierarchyRecursive(ref Hierarchy.Root, ref data, 0, Metadata.Hierarchy.FirstChunkSize); + + Hierarchy.Nodes = new(); + Hierarchy.Root.Traverse(n => Hierarchy.Nodes.Add(n)); + + FlipYZAxis(Metadata, Hierarchy); + + Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; + Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; + + return (Metadata, Hierarchy); + } + + private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) + { + var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); + + Guard.IsNotNull(potreeData, nameof(potreeData)); + + return potreeData; + } + + private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) + { + int bytesPerNode = 22; + int numNodes = (int)(size / bytesPerNode); + + var nodes = new List(numNodes) + { + root + }; + + for (int i = 0; i < numNodes; i++) + { + var currentNode = nodes[i]; + if (currentNode == null) + currentNode = new PotreeNode(); + + ulong offsetNode = (ulong)offset + (ulong)(i * bytesPerNode); + + var nodeType = data[offsetNode + 0]; + int childMask = BitConverter.ToInt32(data, (int)offsetNode + 1); + var numPoints = BitConverter.ToUInt32(data, (int)offsetNode + 2); + var byteOffset = BitConverter.ToInt64(data, (int)offsetNode + 6); + var byteSize = BitConverter.ToInt64(data, (int)offsetNode + 14); + + currentNode.NodeType = (NodeType)nodeType; + currentNode.NumPoints = numPoints; + currentNode.ByteOffset = byteOffset; + currentNode.ByteSize = byteSize; + + if (currentNode.NodeType == NodeType.PROXY) + { + LoadHierarchyRecursive(ref currentNode, ref data, byteOffset, byteSize); + } + else + { + for (int childIndex = 0; childIndex < 8; childIndex++) + { + bool childExists = (1 << childIndex & childMask) != 0; + + if (!childExists) + { + continue; + } + + string childName = currentNode.Name + childIndex.ToString(); + + PotreeNode child = new() + { + Aabb = ChildAABB(currentNode.Aabb, childIndex), + Name = childName + }; + currentNode.Children[childIndex] = child; + child.Parent = currentNode; + + nodes.Add(child); + } + } + } + + static AABBd ChildAABB(AABBd aabb, int index) + { + + double3 min = aabb.min; + double3 max = aabb.max; + + double3 size = max - min; + + if ((index & 0b0001) > 0) + { + min.z += size.z / 2; + } + else + { + max.z -= size.z / 2; + } + + if ((index & 0b0010) > 0) + { + min.y += size.y / 2; + } + else + { + max.y -= size.y / 2; + } + + if ((index & 0b0100) > 0) + { + min.x += size.x / 2; + } + else + { + max.x -= size.x / 2; + } + + return new AABBd(min, max); + } + } + + private void FlipYZAxis(PotreeMetadata potreeMetadata, PotreeHierarchy potreeHierarchy) + { + for (int i = 0; i < potreeHierarchy.Nodes.Count; i++) + { + var node = potreeHierarchy.Nodes[i]; + node.Aabb = new AABBd(Potree2Consts.YZflip * (node.Aabb.min - potreeMetadata.Offset), Potree2Consts.YZflip * (node.Aabb.max - potreeMetadata.Offset)); + } + potreeMetadata.OffsetList = new List(3) { potreeMetadata.Offset.x, potreeMetadata.Offset.z, potreeMetadata.Offset.y }; + potreeMetadata.ScaleList = new List(3) { potreeMetadata.Scale.x, potreeMetadata.Scale.z, potreeMetadata.Scale.y }; + } + + private static void CalculateAttributeOffsets(ref PotreeMetadata potreeMetadata) + { + var attributeOffset = 0; + + for (int i = 0; i < potreeMetadata.AttributesList.Count; i++) + { + potreeMetadata.AttributesList[i].AttributeOffset = attributeOffset; + + attributeOffset += potreeMetadata.AttributesList[i].Size; + } + } + + private static Dictionary GetAttributesDict(List attributes) + { + return attributes.ToDictionary(x => x.Name, x => x); + } + + #endregion LoadHierarchy + + /// /// Disposes of the MemoryMapped objects to free the file. /// @@ -217,7 +405,6 @@ protected virtual void Dispose(bool disposing) if (disposing) { // TODO: dispose managed state (managed objects) - _octreeMappedFile?.Dispose(); _octreeViewAccessor?.Dispose(); } diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index ca00e9be8..37486bccb 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -29,8 +29,8 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } long octantCount = 0; long pointsCount = 0; - using (Stream readStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + using (Stream readStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) { BinaryReader binaryReader = new BinaryReader(readStream); BinaryWriter binaryWriter = new BinaryWriter(writeStream); @@ -216,8 +216,8 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } long octantCount = 0; long pointsCount = 0; - using (Stream readStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + using (Stream readStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) { BinaryReader binaryReader = new BinaryReader(readStream); BinaryWriter binaryWriter = new BinaryWriter(writeStream); @@ -272,7 +272,7 @@ public void WriteRawPoints(OctantId oid, PotreePoint[] points) throw new Exception(); } - using (Stream writeStream = File.Open(OctreeFilePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) { BinaryWriter binaryWriter = new BinaryWriter(writeStream); diff --git a/src/PointCloud/Potree/V2/Potree2WriterBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs index 45ee7332e..b1358188e 100644 --- a/src/PointCloud/Potree/V2/Potree2WriterBase.cs +++ b/src/PointCloud/Potree/V2/Potree2WriterBase.cs @@ -14,7 +14,7 @@ namespace Fusee.PointCloud.Potree.V2 public abstract class Potree2WriterBase : IDisposable { /// - /// The + /// The /// public PotreeData? PotreeData { @@ -68,8 +68,6 @@ private set ///
protected int offsetColor = -1; - private string? _octreeFilePath; - private MemoryMappedFile? _octreeMappedFile; private MemoryMappedViewAccessor? _octreeViewAccessor; private bool disposedValue; @@ -81,36 +79,14 @@ protected MemoryMappedViewAccessor OctreeMappedViewAccessor get { Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); - Guard.IsNotNull(_octreeMappedFile, nameof(_octreeMappedFile)); return _octreeViewAccessor; - } - } - - /// - /// The path to the octree.bin file - /// On assign the and the is being created. - /// - protected string OctreeFilePath - { set { - Guard.IsNotNullOrEmpty(value, nameof(value)); - Guard.IsTrue(File.Exists(value)); - - _octreeFilePath = value; - - _octreeMappedFile?.Dispose(); _octreeViewAccessor?.Dispose(); - _octreeMappedFile = MemoryMappedFile.CreateFromFile(_octreeFilePath, FileMode.Open); - _octreeViewAccessor = _octreeMappedFile.CreateViewAccessor(); - } - get - { - Guard.IsNotNullOrEmpty(_octreeFilePath); - return _octreeFilePath; + _octreeViewAccessor = value; } } @@ -120,6 +96,8 @@ protected string OctreeFilePath public Potree2WriterBase(PotreeData potreeData) { PotreeData = potreeData; + + OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); } /// @@ -206,7 +184,6 @@ protected virtual void Dispose(bool disposing) if (disposing) { // TODO: dispose managed state (managed objects) - _octreeMappedFile?.Dispose(); _octreeViewAccessor?.Dispose(); } diff --git a/src/PointCloud/Potree/V2/PotreeData.cs b/src/PointCloud/Potree/V2/PotreeData.cs deleted file mode 100644 index 16b3aa9f6..000000000 --- a/src/PointCloud/Potree/V2/PotreeData.cs +++ /dev/null @@ -1,209 +0,0 @@ -using CommunityToolkit.Diagnostics; -using Fusee.Math.Core; -using Fusee.PointCloud.Potree.V2.Data; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Fusee.PointCloud.Potree.V2 -{ - /// - /// Contains information about the Potree file's meta data and hierarchy/octree. - /// - public class PotreeData - { - /// - /// The hierarchy as linear list of s. - /// - public PotreeHierarchy Hierarchy; - - /// - /// The meta data of the file. - /// - public PotreeMetadata Metadata; - - public PotreeData(string folderPath) - { - Guard.IsNotNullOrWhiteSpace(folderPath, nameof(folderPath)); - - LoadHierarchy(folderPath); - } - - private void LoadHierarchy(string folderPath) - { - var metadataFilePath = Path.Combine(folderPath, Potree2Consts.MetadataFileName); - var hierarchyFilePath = Path.Combine(folderPath, Potree2Consts.HierarchyFileName); - - Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); - Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); - - Metadata = LoadPotreeMetadata(metadataFilePath); - Hierarchy = new() - { - Root = new() - { - Name = "r", - } - }; - - Metadata.Attributes = GetAttributesDict(Metadata.AttributesList); - - Metadata.FolderPath = folderPath; - - CalculateAttributeOffsets(ref Metadata); - - Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); - - var data = File.ReadAllBytes(hierarchyFilePath); - - Guard.IsNotNull(data, nameof(data)); - - LoadHierarchyRecursive(ref Hierarchy.Root, ref data, 0, Metadata.Hierarchy.FirstChunkSize); - - Hierarchy.Nodes = new(); - Hierarchy.Root.Traverse(n => Hierarchy.Nodes.Add(n)); - - FlipYZAxis(); - - Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; - Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; - } - - private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) - { - var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); - - Guard.IsNotNull(potreeData, nameof(potreeData)); - - return potreeData; - } - - private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) - { - int bytesPerNode = 22; - int numNodes = (int)(size / bytesPerNode); - - var nodes = new List(numNodes) - { - root - }; - - for (int i = 0; i < numNodes; i++) - { - var currentNode = nodes[i]; - if (currentNode == null) - currentNode = new PotreeNode(); - - ulong offsetNode = (ulong)offset + (ulong)(i * bytesPerNode); - - var nodeType = data[offsetNode + 0]; - int childMask = BitConverter.ToInt32(data, (int)offsetNode + 1); - var numPoints = BitConverter.ToUInt32(data, (int)offsetNode + 2); - var byteOffset = BitConverter.ToInt64(data, (int)offsetNode + 6); - var byteSize = BitConverter.ToInt64(data, (int)offsetNode + 14); - - currentNode.NodeType = (NodeType)nodeType; - currentNode.NumPoints = numPoints; - currentNode.ByteOffset = byteOffset; - currentNode.ByteSize = byteSize; - - if (currentNode.NodeType == NodeType.PROXY) - { - LoadHierarchyRecursive(ref currentNode, ref data, byteOffset, byteSize); - } - else - { - for (int childIndex = 0; childIndex < 8; childIndex++) - { - bool childExists = (1 << childIndex & childMask) != 0; - - if (!childExists) - { - continue; - } - - string childName = currentNode.Name + childIndex.ToString(); - - PotreeNode child = new() - { - Aabb = ChildAABB(currentNode.Aabb, childIndex), - Name = childName - }; - currentNode.Children[childIndex] = child; - child.Parent = currentNode; - - nodes.Add(child); - } - } - } - - static AABBd ChildAABB(AABBd aabb, int index) - { - - double3 min = aabb.min; - double3 max = aabb.max; - - double3 size = max - min; - - if ((index & 0b0001) > 0) - { - min.z += size.z / 2; - } - else - { - max.z -= size.z / 2; - } - - if ((index & 0b0010) > 0) - { - min.y += size.y / 2; - } - else - { - max.y -= size.y / 2; - } - - if ((index & 0b0100) > 0) - { - min.x += size.x / 2; - } - else - { - max.x -= size.x / 2; - } - - return new AABBd(min, max); - } - } - - private void FlipYZAxis() - { - for (int i = 0; i < Hierarchy.Nodes.Count; i++) - { - var node = Hierarchy.Nodes[i]; - node.Aabb = new AABBd(Potree2Consts.YZflip * (node.Aabb.min - Metadata.Offset), Potree2Consts.YZflip * (node.Aabb.max - Metadata.Offset)); - } - Metadata.OffsetList = new List(3) { Metadata.Offset.x, Metadata.Offset.z, Metadata.Offset.y }; - Metadata.ScaleList = new List(3) { Metadata.Scale.x, Metadata.Scale.z, Metadata.Scale.y }; - } - - private static void CalculateAttributeOffsets(ref PotreeMetadata potreeMetadata) - { - var attributeOffset = 0; - - for (int i = 0; i < potreeMetadata.AttributesList.Count; i++) - { - potreeMetadata.AttributesList[i].AttributeOffset = attributeOffset; - - attributeOffset += potreeMetadata.AttributesList[i].Size; - } - } - - private static Dictionary GetAttributesDict(List attributes) - { - return attributes.ToDictionary(x => x.Name, x => x); - } - } -} \ No newline at end of file From 5bbdd96466fb736d4e20a14afd2b5f93bc27dbf8 Mon Sep 17 00:00:00 2001 From: ASPePeX Date: Mon, 13 Mar 2023 08:40:50 +0000 Subject: [PATCH 116/294] Linting --- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index cc3367696..860e0bf30 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -105,7 +105,7 @@ protected MemoryMappedViewAccessor OctreeMappedViewAccessor /// Meta and octree data of the potree file. public PotreeData ReadNewFile(string path) { - (var Metadata, var Hierarchy)= LoadHierarchy(path); + (var Metadata, var Hierarchy) = LoadHierarchy(path); PotreeData = new PotreeData(Hierarchy, Metadata); From a749201a13b4a7ecbebef92ed6ed47847e1ca2a4 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 13 Mar 2023 09:47:15 +0100 Subject: [PATCH 117/294] Updated _potreeReader.ReadNewFile() to new signature. --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index bdf3fddad..45f147e52 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -6,6 +6,7 @@ using Fusee.PointCloud.Common; using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2; +using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; using System.IO; @@ -63,7 +64,7 @@ public void OnLoadNewFile(object sender, EventArgs e) if (path == null || path == string.Empty) return; - _potreeReader.ReadNewFile(path, out _potreeData); + _potreeData = _potreeReader.ReadNewFile(path); _pointCloud = (PointCloudComponent)_potreeReader.GetPointCloudComponent(RenderMode.DynamicMesh); _pointCloud.PointCloudImp.MinProjSizeModifier = PointRenderingParams.Instance.ProjectedSizeModifier; @@ -83,7 +84,7 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { _potreeReader = new Potree2Reader(); - _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile), out _potreeData); + var _potreedata = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); _rc = rc; } From 99bd770c19c50b36428c11a0a9bff4d88029c80f Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 13 Mar 2023 10:12:16 +0100 Subject: [PATCH 118/294] Cleanup / fixed some nullable warnings --- Examples/Complete/Picking/Desktop/Main.cs | 2 - .../Core/PointCloudPotree2.cs | 1 - .../Core/PointCloudPotree2Core.cs | 2 - src/Base/Imp/Desktop/FileAssetProvider.cs | 1 - src/Engine/Core/GpuMesh.cs | 11 ++-- src/Engine/Core/IPickerModule.cs | 3 -- src/Engine/Core/Scene/InstanceData.cs | 20 +++---- src/Engine/Core/SceneRayCaster.cs | 4 +- src/Engine/GUI/SceneInteractionHandler.cs | 3 +- .../Imp/Graphics/Desktop/RenderContextImp.cs | 1 - .../Core/Fusee.PointCloud.Core.csproj | 1 + src/PointCloud/Core/MeshMaker.cs | 54 +++---------------- src/PointCloud/Core/PointCloudDataHandler.cs | 13 ++--- src/PointCloud/Core/PointCloudOctant.cs | 2 +- .../Core/Scene/PointCloudComponent.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 5 -- src/PointCloud/Core/VisibilityTester.cs | 4 +- src/PointCloud/Core/VisualizationPoint.cs | 13 ++++- src/PointCloud/Potree/Potree2LAS.cs | 1 - src/PointCloud/Potree/V2/Potree2Reader.cs | 21 ++++---- src/PointCloud/Potree/V2/Potree2Writer.cs | 1 - src/Serialization/V2/FusPickComponent.cs | 3 -- 22 files changed, 57 insertions(+), 111 deletions(-) diff --git a/Examples/Complete/Picking/Desktop/Main.cs b/Examples/Complete/Picking/Desktop/Main.cs index a562cfcdc..3cea3f408 100644 --- a/Examples/Complete/Picking/Desktop/Main.cs +++ b/Examples/Complete/Picking/Desktop/Main.cs @@ -4,8 +4,6 @@ using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Serialization; -using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs index 84e559de3..d807da96f 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs @@ -1,6 +1,5 @@ using Fusee.Engine.Common; using Fusee.Engine.Core; -using Fusee.PointCloud.Common; namespace Fusee.Examples.PointCloudPotree2.Core { diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index a37ff7cf9..1cf14d45b 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -1,5 +1,4 @@ using Fusee.Base.Common; -using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; @@ -9,7 +8,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; namespace Fusee.Examples.PointCloudPotree2.Core { diff --git a/src/Base/Imp/Desktop/FileAssetProvider.cs b/src/Base/Imp/Desktop/FileAssetProvider.cs index 503bb03c7..46fb05a32 100644 --- a/src/Base/Imp/Desktop/FileAssetProvider.cs +++ b/src/Base/Imp/Desktop/FileAssetProvider.cs @@ -1,6 +1,5 @@ using Fusee.Base.Common; using Fusee.Base.Core; -using SixLabors.ImageSharp.Drawing; using System; using System.Collections.Generic; using System.IO; diff --git a/src/Engine/Core/GpuMesh.cs b/src/Engine/Core/GpuMesh.cs index 533e8b30b..e2b81d7d2 100644 --- a/src/Engine/Core/GpuMesh.cs +++ b/src/Engine/Core/GpuMesh.cs @@ -1,4 +1,4 @@ -using Fusee.Engine.Common; +using Fusee.Engine.Common; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using System; @@ -20,10 +20,11 @@ namespace Fusee.Engine.Core /// The bitangents of the mesh. /// The boneIndices of the mesh. /// The vertiboneWeightsces of the mesh. + /// The flags of the mesh. /// - public delegate GpuMesh CreateGpuMesh(PrimitiveType primitiveType, float3[] vertices, uint[] triangles = null, - float3[] normals = null, uint[] colors = null, uint[] colors1 = null, uint[] colors2 = null, float2[] uvs = null, - float4[] tangents = null, float3[] bitangents = null, float4[] boneIndices = null, float4[] boneWeights = null, uint[] flags = null); + public delegate GpuMesh CreateGpuMesh(PrimitiveType primitiveType, float3[] vertices, uint[]? triangles = null, + float3[]? normals = null, uint[]? colors = null, uint[]? colors1 = null, uint[]? colors2 = null, float2[]? uvs = null, + float4[]? tangents = null, float3[]? bitangents = null, float4[]? boneIndices = null, float4[]? boneWeights = null, uint[]? flags = null); /// /// This type of mesh doesn't create a copy of the mesh data in the RAM. @@ -38,7 +39,7 @@ public class GpuMesh : SceneComponent, IManagedMesh /// /// MeshChanged event notifies observing MeshManager the Mesh's disposal. /// - public event EventHandler DisposeData; + public event EventHandler? DisposeData; /// /// SessionUniqueIdentifier is used to verify a Mesh's uniqueness in the current session. diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs index abc595e0c..9f452f683 100644 --- a/src/Engine/Core/IPickerModule.cs +++ b/src/Engine/Core/IPickerModule.cs @@ -1,7 +1,4 @@ using Fusee.Engine.Common; -using System; -using System.Collections.Generic; -using System.Text; using static Fusee.Engine.Core.ScenePicker; namespace Fusee.Engine.Core diff --git a/src/Engine/Core/Scene/InstanceData.cs b/src/Engine/Core/Scene/InstanceData.cs index a3231c7e1..30dff6ed7 100644 --- a/src/Engine/Core/Scene/InstanceData.cs +++ b/src/Engine/Core/Scene/InstanceData.cs @@ -38,50 +38,50 @@ public float3[] Positions /// /// The rotation of each instance. This array needs to be as long as . /// - public float3[] Rotations + public float3[]? Rotations { get => _rotations; set { - if (Amount != value.Length) + if (Amount != value?.Length) throw new ArgumentOutOfRangeException(); _rotations = value; DataChanged?.Invoke(this, new InstanceDataChangedEventArgs(this, InstanceDataChangedEnum.Transform)); } } - private float3[] _rotations; + private float3[]? _rotations; /// /// The scale of each instance. This array needs to be as long as . /// - public float3[] Scales + public float3[]? Scales { get => _scales; set { - if (Amount != value.Length) + if (Amount != value?.Length) throw new ArgumentOutOfRangeException(); _scales = value; DataChanged?.Invoke(this, new InstanceDataChangedEventArgs(this, InstanceDataChangedEnum.Transform)); } } - private float3[] _scales; + private float3[]? _scales; /// /// The color of each instance. This array needs to be as long as . /// - public float4[] Colors + public float4[]? Colors { get => _colors; set { - if (Amount != value.Length) + if (Amount != value?.Length) throw new ArgumentOutOfRangeException(); _colors = value; DataChanged?.Invoke(this, new InstanceDataChangedEventArgs(this, InstanceDataChangedEnum.Colors)); } } - private float4[] _colors; + private float4[]? _colors; /// /// The amount of instances that will be rendered. @@ -102,7 +102,7 @@ public float4[] Colors /// The scale of each instance. /// The color of each instance. /// - public InstanceData(int amount, float3[] positions, float3[] rotations = null, float3[] scales = null, float4[] colors = null) + public InstanceData(int amount, float3[] positions, float3[]? rotations = null, float3[]? scales = null, float4[]? colors = null) { Amount = amount; if (Amount != positions.Length) diff --git a/src/Engine/Core/SceneRayCaster.cs b/src/Engine/Core/SceneRayCaster.cs index 5e916b4c1..75daa5c59 100644 --- a/src/Engine/Core/SceneRayCaster.cs +++ b/src/Engine/Core/SceneRayCaster.cs @@ -1,10 +1,8 @@ -using Fusee.Base.Core; -using Fusee.Engine.Common; +using Fusee.Engine.Common; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.Xene; using System.Collections.Generic; -using System.Linq; namespace Fusee.Engine.Core { diff --git a/src/Engine/GUI/SceneInteractionHandler.cs b/src/Engine/GUI/SceneInteractionHandler.cs index ec7da26e4..c6ad9599e 100644 --- a/src/Engine/GUI/SceneInteractionHandler.cs +++ b/src/Engine/GUI/SceneInteractionHandler.cs @@ -1,5 +1,4 @@ -using Fusee.Engine.Common; -using Fusee.Engine.Core; +using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.Xene; diff --git a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs index e766783c5..1d6e3c1e2 100644 --- a/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs +++ b/src/Engine/Imp/Graphics/Desktop/RenderContextImp.cs @@ -6,7 +6,6 @@ using Fusee.Engine.Imp.SharedAll; using Fusee.Math.Core; using OpenTK.Graphics.OpenGL; -using SixLabors.ImageSharp.Formats; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj index 8bb29b19d..d43a732d0 100644 --- a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj +++ b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj @@ -3,6 +3,7 @@ netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml + enable diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index f651c9f6b..eedf5264c 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -6,7 +6,6 @@ using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System.Collections.Generic; -using System.Runtime.InteropServices; namespace Fusee.PointCloud.Core { @@ -20,6 +19,7 @@ public static class MeshMaker /// /// The generic point cloud points. /// The method that defines how to create a GpuMesh from the point cloud points. + /// The octant identifier. /// public static IEnumerable CreateMeshes(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { @@ -63,6 +63,7 @@ public static IEnumerable CreateMeshes(MemoryOwnerCan be of type or . The latter is used when rendering instanced. /// The generic point cloud points. /// The method that defines how to create a InstanceData from the point cloud points. + /// The octant identifier. /// public static IEnumerable CreateInstanceData(MemoryOwner points, CreateGpuData createGpuDataHandler, OctantId octantId) { @@ -77,7 +78,7 @@ public static IEnumerable CreateInstanceData(MemoryOwner /// The lists of "raw" points. /// The id of the octant. - public static GpuMesh CreateMeshVisualizationPoint(MemoryOwner points, OctantId octantId) + public static GpuMesh CreateStaticMesh(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; @@ -102,6 +103,7 @@ public static GpuMesh CreateMeshVisualizationPoint(MemoryOwner /// The lists of "raw" points. /// The id of the octant. - public static Mesh CreateDynamicMeshVisualizationPoint(MemoryOwner points, OctantId octantId) + public static Mesh CreateDynamicMesh(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; @@ -144,11 +146,11 @@ public static Mesh CreateDynamicMeshVisualizationPoint(MemoryOwner - /// Returns meshes for point clouds of type . + /// Returns meshes for point clouds of type . /// /// The lists of "raw" points. /// The id of the octant. - public static InstanceData CreateInstanceDataVisualizationPoint(MemoryOwner points, OctantId octantId) + public static InstanceData CreateInstanceData(MemoryOwner points, OctantId octantId) { int numberOfPointsInMesh; numberOfPointsInMesh = points.Length; @@ -193,48 +195,6 @@ private static uint ColorToUInt(int r, int g, int b, int a) return (uint)((b << 16) | (g << 8) | (r << 0) | (a << 24)); } - /// - /// Converts a color, saved as an uint, to float4. - /// - /// The color. - private static float4 UintToColor(uint col) - { - float4 c = new(); - c.b = (byte)((col) & 0xFF); - c.g = (byte)((col >> 8) & 0xFF); - c.r = (byte)((col >> 16) & 0xFF); - c.a = (byte)((col >> 24) & 0xFF); - - return c; - } - - /// - /// Converts a color, saved as an uint, to float3. - /// - /// The color. - private static uint ColorToUint(float3 col) - { - uint packedR = (uint)(col.r * 255); - uint packedG = (uint)(col.g * 255) << 8; - uint packedB = (uint)(col.b * 255) << 16; - - return packedR + packedG + packedB; - } - - /// - /// Converts a color, saved as float4, to uint. - /// - /// The color. - private static uint ColorToUint(float4 col) - { - uint packedR = (uint)(col.r * 255); - uint packedG = (uint)(col.g * 255) << 8; - uint packedB = (uint)(col.b * 255) << 16; - uint packedA = (uint)(col.a * 255) << 24; - - return packedR + packedG + packedB + packedA; - } - #endregion } } \ No newline at end of file diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 6bc79a077..f43af883d 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -1,4 +1,3 @@ -using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance.Buffers; using Fusee.Base.Core; using Fusee.Engine.Core; @@ -7,7 +6,6 @@ using Fusee.PointCloud.Potree.V2.Data; using Microsoft.Extensions.Caching.Memory; using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -52,14 +50,14 @@ namespace Fusee.PointCloud.Core /// /// /// The point cloud points as generic array. + /// /// public delegate TGpuData CreateGpuData(MemoryOwner points, OctantId octantId); /// /// Manages the caching and loading of point and mesh data. /// - /// - /// + /// Generic for the point/mesh type. public class PointCloudDataHandler : PointCloudDataHandlerBase, IDisposable where TGpuData : IDisposable { /// @@ -122,7 +120,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin /// /// The unique id of an octant. /// - public override IEnumerable GetGpuData(OctantId octantId) + public override IEnumerable? GetGpuData(OctantId octantId) { if (_gpuDataCache.TryGetValue(octantId, out var gpuData)) return gpuData; @@ -206,10 +204,11 @@ public override void TriggerPointLoading(OctantId guid) } } - private void OnItemEvictedFromCache(object guid, object meshes, EvictionReason reason, object state) + private void OnItemEvictedFromCache(object guid, object? meshes, EvictionReason reason, object? state) { lock (LockDisposeQueue) { + if (meshes == null) return; DisposeQueue.Add((OctantId)guid, (IEnumerable)meshes); } } @@ -239,7 +238,6 @@ protected override void Dispose(bool disposing) // Call the appropriate methods to clean up // unmanaged resources here. _gpuDataCache.Dispose(); - _gpuDataCache = null; lock (LockDisposeQueue) { @@ -253,7 +251,6 @@ protected override void Dispose(bool disposing) } _pointCache.Dispose(); - _pointCache = null; LoadingQueue.Clear(); // Note disposing has been done. diff --git a/src/PointCloud/Core/PointCloudOctant.cs b/src/PointCloud/Core/PointCloudOctant.cs index 786b9f3d2..ae02218bc 100644 --- a/src/PointCloud/Core/PointCloudOctant.cs +++ b/src/PointCloud/Core/PointCloudOctant.cs @@ -96,7 +96,7 @@ public double Size /// The size (in all three dimensions) of this octant. /// /// The octants child octants. - public PointCloudOctant(double3 center, double size, OctantId octId, PointCloudOctant[] children = null) + public PointCloudOctant(double3 center, double size, OctantId octId, PointCloudOctant[]? children = null) { Center = center; Size = size; diff --git a/src/PointCloud/Core/Scene/PointCloudComponent.cs b/src/PointCloud/Core/Scene/PointCloudComponent.cs index a1bd6cd33..92cdd8241 100644 --- a/src/PointCloud/Core/Scene/PointCloudComponent.cs +++ b/src/PointCloud/Core/Scene/PointCloudComponent.cs @@ -32,7 +32,7 @@ public class PointCloudComponent : SceneComponent, IPointCloud /// /// Reference to the Camera whose properties are used to control the visibility of point cloud chunks (octants). /// - public Camera Camera; + public Camera? Camera; /// /// Instantiates the . diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 2b9bb83fc..b3568a4f4 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -1,16 +1,11 @@ using Fusee.Base.Core; -using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Core; using Fusee.Xene; -using Microsoft.Extensions.Options; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using static Fusee.Engine.Core.ScenePicker; namespace Fusee.PointCloud.Core.Scene diff --git a/src/PointCloud/Core/VisibilityTester.cs b/src/PointCloud/Core/VisibilityTester.cs index e7998a4e5..51c66120d 100644 --- a/src/PointCloud/Core/VisibilityTester.cs +++ b/src/PointCloud/Core/VisibilityTester.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Diagnostics; using Fusee.Engine.Core; using Fusee.Math.Core; using Fusee.PointCloud.Common; @@ -49,7 +50,7 @@ public float3 CamPos /// /// Current camera frustum - set by the SceneRenderer if a PointCloud Component is visited. /// - public FrustumF RenderFrustum { get; set; } + public FrustumF? RenderFrustum { get; set; } /// /// The octree structure of the point cloud. @@ -178,6 +179,7 @@ private void DetermineVisibilityForNode(PointCloudOctant node) //If node does not intersect the viewing frustum or is smaller than the minimal projected size: //Return -> will not be added to _visibleNodesOrderedByProjectionSize -> traversal of this branch stops. + Guard.IsNotNull(RenderFrustum); if (!node.InsideOrIntersectingFrustum(RenderFrustum, translation, scale) || node.ProjectedScreenSize < _minScreenProjectedSize) { node.IsVisible = false; diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index 48375dea0..700fd3c5f 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -4,12 +4,23 @@ namespace Fusee.PointCloud.Potree.V2.Data { /// /// This point is used for visualization purposes. - /// It is read by and converted to mesh data. + /// It is read from a file and converted to mesh data. /// public struct VisualizationPoint { + /// + /// The position of a point. + /// public float3 Position; + + /// + /// The color (r,g,b,a) of a point. + /// public float4 Color; + + /// + /// Flags have to be interpreted manually or they will be ignored. + /// public uint Flags; } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index e6c112f5f..bf78a5b63 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,5 +1,4 @@ using Fusee.PointCloud.Common; -using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; using System.Runtime.InteropServices; diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index ce6e76283..b5081f5ce 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -32,7 +32,7 @@ public class Potree2Reader : Potree2ReaderBase, IPointReader /// Generate a new instance of . /// /// - public Potree2Reader(int offsetToExtraBytes = 0) : base() => (OffsetToExtraBytes) = (offsetToExtraBytes); + public Potree2Reader(int offsetToExtraBytes = 0) : base() => OffsetToExtraBytes = offsetToExtraBytes; /// @@ -46,21 +46,21 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta default: case RenderMode.StaticMesh: { - var dataHandler = new PointCloudDataHandler(MeshMaker.CreateMeshVisualizationPoint, + var dataHandler = new PointCloudDataHandler(MeshMaker.CreateStaticMesh, LoadVisualizationPointData); var imp = new Potree2Cloud(dataHandler, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.Instanced: { - var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceDataVisualizationPoint, + var dataHandlerInstanced = new PointCloudDataHandler(MeshMaker.CreateInstanceData, LoadVisualizationPointData, true); var imp = new Potree2CloudInstanced(dataHandlerInstanced, GetOctree()); return new PointCloudComponent(imp, renderMode); } case RenderMode.DynamicMesh: { - var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMeshVisualizationPoint, + var dataHandlerDynamic = new PointCloudDataHandler(MeshMaker.CreateDynamicMesh, LoadVisualizationPointData); var imp = new Potree2CloudDynamic(dataHandlerDynamic, GetOctree()); return new PointCloudComponent(imp, renderMode); @@ -74,20 +74,17 @@ public IPointCloud GetPointCloudComponent(RenderMode renderMode = RenderMode.Sta /// public IPointCloudOctree GetOctree() { + Guard.IsNotNull(PotreeData); + Guard.IsNotNull(PotreeData.Metadata); int pointSize = 0; - if (PotreeData.Metadata != null) + foreach (var metaAttributeItem in PotreeData.Metadata.AttributesList) { - foreach (var metaAttributeItem in PotreeData.Metadata.AttributesList) - { - pointSize += metaAttributeItem.Size; - } - - PotreeData.Metadata.PointSize = pointSize; + pointSize += metaAttributeItem.Size; } - Guard.IsNotNull(PotreeData.Metadata); + PotreeData.Metadata.PointSize = pointSize; var center = PotreeData.Hierarchy.Root.Aabb.Center; var size = PotreeData.Hierarchy.Root.Aabb.Size.y; diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 3d37fae30..67cd45872 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -1,5 +1,4 @@ using Fusee.Math.Core; -using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; using System; using System.IO; diff --git a/src/Serialization/V2/FusPickComponent.cs b/src/Serialization/V2/FusPickComponent.cs index 7984baf1c..ba220bd65 100644 --- a/src/Serialization/V2/FusPickComponent.cs +++ b/src/Serialization/V2/FusPickComponent.cs @@ -1,7 +1,4 @@ using ProtoBuf; -using System; -using System.Collections.Generic; -using System.Text; namespace Fusee.Serialization.V2 { From 5fa94a9795dce2fe76c36cdf3f99eb252b799ce8 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 13 Mar 2023 10:48:05 +0100 Subject: [PATCH 119/294] Fixed merge errors --- src/PointCloud/Potree/V2/Potree2Writer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 718b4d13c..56b449c9a 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -271,9 +271,9 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } // throw new Exception(); // } - using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - { - BinaryWriter binaryWriter = new BinaryWriter(writeStream); + // using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + // { + // BinaryWriter binaryWriter = new BinaryWriter(writeStream); // for (int i = 0; i < points.Length; i++) // { From 642f674b66ff3659d8bee387a29d0b8f4cd3dacd Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 13 Mar 2023 11:05:36 +0100 Subject: [PATCH 120/294] Fixed nullref warning & missing ref in comment --- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index 06d3492ab..96b266eed 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -12,7 +12,7 @@ namespace Fusee.PointCloud.Potree.V2 { /// - /// This is the base class for reading and writing s. + /// This is the base class for reading and writing s. /// public abstract class Potree2ReaderBase : IDisposable { @@ -202,7 +202,7 @@ protected void CacheMetadata(bool force = false) /// /// /// - public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) + public static PotreeNode? FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) { return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); } From d1b798223a2c5de9d073f2770f94cc5c5bb3e76b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 09:53:59 +0100 Subject: [PATCH 121/294] Fixed merge conflicts --- src/PointCloud/Core/Scene/PointCloudPickerModule.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 90124be5b..0e5960cdb 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -4,9 +4,13 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.Xene; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; using static Fusee.Engine.Core.ScenePicker; namespace Fusee.PointCloud.Core.Scene From e6d106e4958f722bb55a548180a53e4175ff3116 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 10:27:12 +0100 Subject: [PATCH 122/294] Removed visitor map alltogether, implemented picker example for point clouds in point cloud rendering core --- .../Core/PointCloudPotree2Core.cs | 49 ++++++++++++++++++- src/Xene/Visitor.cs | 14 ++---- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 082a805f1..a9a88664a 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; namespace Fusee.Examples.PointCloudPotree2.Core { @@ -26,7 +28,7 @@ public bool ClosingRequested } private bool _closingRequested; - public RenderMode PointRenderMode = RenderMode.Instanced; + public RenderMode PointRenderMode = RenderMode.DynamicMesh; public string AssetsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); private static float _angleHorz, _angleVert, _angleVelHorz, _angleVelVert; @@ -52,6 +54,9 @@ public bool ClosingRequested private Potree2Reader _potreeReader; private PotreeData _potreeData; + private ScenePicker _picker; + private ScenePicker _picker2; + private readonly RenderContext _rc; private Transform _debugTransform = new(); @@ -71,12 +76,21 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloud.Camera = _cam; _pointCloudNode.Components[3] = _pointCloud; + + // re-generate picker and octree + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, + (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, + (float)_potreeData.Metadata.Spacing) + }); + } public PointCloudPotree2Core(RenderContext rc) { _potreeReader = new Potree2Reader(); - var _potreedata = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeData = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); _rc = rc; } @@ -168,6 +182,17 @@ public void Init() _sceneRenderer.VisitorModules.Add(new PointCloudRenderModule(_sceneRenderer.GetType() == typeof(SceneRendererForward))); _pointCloud.Camera = _cam; + + _picker2 = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); + + // re-generate picker and octree + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, + (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, + (float)_potreeData.Metadata.Spacing) + }); + } // RenderAFrame is called once a frame @@ -238,6 +263,26 @@ public void Update(bool allowInput) _angleVelVert = 0; _camTransform.FpsView(_angleHorz, _angleVert, Input.Keyboard.WSAxis, Input.Keyboard.ADAxis, Time.DeltaTimeUpdate * 20); + + + if (!_keys && Input.Mouse.RightButton) + { + _debugTransform.Translation = float3.Zero; + _debugTransform.Scale = float3.One * 0.05f; + + var width = _rc.ViewportWidth; + var height = _rc.ViewportHeight; + var result = _picker?.Pick(Input.Mouse.Position, width, height).ToList(); + if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) + { + _debugTransform.Translation = (float3)ppr.Mesh.Vertices[ppr.VertIdx]; + //_debugTransform.Scale = new float3((float)ppr.Octant.Size); + + //ppr.Mesh.Colors0[ppr.VertIdx] = (uint)ColorUint.Red; + } + + } + } private void OnThresholdChanged(int newValue) diff --git a/src/Xene/Visitor.cs b/src/Xene/Visitor.cs index f58483d9f..d0bfeba63 100644 --- a/src/Xene/Visitor.cs +++ b/src/Xene/Visitor.cs @@ -111,7 +111,8 @@ internal class VisitorSet // The static list of all known sets of visitor methods // This is kept to avoid building the visitor map again and again different instances of the same visitor. - private /*static*/ Dictionary _visitorMap; + // Removed this functionality as we now have the possibility to "side load" additional visitors even with the same VisitorType + // private static Dictionary _visitorMap; #endregion #region Public Traversal Methods @@ -420,19 +421,12 @@ private void ScanForVisitors() if (_visitors != null) return; - if (_visitorMap == null) - _visitorMap = new Dictionary(); - - var myType = GetType(); - if (_visitorMap.TryGetValue(myType, out _visitors)) - return; - _visitors = new VisitorSet(); - AddVisitMethods(); foreach (var module in VisitorModules) { AddVisitMethodsFromModule(module.GetType()); } + AddVisitMethods(); } /// @@ -461,7 +455,6 @@ private void AddVisitMethodsFromModule(Type moduleType) _visitors.ModuleNodes.Add(paramType, VisitorCallerFactory.MakeNodeVistorForModule(methodInfo)); } } - _visitorMap.Add(moduleType, _visitors); } /// @@ -491,7 +484,6 @@ private void AddVisitMethods() _visitors.Nodes.Add(paramType, VisitorCallerFactory.MakeNodeVistor(methodInfo)); } } - _visitorMap.Add(type, _visitors); } private static bool IsVisitor(MethodInfo methodInfo) From 9d66b1756ca73f558fa21f957f61ab702092599f Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 14 Mar 2023 11:03:46 +0100 Subject: [PATCH 123/294] Potree2Reader/PotreeMetadata: extra byte handling --- .../Potree/V2/Data/PotreeMetadata.cs | 4 ++ src/PointCloud/Potree/V2/Potree2Reader.cs | 47 +++++++------------ src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 10 +++- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index 99f762a57..c97fa8f1b 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -47,6 +47,9 @@ public class PotreeSettingsAttribute [JsonIgnore] public int AttributeOffset { get; set; } + + [JsonIgnore] + public bool IsExtraByte { get; set; } = false; } public class PotreeMetadata @@ -55,6 +58,7 @@ public class PotreeMetadata public string Name { get; set; } public string Description { get; set; } public int Points { get; set; } + public int OffsetToExtraBytes { get; set; } = -1; public string Projection { get; set; } public PotreeSettingsHierarchy Hierarchy { get; set; } diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index b5081f5ce..9f845dd60 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -13,27 +13,22 @@ namespace Fusee.PointCloud.Potree.V2 { + /// + /// Delegate for a method that knows how to parse a slice of a point's extra bytes to a valid uint. + /// + /// + /// + public delegate uint HandleExtraBytes(Span bytes); + /// /// Reads Potree V2 files and is able to create a point cloud scene component, that can be rendered. /// public class Potree2Reader : Potree2ReaderBase, IPointReader { - /// - /// Specify the byte offset for one point until the extra byte data is reached - /// - public readonly int OffsetToExtraBytes; - /// /// Pass method how to handle the extra bytes, resulting uint will be passed into . /// - public Func? HandleExtraBytes { get; set; } - - /// - /// Generate a new instance of . - /// - /// - public Potree2Reader(int offsetToExtraBytes = 0) : base() => OffsetToExtraBytes = offsetToExtraBytes; - + public HandleExtraBytes? HandleExtraBytes { get; set; } /// /// Returns a renderable point cloud component. @@ -77,15 +72,6 @@ public IPointCloudOctree GetOctree() Guard.IsNotNull(PotreeData); Guard.IsNotNull(PotreeData.Metadata); - int pointSize = 0; - - foreach (var metaAttributeItem in PotreeData.Metadata.AttributesList) - { - pointSize += metaAttributeItem.Size; - } - - PotreeData.Metadata.PointSize = pointSize; - var center = PotreeData.Hierarchy.Root.Aabb.Center; var size = PotreeData.Hierarchy.Root.Aabb.Size.y; var maxLvl = PotreeData.Metadata.Hierarchy.Depth; @@ -116,8 +102,8 @@ public MemoryOwner LoadVisualizationPointData(OctantId id) private MemoryOwner LoadVisualizationPoint(PotreeNode node) { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); - if (HandleExtraBytes != null) - Guard.IsGreaterThan(OffsetToExtraBytes, 0); + //if (HandleExtraBytes != null) + // Guard.IsGreaterThan(OffsetToExtraBytes, 0); Guard.IsNotNull(PotreeData); var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; @@ -155,13 +141,16 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var colorSpan = MemoryMarshal.Cast(color.ToArray()); - var extraByteSize = PotreeData.Metadata.PointSize - OffsetToExtraBytes; - var extraBytesSpan = pointArray.AsSpan().Slice(i + OffsetToExtraBytes, extraByteSize); - uint flags = 0; - if (HandleExtraBytes != null) + if (PotreeData.Metadata.OffsetToExtraBytes != -1 && PotreeData.Metadata.OffsetToExtraBytes != 0) { - flags = HandleExtraBytes(extraBytesSpan.ToArray()); + var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; + var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); + + if (HandleExtraBytes != null) + { + flags = HandleExtraBytes(extraBytesSpan); + } } var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index 96b266eed..a804c0ee4 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -104,6 +104,15 @@ public PotreeData ReadNewFile(string path) PotreeData = new PotreeData(Hierarchy, Metadata); + foreach (var item in PotreeData.Metadata.Attributes.Values) + { + PotreeData.Metadata.PointSize += item.Size; + if (PotreeData.Metadata.OffsetToExtraBytes > -1 && PotreeData.Metadata.PointSize > PotreeData.Metadata.OffsetToExtraBytes) + item.IsExtraByte = true; + else + item.IsExtraByte = false; + } + CacheMetadata(true); OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); @@ -254,7 +263,6 @@ protected void CacheMetadata(bool force = false) private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) { var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); - Guard.IsNotNull(potreeData, nameof(potreeData)); return potreeData; From 5ba279b8bdd4a96849c61951a103e07882353011 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 11:31:24 +0100 Subject: [PATCH 124/294] Fixed picking for ImGui --- .../Core/PointCloudPotree2.cs | 4 +- .../Core/PointCloudPotree2Core.cs | 88 +++++++++++-------- .../ImGui/PointCloudRenderingControl.cs | 11 +++ src/Xene/Visitor.cs | 3 + 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs index d807da96f..c14c9a59c 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2.cs @@ -57,13 +57,13 @@ public override void Update() return; } - _pointRenderingCore.Update(true); + _pointRenderingCore?.Update(true); } // Is called when the window was resized public override void Resize(ResizeEventArgs e) { - _pointRenderingCore.Resize(e.Width, e.Height); + _pointRenderingCore?.Resize(e.Width, e.Height); } public override void DeInit() diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index a9a88664a..eb422dd7e 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -9,6 +9,7 @@ using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -21,6 +22,16 @@ public class PointCloudPotree2Core public WritableTexture RenderTexture { get; private set; } + /// + /// This value is used, when we use this class as a + /// + public float2 ExternalMousePosition { get; set; } + + /// + /// This value is used, when we use this class as a + /// + public int2 ExternalCanvasSize { get; set; } + public bool ClosingRequested { get { return _closingRequested; } @@ -55,11 +66,11 @@ public bool ClosingRequested private PotreeData _potreeData; private ScenePicker _picker; - private ScenePicker _picker2; + private readonly Transform _pickResultTransform = new(); private readonly RenderContext _rc; - private Transform _debugTransform = new(); + public void OnLoadNewFile(object sender, EventArgs e) { var path = PointRenderingParams.Instance.PathToOocFile; @@ -77,14 +88,16 @@ public void OnLoadNewFile(object sender, EventArgs e) _pointCloudNode.Components[3] = _pointCloud; - // re-generate picker and octree - _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + // re-generate scene picker + if (PointRenderMode == RenderMode.DynamicMesh) { - new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, - (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, - (float)_potreeData.Metadata.Spacing) - }); - + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, + (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, + (float)_potreeData.Metadata.Spacing) + }); + } } public PointCloudPotree2Core(RenderContext rc) @@ -165,15 +178,6 @@ public void Init() Children = new List() { _camNode, - new SceneNode - { - Components = new List() - { - _debugTransform, - MakeEffect.FromDiffuse(new float4(1,0,0,1)), - new Sphere(10, 10) - } - }, _pointCloudNode } }; @@ -183,16 +187,32 @@ public void Init() _pointCloud.Camera = _cam; - _picker2 = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults); - - // re-generate picker and octree - _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + // Generate picker, pass camera prepass results and the picker module + // which needs the point cloud, the octree and the spacing + // does not work for instanced and static point cloud meshes + if (PointRenderMode == RenderMode.DynamicMesh) { - new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, - (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, - (float)_potreeData.Metadata.Spacing) - }); + _picker = new ScenePicker(_scene, _sceneRenderer.PrePassVisitor.CameraPrepassResults, Engine.Common.Cull.None, new List() + { + new PointCloudPickerModule(((PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp).VisibilityTester.Octree, + (PointCloud.Potree.Potree2CloudDynamic)_pointCloud.PointCloudImp, + (float)_potreeData.Metadata.Spacing) + }); + // Add sphere to visual identify the pick result + _scene.Children.Add(new SceneNode + { + Components = new List() + { + _pickResultTransform, + MakeEffect.FromDiffuse(new float4(1,0,0,1)), + new Sphere(10, 10) + } + }); + + _pickResultTransform.Translation = _pointCloud.PointCloudImp.Center; + _pickResultTransform.Scale = float3.One * 0.05f; + } } // RenderAFrame is called once a frame @@ -265,20 +285,14 @@ public void Update(bool allowInput) _camTransform.FpsView(_angleHorz, _angleVert, Input.Keyboard.WSAxis, Input.Keyboard.ADAxis, Time.DeltaTimeUpdate * 20); - if (!_keys && Input.Mouse.RightButton) + if (!_keys && Input.Mouse.RightButton && PointRenderMode == RenderMode.DynamicMesh) { - _debugTransform.Translation = float3.Zero; - _debugTransform.Scale = float3.One * 0.05f; - - var width = _rc.ViewportWidth; - var height = _rc.ViewportHeight; - var result = _picker?.Pick(Input.Mouse.Position, width, height).ToList(); + var size = RenderToTexture ? ExternalCanvasSize : new int2(_rc.ViewportWidth, _rc.ViewportHeight); + var mousePos = RenderToTexture ? ExternalMousePosition : Input.Mouse.Position; + var result = _picker?.Pick(mousePos, size.x, size.y).ToList(); if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) { - _debugTransform.Translation = (float3)ppr.Mesh.Vertices[ppr.VertIdx]; - //_debugTransform.Scale = new float3((float)ppr.Octant.Size); - - //ppr.Mesh.Colors0[ppr.VertIdx] = (uint)ColorUint.Red; + _pickResultTransform.Translation = ppr.Mesh.Vertices[ppr.VertIdx]; } } diff --git a/Examples/Complete/PointCloudPotree2/ImGui/PointCloudRenderingControl.cs b/Examples/Complete/PointCloudPotree2/ImGui/PointCloudRenderingControl.cs index 70c384dcc..349708941 100644 --- a/Examples/Complete/PointCloudPotree2/ImGui/PointCloudRenderingControl.cs +++ b/Examples/Complete/PointCloudPotree2/ImGui/PointCloudRenderingControl.cs @@ -2,7 +2,9 @@ using Fusee.Engine.Core; using Fusee.Examples.PointCloudPotree2.Core; using Fusee.ImGuiImp.Desktop.Templates; +using Fusee.Math.Core; using Fusee.PointCloud.Common; +using ImGuiNET; using System; namespace Fusee.Examples.PointCloudPotree2.Gui @@ -46,6 +48,13 @@ public override void Init() protected override ITextureHandle RenderAFrame() { _pointRenderingCore.RenderAFrame(); + + // set mouse position with offset to canvas + var iScreenPos = new float2(ImGui.GetCursorScreenPos().X, ImGui.GetCursorScreenPos().Y); + var fbScale = ImGui.GetIO().DisplayFramebufferScale; + var scaledInput = new float2(Input.Mouse.Position.x / fbScale.X, Input.Mouse.Position.y / fbScale.Y); + _pointRenderingCore.ExternalMousePosition = scaledInput - iScreenPos; + return _pointRenderingCore.RenderTexture?.TextureHandle; } @@ -62,6 +71,8 @@ public override void Update(bool allowInput) // Is called when the window was resized protected override void Resize(int width, int height) { + // set size from extern + _pointRenderingCore.ExternalCanvasSize = new Math.Core.int2(width, height); _pointRenderingCore.Resize(width, height); } diff --git a/src/Xene/Visitor.cs b/src/Xene/Visitor.cs index d0bfeba63..4460eea2a 100644 --- a/src/Xene/Visitor.cs +++ b/src/Xene/Visitor.cs @@ -411,6 +411,9 @@ protected bool EnumMoveNextNoComponent() #endregion + /// + /// The list of visitor modules defined during generation. + /// public List VisitorModules = new(); /// From e0940442063d4566a3ac2a10d1773e155f6bb63e Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 11:52:09 +0100 Subject: [PATCH 125/294] Housekeeping --- .../Core/Scene/PointCloudPickerModule.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 0e5960cdb..2ce3da824 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -1,3 +1,4 @@ +using CommunityToolkit.Diagnostics; using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; @@ -7,7 +8,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -23,7 +23,7 @@ public class PointCloudPickResult : PickResult /// /// The point mesh. /// - public Mesh Mesh; + public Mesh? Mesh; /// /// The index of the hit vertex, in this case the point index. /// @@ -39,15 +39,17 @@ public class PointCloudPickResult : PickResult /// public class PointCloudPickerModule : IPickerModule { - private PickerState _state; - private readonly PointCloudOctree _octree; - private readonly IPointCloudImp _pcImp; + private PickerState? _state; + private readonly PointCloudOctree? _octree; + private readonly IPointCloudImp? _pcImp; private readonly float _pointSpacing; /// /// The pick result after picking. /// - public PickResult PickResult { get; set; } +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + public PickResult? PickResult { get; set; } +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). internal struct MinPickValue { @@ -66,6 +68,10 @@ public void RenderPointCloud(PointCloudComponent pointCloud) { if (!pointCloud.Active) return; + Guard.IsNotNull(_pcImp); + Guard.IsNotNull(_octree); + Guard.IsNotNull(_state); + var proj = _state.CurrentCameraResult.Camera.GetProjectionMat(_state.ScreenSize.x, _state.ScreenSize.y, out _); var view = _state.CurrentCameraResult.View; var rayD = new RayD(new double2(_state.PickPosClip.x, _state.PickPosClip.y), (double4x4)view, (double4x4)proj); @@ -78,7 +84,6 @@ public void RenderPointCloud(PointCloudComponent pointCloud) var currentRes = new ConcurrentBag(); Parallel.ForEach(_pcImp.GpuDataToRender, (mesh) => - //foreach(var mesh in _pcImp.GpuDataToRender) { foreach (var box in allHitBoxes) { @@ -89,6 +94,8 @@ public void RenderPointCloud(PointCloudComponent pointCloud) Distance = float2.One * float.MaxValue }; + Guard.IsNotNull(mesh.Vertices); + for (var i = 0; i < mesh.Vertices.Length; i++) { var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], _pointSpacing); @@ -122,6 +129,8 @@ public void RenderPointCloud(PointCloudComponent pointCloud) } } + Guard.IsNotNull(minElement.Mesh.Vertices); + var mvp = proj * view * _state.Model; PickResult = new PointCloudPickResult { @@ -157,15 +166,6 @@ float2 SphereRayIntersection(float3 ro, float3 rd, float3 ce, float ra) h = MathF.Sqrt(h); if (float.IsNaN(h) || float.IsInfinity(h)) return new float2(-1.0f); return new float2(-b - h, -b + h); - - //var oc = ro - ce; - //float b = float3.Dot(oc, rd); - //var qc = oc - b * rd; - //float h = ra * ra - float3.Dot(qc, qc); - //if (h < 0.0) new float2(-1.0f); // no intersection - //h = MathF.Sqrt(h); - //if (float.IsNaN(h)) return new float2(-1.0f); - //return new float2(-b - h, -b + h); } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 882aba2a499c59e66a18c4a17b576803cbd5c738 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 12:03:52 +0100 Subject: [PATCH 126/294] Updated references for RenderTests (after new ScenePicker) --- .../Render/Desktop/References/AdvancedUI.png | Bin 56421 -> 50221 bytes .../Render/Desktop/References/Camera.png | Bin 39900 -> 39856 bytes .../Render/Desktop/References/Deferred.png | Bin 498052 -> 497174 bytes .../Render/Desktop/References/Fractal.png | Bin 84431 -> 84404 bytes .../Render/Desktop/References/Labyrinth.png | Bin 268170 -> 268186 bytes .../Render/Desktop/References/Materials.png | Bin 74502 -> 73491 bytes .../Render/Desktop/References/Picking.png | Bin 13026 -> 12998 bytes .../Desktop/References/PointCloudPotree2.png | Bin 2151 -> 2616 bytes .../Render/Desktop/References/Simple.png | Bin 24345 -> 24308 bytes .../Render/Desktop/References/ThreeDFont.png | Bin 23558 -> 23568 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Tests/Render/Desktop/References/AdvancedUI.png b/src/Tests/Render/Desktop/References/AdvancedUI.png index bc1b67c02f28b1ee68d9c3647a8fa1c123d30db3..fcffcc60172dd0ef63fd768789dab10187553776 100644 GIT binary patch literal 50221 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 O#C6qMDuqhMf&U8*SR}{* literal 56421 zcmagF1yq&Mw=cTsF6k}>1nKS+3=om-knZkoq(nrzOQfZ{ySux)yWYb8Iq#l3-hJ;e zhVF0d@x|I}%{AvQ<`?q$lN1^<5i$e~oBQ?-LY z(A%H?LB%kj6GI^L`0ph|6`YgylMyr&9iF7OAKlks6ahn{IJ{=YMFA6I4OD*WwzNb6E+q$6@YvwcG`M#?M zS5ge42Tvmx;nX85(zdsuc%1CM6o|eQx43~}&!54b2!xR5Z?=QJLs8tm85kI_6%-V7 zl|u5RaD`L|7a<2^2r)7;)|QkEBgV=|OB40?_Y+7<=H%oUvdlCueS(LFHw^ne>V7a= zk&%#)K&A1!xVSh5Mn*;kSz~f)YRd1~>jG~ef zSF;AZUES`kDfhIYu8B!JuU>m_fo@wM94i=;*w^12kTgZkt_aeP%uGz^mdsG`(n|1R zN||}s&^ABSRaJ3YcnN&H`9yGjPt9;pn3{8Ob2EcyR^&-8I&-ChuFRwOda5c(A{JPC`Ngucoe! z(b2bZx4ynEpoyMU1C`0vk>n6RS8urcryl$0x|!CvpAUKkr2mvp?3riBx}C0mezq3RZ;P$A;Q z$6O3tUS3wg$B#YvVG;lO_3QX%<7xz`g&u^6Dj&CqXxrqxJd?*}9~k+C`FZKy-d@wy z)m5<>83@&B`aTWJ(Yn5@?2sNdHa5cZBkhU7BN-UX2&jLG_%ri++?Myv*VrYVpP!>r z5HZ5Bev%B<)N-;}J zo!~I^0m5#G5d8qLGdx1|s*6ME^Ky^x{*h6q{Y@MUr(7}{iiMMt5et8hpD&T!%u32o z&!9Un+>Fq>hd8mBg^t5fEU2-e`$fiSK$G^-FVG7;9&nSbPn6Edb z2DMa=ZEcWK%j$P*S?j*(uT<5Jzf3WI!?M_v)U#)C3S5l%>dab%P1n+ts;;QFHlLqV@65<9 zuC+X#lk3tPi>I>qT?nIjMWZZ1CYdLKDZM%uQ^0l;j-fsTq&yD)+85q<54Y?;in;x< zi7a|hRXrM#E|6V4#CFw|*#|=gsy%r4-QU!7oqcHd+)q@L$4xlUVA1Y+Vl%E#Zmxp} zpZ4wFiiv9?d51dla;|vE(N)51Gs2{qJXlS7>j~Yq{v^vgQ-0g}p0nf0V+h}b#H8vS z3#8&X;|>$hqj^U~MP-+k+L(BFG_VMHcq&zVrZv+^!+>x89us?Y_})>uk~440a}D0{ zX8Jtqg`=JoQ9gaK5gRtlra)g}hE?&8gIVn^fLQf0BCp~BK zSL2SYoqWockne%;HJA^?yL)AW#}Acd1r+&z+wQdUAGCMGv{CFC3k>>ONBtdQJW@NW zwr7-@$vH1DgjFH6W<6v=1t80aaGb>?2tR;z^gT*Np5MYoq``| zRhs-`#sB4}{6M28W8i>GcThN*4fbS*RK>(?liBjNpQa~Xfm2xma!{0QYLG5=(#`^!(PB(UutG?e-e&3pne&wXE+GDVb zy$kb}y5WYbx~@)&mxo7M7i=;A&TVTX?-XH> zs{3NsijIXj1{0Sp#mr!H|4@P##pRnv5@`lMu6bqY4683C9-(nK zP^!lEsYKVmCrGL0pPo+qJBNpEG{!n2Xc09 zarf1==`a`u=F6<7Ua~!Tq-8>X!W;`H&;H5PQHmlH1_8_R{`t;WS4-;Pzo_96T!e3) z9uJO>tJ1Vz{7kD%dUDB%GFqVXhBxnfIMG2XhfjKx5V4>^8q0=(LdkF8G1b%f)`(aI z`!D{i!s3~gL3Y19pVHtK=-}Bbwn$l6tuE-lWC{ZSK$e9KpJpcf_)!bd*eSL6>4>AR zalF<_w``Th%p3XD<*8#l=S&u76M=e>XzJ z&2K&0rpLWd4Fo%`3H{-kEP?(y+)1b`1dKLq5-MyD)p<15TbpzW}AxM7bXV!!X5iW-`-O*3(GEe zHy(|nYjyb29y@`!fQrpZjhueS?4mWFPnm19m*|uidK}u=}vq zk^}OxG9=^pKFSyfn*El4cqPO^5Eyt{kd>zNnnxG_s#{mxkhVgqJGeXVA<29WzHG^J~dYQGR%>1&SH*u7d}MpmK2kRuMtRrI$xk53$-6 z!L^4|21}Dy2hXNpe>@=}VM%DuO|lTaNmXa!6w30Zeo|ZGhG(<>{qxUJp8R#o}`xOD`LTkm>QQEWb&K$ zpahi`M~z&RLYp591h^<8aQ*OPPGcTU#AUZGzBoHQ{rew~74eDKJLP^w>LiuRLQO+K zVf@`2=uQWt*1xK&Hv-e$uHo`QY!jA=p1H9s%=CGN;o(!EX9r3B)AzaeswTRO9|g7B zKNKmkOW}Xyx`NWyb@LFXuKS_YKFS`ds!Z4Wpv~D`f?Zsa%(4{!?W18B+JOYWU%K`E zs!IXCK%DCuMi(oK!#z)zHjSgaP8<8mUDNFuO=#BwRVE^1!?egYE z1z9aYcym_#;nTy3##b_HWm z&yS6cCN%paieGOSDU?McuucD*bE||UpuSxalKni_b}*Qo8JC_8W%h+m#Z&jlzAscb zzKduV_aCX@cuz|NbxEd!IUj^Z>`2AQsi=xJepfE4bro(^)V|fdM zxx^*%hAkBdh9f~)+%M_i;bn>@pL&)fImxw=|I&rK-5G<5?AUmgPLn4|*PlM(xFQ`R z{Xz6FuMct9u<35j0ZqM#2kAo>(97NWYtMy(eZ zcZSh+89b!4s*0S`p!er?cXzjqMp&sQSkuX?wo>hgeOnA7f;wdO!q-q2h2n#Ev-<>S%M5>opx69lW&nUo(m?S}I+hH&<|mM|w2wd`|1_P^7m6vbvVIp`qPh zw_0sgJPPY49HpeXc)EuQ0g=Q)^|tjmy%D<{uh;fpv9>YyjEuT8Kekri6qx>@I+I|3 zb6aA%wi|=&h%_1*THpJpPYp%*GQs<9F?WFIISFb0#>It!@xxoI6yi8jr->#kS-m%% z93DPdF*kdaIv!xpFIaQ{_!Dh^67LyX97IV@j#aw8@MZBYknfQ1?u1|Wj|_+ra4!wc zgvBQivwFY?>iReSIW;TMM(cZ@ucMf48kA{qd3hbO7s`u6r#va_?(QAa4l&eoQu zVmdD#Y3+2)-qtIyWN>;M^OVDYD$tOMli=CBSFYxc4AO5mSTH=qsL{;(RqzOgt zlHR|rN(tI+$bbI?U?5l69I)zqFlExH#_V;4D1S|yuMjsh3@;jxjRe+d?4zs{tjKffj%PV^vU!Med@bV6!Am2o zZX3a%)vXbwEXD})ENlGWIq(l9t3|58u$Ye1UCqzWkKW$8^2_LF<>uykZ{O12^Zw2^ zAs`@#yS}{*bai#z^g{}+vEBIKU9FZ@mPSd)swcsh#Z8#;sN{z#;%TUZ>MI)D_xJDL zjIy$_0b~>uj}CEMGor6w*IqC)7du;79gQ4U4Wo^Mr7_zcPRFut7^{uGgnZ4fKMpa| zTpm)QO0g?^Q&(P^!32E z3Fbksy_i%lZtv{O!Nxv|Yv}(l1#}lSR zNBgx3`~`ctyy2_OOEEy}Lk|xR+2SH$zK#}A`WVmSQJql49Wyv^eplA8v9XbP|32iE z#FnLlgM;Ry8Ws`t8y{Q^MmNHg#KiV;i9Wy9shGiEC0du zmek*58z9Tl6>?)U`Pjxj6g?Whp2Q1?q@<*hMjR!*6&*9PEVL+7VPRnmHwVYIuHW0> z)kM+ei{@bhb#-ee*D6~3Gd_nBJx z`DUigKcb@wVA9?b5SE6-6Lp)e=G4>wtWJw>w$EE)22t-gn*N)ty$cc$d7(ROJUufL z?w>bx22ZVLVrIrqLNhs4P5B6IrNK*3$$n}Lj#QrW|mG|}C zrG(J;BAA>eqy7)t-9zYSg7Q(k2_~#k5hT2^ad9d&t{1y_*{|*{4?>wnr2Ypjs@anQS$ zR`6`>>~;hF{nANENhAcK_LyuCK7X&t(ex+GMBR`7&~h ztvNic>-oqlzUDJy!vP|~%4%*T3{d7PDjdiJUCU#gA40Hc{{&%>rwHjZdn;wW-2{JD zi}k!0UF2Tp#RUcD%X?B`1U4HJ%Gv{7%RUG>_FG9HMTLI+@PXdL-96CpJPst+zhFdE zJVs7m)7Wnmm6fN!YV&r@&ADMBep7Y3GyAA@;A6qEIh1Mzy~U23Vwawo`L&}+v#ws~ zcANUl8{Y+L^&H9o2{1T%|?N?=gq6M^mJVTZ7UNK)dtIj+O-l! z3E9&f`RQWai9d?DO}D%GCU#qcf(=$nLTnI;AWE1t@%l{%W!Qe#EU0*<uxUS;LLH zAKKcgGm|a_vI}!t#D5jRU$It$n(DmQ>3WYS$PlMeWG*j1)Gv)>N;RxFRk_UkHE{Nt z@xu@@X7#wWfBO0J=lS>Vn4z_`d{04SuG=IMrhzI!+5pm?FS;FttJFK6-~>OmE_c<9 zDN-$Y-dU{g6*nFJNJ5UQik8@XsflMK*Sq;I6FH-5q`X0%Odx@ZlUJnoPbf9 z@s*quZ+F2%{f0VN_*NZraT_yZynN2G&SGBM>jAgUVNVH$MYPej#+`@k?zjyjsLer| z1*-g2agq1P8DrJ_Dy%B^{!-IZ|Ncx_SKFE6AE-*JB~SOY&hQqeC6B8c@gVdSy5w8I zsL7+ICr{U&AFqUfx=qb(K8*=?huYfGnvs$5SI6a;p}779@?1pI^=fE(wQxi~d?$w| zkiR>vy=d3&dJr?Bze5dE9@?K12c3o0{y>d$F=n$Uy_Wi^8KD_}eE_>76jvOS*MN|M zKmf^+p~FFL_!!IN0t*M%5i-?wR4J;YG})|LX4vr|(gm!xT?>nIU_bz_xB}6e#Y(-7 zZ{M1mo6nWCJsf&Rvp=GM8Z^DKl9B3pyHoOXza{LcBeA;VycV7TB;r7FultevHVk1@ zcy?Pt8e$tcD-DhL?AP)+)u7sG1x4U_CYZ3Bx^tBlC?mh>YoR5ET`DRn8jndn zx_fxQT)Wyq0`To`uWv8*#D$;k&yFu$jqN!Z-Q3*9dV4>7be<|gC*q*{SxnBR-wZI>HcMFQs@aq;e-FCfo^`Sw|E<+xX_?VC>gvTKw++DK|}3TWTEw=Xd-)$(j<`Z>(?)|)ur7;lIum81dg44 z9~cDaKl%9#EwHL9EL*AWr|+*b-iF>678F41a`RFLe-eWiLAViOrin9w#5d}Ctd58G z)=sWv$LcA)^kf%Zc#LBPF&OUsZmR`j2V(Nz1t{G{iEbO)id6MPP~@M1cx~6a$%-q_jUt)?9%;ewgf)8* zviho@_EXaIiW~vpcFBmV4#Hqh5s@cvvmeF-v3 zDzvCl{P)PnNK8e)*Ll!#lYi>@kP{e_?(Xg|%*=$REuFtUxjVp%M4Q-@q_3}bgkrO^ zv(thYYDI}M`{HSNWft5s#>Eh-P&%z0+d&SMex*`jI`Qg+ z&s>+3l=PrMzvo9;@_Upz*v7L#{;21KNtfj!HY=3I_~y-<*?qy4jg1YTC<=)N#zp}} z6nC&uO7E_YxNB`UzFD}q)bIwIK6Xb@n^CCY5e4Vbha6vAD5~>8bs;rjOciOG$<-I2 zxj6j%^M_s>8#~%;v@__7xw(0m<^Iw%vB^r^)kds#it9neRAB6{UlEV{Wkap8Oq#P> z#FH;(;Rqq5zLBk4Mk}tYjeJ#aqv(peQujCvO=aZULeOHp(JpB>Ey@-?;hiQCL(mp&9i~HVWp)O{rSr(rpD&jlN;Gi%16ip0voBdHh z&p~rNACY2LF49o7FgMpgxT9NkO}IK-n9Kehb;Z+mYpB`#F|4Mhh6L50HG!+U>)nDU zW(Z1WJHQJERyj1Itu4PD59bMMY}O=?T3cIR>j=pfYBzaedR``B`3FAN&Ew+J_Z{Rd z7xS=hY~wNK)4m5aI2w3`-kO;tKb4ODBv34Tm^f)){{A87)%M=rp3iq=EEO>^UqMMr zOvPUzSMOzHDyk|f7_ZLH6|`cdY2@dgNHVTy-jywmkDse)X{C&TeKA^kRQu=lz6jdz zEg%$YIGC8<1N(Ro`vEM(rJ%V01_Ju`rXc4h{-gS6nPfgr$IJbhg+X=vv(wAVU*bxx zAt52k_YV(2`EwYHQGvvF`GreImTJ?Am_;|MEq(~OKF3l%NWX)eilrI_MKKYB)UFIgmW^H!CV0`F--SvJt({oGRC z-TQh5Hg0?Jv}`GMlW`!OTPwT9%MQ{ zT(3palDn*9J!NHOC4qzxU!nY_zh`L3s!aGQT|8(|Q9(f(5eX@;*?{GBJOy1_rNtZ` zJXM};R;!_n$^x1_YcZ&gyyE@?k9SK?n|at4{YFQN4SqiPIxi+7;Aa-f$=r`yuCZx9 zT_4OBwv4Seb!$CRn9Uan)9K=l zik51B;9NYJtUhq-&UF?lN2WQl=nm}VvGzTd(ZFuI_Q^tZe#NKAWn&8ze5Rx?s;aNg zFCI4&ty+}}R7TRN=9BrIB3ut^wmyKgsTM<|<8fWG(F1_T<}l%(Sp1oE%|qn}#Ml~Wc23U5oogK+59@<%bBZ~SpJT2?3u z)UzG}s_RvNEMA98Z4wlET`f4t$qJmb!v=55r?_kqX0x>2c3&-dEnNcoN?n(T2Tdbq zwcP9jceXWzp$|Lq5x_21(?lZEepLOgJ`L-5ER!-sR5Dj=b*8Dgbo4LkM(Y-@biY$7j+Y$O$R`4ic&l92^|a zx?SRdu@Jyop)DIAg9Z+CKIB-NQ>)ya9RbqWBV|I7&+$OU&eCLxJwAp$SbMSxw= zk%Ye`TX7*ktokd*mIC(;5@w=z5#HUIyAD^x)=)Vjrf~zlmNV2G#Q)n=TV&-Th~(At zi;IcH=14hM*$GDyO-$Gx;*1%ygXAC(2O9u&l0Zc@phz^V;eml72wy5v z-1~tI@dxYzPt%RwmMMBLUD@VQzi6 zvZBihNS(98T{r=2MhKP!KL{yNidHL@QTFc#rykBEM?}AoIiRW+mTW(_f|SP;BJ6u} z?5<1mRSkA>qHWyN!>yz1n)Mezl}qsId44<_zTP$8s-~d6OTzquG)Fd~m?4lX;sp_J z_R)Egl5Pi)V|0oh&<2aXp=rtD5?0hn=GxCxbXkeJ8yp-|WH;=$)=zE+pwY7}5G|eM zMA7u@ZB)>EY7@$*$EzlO#nZr4+(0l)CkmJ}zp$|BmuuOOXZntcu{6Ai-(M?l6uHBG z|K&K`T~!z=vl`?jyhV?zg*E`uFOC|n7K59cMFK#j@ci)M+dUZb73eJ?x)|g?O7ada z@7w&gEdb)~5(K^<@Omr&mXxZwn~w;6BE74Q6dD^7CcXZ$(=KmiT)@nSFcxrr;Or^= z5guw32h$)z(Cg9ta&sV&9fO32WfibIyl=%e(on&R(T)eRia10>A6A$8<_Tz1DS{or z=rdm`XkXmCylX#E^z#+B2=Cu#`ZPDbja}LkY=S5~^m#}G)2koh6vf~H^In4ivEdji z7U+rYm|lcjkI_B)3%3%CqL>o0;_xCtF5GKJ!!0z9v{p{%$*6i3{u9t+Un>o^G#H56 zYl9O*EG|othE`5|0fq$educZBjlx+MK5NvKzUG3*uOjyk^-x zpQ|tkuOKbLE8*f`3{5)s%`0t89~BTtDnW5098P-$v{*GnLT^uqBOD1?qi7UKxvFgB z&dy4(2at|lNFIdd_sXcH99Mh{s2@~vCHW?#9cjn&NoA=&AyLZT%3P_$kbaW}$SGd{ zIK&nyxM*nOX0d}=+bxryOw?Ty(GZ>;by$z@Vb155HbE|_qxj_PE+gLnS73TpRso(Z zu@s0ZA`H{v=6rq8*zTLR}?YCnPnV>?FGqR!2z4N~=4ncj$E$dVLwYXFlY3@l6 zu(0h>*&zQM7z|1n5#S(VJ^-OaqF8QLrHv+e_Ss|$PQ>^h&0)`9cmkvV42FN*V#93D z9?`61|5{3`2JuibXxLH)cRJ_Mxb8s&_d6#A_ErNtvTy=~4I12@oBa9qR7_~iA<__0 z%=Q5vREUjynkm%tgHQ+|t2ts5|IR~TgS5*8{ec4ybbY=i)#pFQ7|+Lk|2ZamKKB35 zG2ZiW;D3(c*&zN(BLD7_5P%9v=lom&p5?zu0V85es=x?-=ZFUwmKus83K*h2>GSF0 z|4!HR1E*vEJ6&Q6oW70opVL{v>3j(PPA>=3A!d~RJYvKP&@N|B_6HA4hA|o;Bt!c@ z4`qCw0fygd-V}(deEN`JPo(E7N0y$yR)79F_z{&5!ssVe;J=yf_7O87K-vK;AQ&T< zFmdpx=M@$9{~up81SUuE^0Pd?*8PO(f4GACNKai3X6XO1a{v9s@Q@8>WsfGmzTqTK ze#;?K=OoO{ZT;=Hd?SW3wxv%`X4aM-H4M*cust#x5Ia;{x$VKNk zGzW|m&+W>~H{5jV7d|6algd5Cs{i`&_9$V7BMEfOWAKG+3Q_K7KD65v#TdkLO)AhR zZpB2DTbGuxvsKx$my)(uWj-tFf<*9m_O}Q1_qQML|3x7qh(e?wn3Mr~?h3oDK2V&| zP?+64d5h?s*25M3#z}f5)oW=USPVN@s0CpNnKDDV3m69KFZvyw{$2kHTo3g>G-K*2 zjeL2IgqGEJlTgDiK?-aKLa6CljwK8B3mA5voi9z=V66`PQN1JV#P8u2t8H1#jV$#M z?@cCS%7;19mYJQBi7rQnXD9_c*r31^&I0;KD*5J-GIy!QwW5(q0(&PTghwUGn$vvs z48}$1Kk6|GP!xN2nqxoc8U}eGq|B;mjH5X7gVo1@Zj%o~6)-hX+Q0S)C3$@U>BKkCrC6EF_gvnbYr{t6L-a!*7aK zhi{#;b}=)8x8nXgUxjjRB@N?>i}Gf27P$>@1tpOeV~jT`Z;v~ zi7N$+^gsb{M;}nGUOn9md695i&eNfwp$Rs*U0LRe`oWu&>*sKrn3{h185^q(>`R7g zJ~;2a-Q8WagXeKbO#Rn5Se0tw0$QYlzdTS^#hpv0#L0dbZu)uS^$WWwsuJD<4yOoo z6&}eXawX#z1G%-pmu}h(Xl@&SBGhZ`cWylHE@kEc20htI^%80ZN-M5J*-j9-XR!O_ zjFA*j7Bcy0XmVPCo|Rxi$;8CuNa5Jk0^$AZ8C)c+Ep`Q1-PZzAHhIjY>Ob=@;mi&{ zb2km1CUq5dRT+?=Z}feS^aS?Xy>otxXPhulOg^@EbYzRI^9j6ssqgLVp`f6Ee7}g$ z>?*s%+60iyPO8@~Dzs!qUToyHzV zWt3x}v)*VllrjwvOWFRu`I%g%q#~0_Ni!c0kF&JY*l_zh^AZM6^%C6g0=fcGqF+uodRl=uPiF=&sOy9Wkw3IrE*(oopUd_ubJuU>Mm;W zEx0rS3eO8rx9ueO@5S)&FiT2tRe5=VR+Z&~%`{#HLU5$d15vYa(&KJ^an3+8|J6Cz zB_MMy1fqJQpCn9aVzHp;33~sZG;Qjw5n|w%DvFQiyg3whIbElqrl!83jud$DYVl}$ zBy;hYf1%!dhOPc+AF#3VI3bT(;b3Jg1%;nSgUS5V*~063KxoZv9!yae6%`%HFK$dg z?TUb>#Q#9n8AfQg12)(jt!nF)pR&Ra93+oqXwX*X(8p`L-audGCTh1dR#ThRQ8BOp zQn%xf;MGg>>5>+CMNLktb%cy))Kwm&Q-K>Ge5k*qp+O%U7~lk<{iU)TOGg=yW9EB6 zo1;CiR}dn{#}zPC3{YmZG30J$1`@gZI7WV>XKtxm)^s3Qer$VuxEt~oi45vmQ-A?G z_~E)UoY?QE@$T>}2%YqF3Sm5ZEP3k=;Js9>zxpAOK0Yn)s*mKpFHs=g8hqEndxPP* z5u+@oJ7=@_?nORMvvFs!X5Az~N|(O-5`m~OBFKI1$q}Aw)+utzKcLt#WO5kso`V1O zl`>#AB~vUQoL#U~Ts5CTV4Cfr0>si0#I~?-aByg~FcNgX$HZ`2PZp?_j(&|G<#&XG zwvSb}YM3a)g2?eVI2~#EF(?;Jqm%NLqmKSTC}SalovvixdX`4vp$(B}bY>p_{W}Ht zRG%$g`U&JpG(azpvw*J=P*Yp0=O5`XICX8K1CI|EK(oVbSqy^uk1y0Y?E$Fo*7>z7 zLU9rcNQ6s9qCABATbrw^qZwoZl$4^N(Mb^yC4EG_Uyfjlj!@u*P@uwrequc4`r}q- z%u2H*uP2ZGgQ}&)-gkM>nF^RzZ9u}X3;sN`;5cU?e_eTZSQjaHF)nXXvH$~isyhiX zj>J(11(i6UqeChz(9zeGa42{zn3wbM>_%Lzr71>(Nj=A>r>EaHANrV^N3<5f=g0ix*FVbJ=%od@W_2^^p zKE#c9ib@8B(LZf=n__9J1J;a+^uM)ozUlwc$}^QpShQIn(y_t8!QFo3i%_GtUco60 zpuOl9lJIPyQ1H*>WD6jxC?E^Z8emRLK3q&`f5Nz`*gFFDwuUAaxO^2#l_{-9*jkV@70b85zl6;4RW)(0dbkC3EZ9T zo3kxuU&7;^4`kL>wmO?gSmm>qFPzChI9Tmo9?Xf0WR)$?pea>oJEa~Cr;96kI_Hx? zGFMl3cM~vHnK^hmMrm_aMg@GA0oYa0OUqpvCp?)_6xl|jH2vt)T=sT^fdD4aWGu&^ zjF?>Yg}jmyO<#XM38L`&1}5C6pOS?XS>lwljBuq)FgEO=k&#xG`Amd2Ctc*CU-v)8 zGOBaEUl7uHH!s>uRjSRWeFePJ86F;8FyZb*=}1yS1Dju|MX6rqmJ&Aevq3XX7AR#q!>xS(3#>Og9LO6uul9HTaGqy7&aK8-eoS>m2khv)_6LP(v{ zQG~xD48cDW{T55*=ac$MKsV=WVOa~SrFE0U7-3VD{U!4MO@)v8zg74@5k4Y=C8eb! z#=ui|-ThV|`tv}`c_pC4DAj{&y+5A)JvNQBaoH5SL{kdj?eD(#=vxRO0QKL%L*Np} zyF5sI-3vV;aeUIIYTh=~eJDjR5EN3Gpr)>GRX(NLCxJ#4kYJO2kh;T5m>TNq=jXe+ zXua0Eqd1k6l*AqvOw2wjDjFE;>DkK6vwXn?%Pz@%X{AsVNt4Rl_!0QO8+lV&ZWhj< zhi-dF=c!@!6#CtNd@hKfpm0{8QHyX^H%3GCm^cxq~cFCR6g(jvvr7Nda^wfM| z_Z_R`T8_MqH3@uPiZ`tVAK#XH1ONwd6qi9MYjw%CMnjmN->ntY*kj8(e@wg0e?B%Kog~Sf_@Chi{~z$jIkN6HX{;QBqQh&G`VC_X{Rs#htF8r>17| zYl%&?BUA0_f{*>w{PDH?irMHlSqH-@9s7p2Jj#Ov;*~;RtGsy zv?5m@4Y*w$9yoHCTUs(9tZ;R@A>9z<&0$d4q(_j3{hVErlo8M-(Li>?GM}#=sVgb5 zMvm)|-g?Fi9ky_A@U8c>H zFRP4EUlpDSD#yXD6>- zE4U=)YM`wx6VKO5@H*V8Y=p?%YSX3C;S-&T%6)2d^jKbJ427N{@a;LRlT6(13=IsB zHd|+AYLtns^rI{%QKwM}qBhrMYe6dGcRDd@K~Y*8?%_TKWYxcJVbZnUt%@ z1QrdFBocSa)6hEyRf2@)N zB0W8^*^Pv`X+4icH0FcAIv;QQY&VCkMK5#jE~NDOsL_?p{NwE7r|{2RXM)->$q@F! zdje#~g;haFiI*^m>(dB1czTXuF&MB34@GH<;)D^n`+_7^DSz9FCQDkOvd9#DKJA{6$l$KQL~ zEN$SsjSjqIb%Nlg+&><}FYUQg|2K)B9TJBzAJo#UXs(dQRCp+~gh1}j@(fiypBrbO z{KhODG-So~mb{+*8E+l#r}d^EgnPt)3buZ7a}$Ptn=c4E-QvAji;UEPZhx9->JCA4 zV}JOZ8HYbbtoCzqOIF~d4-E-<&u72=Idt+IOj8ya z0m2xt>^V2rB88Py56%{xmRerD{iNC8>JSI?H_5 z#R<6Bwm$z-M?kh*EJ56ldNdEe1!k$uY@$^oA^cYMCAAQM%*Uv*EjWlnm|Zq;{g%)# zuVI4#6o!hbV%u#TGqE2(3b2vSy5pc*@1&;Y;(RGH_p;4^1B&H!l~=6Ot%Pzr`I+Ag z&W?*Q1JCRn@W0`KoQxYXmCEmQ_#$|sdwE%>0@??r&mGumC!i4^&K^M5q#Llp2y4|h z?)>O0b;cu9F1otuT>!0om^Gt}SVr`V6ueZq&Q0|s(l<3Fssa2gYSqUO9mJ|3(0*ZP zWfFecpRT{(ct~Y8`qcd6)ki9){b?*zKHSSrVk_xYv=wJTj=0c}>1>;`&4I^)#ATif z9%0w>-w%Ok#QwLAB(4O)!I`FspcN>*s>%s|p845y(8P^=7rb?~5=0&d6nk~dVPHAn zSx}U8L8GU@TxIKkPMCwsFW$d3;jg-!0SxT`tpM^wk*BPu2-r9m?bx!?$5&UfE}QX2 zYKZJaS^w=dh>15I3M^SQ-bi9xGR6sedFD#_>?6Otlu!q$#lU9`Pv2AL3?WveXe3^?|7u zx2TMTlDEMEePBL-PnC}j_iWhi0TdS&aL!;&m|}oDpBv)Nk0dlg_}qy0HKQ`_>}g-# zLU2g(S9TtW4%|oRcAB*OjzU zN+}g0)p)^kRp#535z@jxmul_?9ikXt( z^d5VKRmhuwh(dL31f4~|%e9}}#DsqVuiA!}1~-IbY*#?`UJ1g&b7Fl*}& zmko>A^4BQ+QfYrAJ#^IG{9@Al)*z@>K77=BmJGI@E4bRwZ41dzj3Bh7t*uROvEBvr z(uAlSave~8#m>%<)swZy0i^^{;u=lT75=YgBSxfIzB+mWWd6{Kmu|&f(J^+IpC?K2+qj=^L5;!t^C}h*wk$_a8Ej?)H$1zrmy5zBSx;bK;5TLcHTwf&(USke76*_)m83}nbz139PCk$ z%vcRT6S8i;axwZZE|PY58{if9n;07tTmad^ahJhwwm-b|w1Vh`+p3``a8};`ZrnY4 ze;Klt4-XeKOB6q&nN*oKLaP^zHHI0NlAhv2@fe4(j^7(h1|uw+7#r(!U$d2hvRGqh zv?~ajbS4G{WPTSnTCa$mfwXlrH$BaB{;YppTwNXEk2F<^`O2Q-f}g;0WOz@?H{A|i zuZw7m7A*)rMlf{kuiQ^EGw@Tvw_z_G5aL?l70_bAz6*WvNlz$j+lI?x7RMD3=~q^B zm4)kdM~!FiWMnP~Sad{IfZ}!v+Gj|s08Enu8sN)E55yO48O*?k>2l$_>FRMq9!%Om zyZBtchkI%UnyZT#%?b{zE4(YxO;p#j@TJ2q&|)@MR{S-m`<8Q14M(+G@XnUp&%&K! z#==9Yd%jO&&%*z|S}X7dA;p6Y)o&j6pm0xXl`cBzZnbX&e;ygFd~>+#Js2pcagn(0 zG=wu#n(6pXaSQYE+P_j9J}niZS9nC)=o3#&pq!fgcM+8sVbvAqialCe$tjKz8%a1T zodEyB)#BM?c~i;nN-DYylD$8Z7`W7DRZ!sG^=ZrN?u`IvpRPikLub%8Y}tpiRg7x# z1b7rlOg?mwkjN>3P+mj1OX@@P3yTjWL~ukK7yy_OO}?GaQZ zxtp7Yy5tUag8)6oCnuAZm6Yg!*DLy`r{f3-2v`Jl!#ei9Qt#~TF+^fNg&*G&5fcZT zf!FnoK<9ZuPEH_by8ul5-A3bReFaivCNuI`7@>Onbz6Y*6L62?fs<{0bWHYG@%bWn z{OTYU&a8}Ud~yL$9mP*H@HJ7=KAjDh856qLlcwN!FZqO2=q?2THD_Sv$A4kyd}F? z!)T;WSFT{<;P^Q+J&h>GkV75a1G};rm8nvWSxSjnye7whPo4C++?h|K{jheA2{exi zm3<%glV!6Eb+utds(a_L*gRNJNg0I0l8Z1&$Qh+dI zzd78(qK&+-ffBhUX}S25@kv7xn}Q3`*;7E1$(K%uPAoyfmF!Ptz8bTj)`F16e)e^} zhv;vv5cl=lpqEFp%3bGH8tge8#j394P4SD))B!Y;BeJqd&dzD4N1}VQEL*V=rC=!K z)m)0%(4$6?L$FJvZ{#QtHJ@#?vU^aYe5=#0&$`Lwj3DiOfa7BNlkeTKQnlMxzYE<9 zkHIQ`-IeW~!CE{U#J*oybM0!;}n&Q?9i z@>czW!@W6dabsaAH0GUQL_7sj@Q0M^!(8+nU=q9|C~!F)sA%z?Dt9}xui&aMcrSnS z)_3SJb0N7$Tw(1^+<4S)MZ&LW-MiOO&ao38$+N55j(dCXix1Aec-+7;?6q6#xU6Rk zs-~pD!wp3i9@b32I}rA` zV0vSBijJLLE(|9409ydXZgE!o4VA^)$NF?~yqip&6Dk*`rfV5_X_ucU~4)=9yMu@FP6+}{P8&ZH$Ot2Xp7xuIL%E1 zHgWFL{2!e^DlWZ$Is~z|`eIL zQ>s^hmw_~@J8QzGGXRN-Pe#3UX<}PbGr`?MYV>VXJGs~5kao=W$G05|3&}2SZVY>O zCqXIr7onN%Ca=BiSC5-IKtZ~zYA+ajBb)CY6)Ad6FT(q zUfR%Ioi2N$PYsb?E*^H2aI@d#LqTb4Dk$Cj;~p6 zL|(QAQ8IHfEXW}iEFtym5$si3_L+3yYi%LgU)sN7!ML&dvJ4+gl6=SI#xAwY56iflIAh!!Py@d0O!f}(WBY@Y>o0n>#+ z2u>sxmqCENE&{=hx0-G{r#;0aUDiicDIBR&8vM3 z%v4QbXR{Xc#k^)_X6Zpxh=kVgO}I-G!rs0F{v$*;=Mdbs7A~80u86;GI)lRA4yE;F zy~&}3yj^@^zrWh|T@bbu6#m_{pEl|2S;Q?%Y^&J% z50QXGh=vixS^fO}*-kq25s8HP6W~@6I~A6T;!uAG?JGGhaGq)BXrWaF9L0NVN?~V~ z-+%sS2q-OLN=y+N>RI=!>J7IVKZ-E=5oN7w+*RSkNJqx+faB+?fA(~YQa;j1TC3zK z$7{0XpO&zkB7c-Cb?TzmeZqv#Lw1LMW7DFNA;%B}QeuAaG*mzhP&iHRL zLfsN0C?mEFKq>}85~+_XFj7L#uC`9Z8V~Fc$~L&J^}VyRhG(U1-s>zkdPf?v*Pfga zO0D?+T2zILzmly!vK?o>4|m|}I-QSVB`@E+Ui8;{7tI=(eDm5z<$bu6zXmiPrZX)p zQyqCEmN&s!|5yW`^4weNsBiQ7T$DIe9CcFeuDo~I#OUg62GO?TB_?&%j zIgQrD-D7(wBeH@PXo&t@V*mF_kRqCTo$eIO=>Bk@`0ZcNs~3}lPryLJg5jlo6$QA< z*niX&CM)7%8G3?7x4c^$TQ(dgB$Q|cyX|YFp*gKYC|7KQQYd`vGSpvk3R6NOi!ck*%3o*?;iT3T8GqXtNNDDAzNvu=-t3~z_wzD#?UD7Fl? zUu5LEp^OMyG*`us2B{nvHBI}L(3$X3Rkf~$&|=lx{{CEI4_2Gl5QRQ{cbf&227mq% zVv)DeX(9ZE;BSR()NB)qPG7o?>{Kl&u#D3%?9BVXq?7`(Y7Vns7=bN(ge+V>u0sqO zkc-e3Ov=9r3P)#*t6}Yvv_#7~#Tjb;M7)b$p9wlbCa`pTaNxr(s?TmuChes5MZGVR zuijS(n`ebVjkPBwfh@fJu_HOIGUoeADCNU*lvzgz&ez)PZTai zEQIMlp|=^dvQ)j_fDu)^SUBm{i6LOr5{+*S?-+vj{Xm4OYBDrL5|Qs$U(|#YFuR^tq9n(#S+l@wJ}Cqw90k`X{|l0BrHyiYr-)67H43U9v2ehngl83A&N(vUt==H|B575k*H=vykYowCFTRK^ehVgqLL zi*up=Uz_6rSMtQO1#9yH%QLP~sqhG1POH1|cjxTK%YR*|f+{40JI(%vI`eJqSkKrr>IC+-&pXevCkjoibi-X$0Sb z>0_)0yDO(W*>kpv=|9B;o7_73U&}No(i)QOh}M4s!wl1n2IqYmA{j$}z$<2R$=|Y= z+l23|&d<{yZMM7HlS+ym6+E!mM^&1N?{setiGR~70wJB}m)dZm?x#)ZtP%A53_Xyx z@eRfbT#)4SP(m0M+MI<}A0sn+Uj?-qifBGMS+E&IohMaX#NQKC*467`Fz74RwZ_b1 zWGL6jNZ+VeRHPVw2-PDKEL&<1^}q~5U33f)e9pT&agyTr(cUM#gqA=uRKLF^^J4p6 z{YS=lD~)>W|{q|Sua>sqn(RibP8|py7lzcRIc*GV<}IR z8#T&RG=jE4o>*p2`><9$Ij0g9m`Wb^c)t|?tU+>0;b0J1St%5&K{n0}pIrx~GSCYE zK%VY`RH$6|#6j~|Tl31fI5RWzVAHEM#ZcHTYUb5d#d@2w*HU?VMxpNM{+OL8RiS#! z*~k57FBskH4MyaHTP_uPkogyOHx*jgt()fpGj#Bu(x4xiQ||CRKN_lWo_CSx=eFlj zoS&{2>XAR@$W7~_fw!OJZaiJK*uU)BMHh5k zC?^{PY84QsDQTr1D8fn#vA#nbqQq$eFBfY35r+#1c2lC}_JFzb{ZpdTW{;S}#O{}h zin_JrXSO6VhQ4=Qn-)9IilLx)w?7X@W!%3n4G^UCrC8)|Zj;N5mrdlz^*vDc*H4A* zVO0emQ?Zz5oM2xROg@-O2%G$8H^||nW%rv-s>}#+eIq!VKw6M!&(%U3UpvWqZ|^iK zcf=zN;ngJHVOM?dV8GVT=D~p2`hIEHS`D~i?Ln=-iie0A?KZV+s^0?Cb_NNypLFt< zlSkEzCwEPyc90lMsk^If@!;EV-+8hKq6028CXwiMV&b=7#cj)ZQ*jh!KbAx2dX}n~ zuSPbVxX}s^0PUMufLPR*GbaQmDp4IZens!@#Zw%fJF+(rvxtM^Pr2|?$q<0r1A-j)^r@*h4N?SG1>T{?40WU*gT|T^7HGZ^ zs>+>_-ELYlbM!{ObKnST^lu5}r%W}pi9lHW#A(>frojepY7FqK$`S)BUJK;1|M&%1 zN#zPi`1l0A+j*~|7Yg3tpe-(-XDZ|##*aD~{7#mtyIFAN)DKHr%RFNgLM(@W+SfIs zzHFho^RY1l82h3^t09TCtCPnfYE;y!WoRre8p3u1yi(3+0lo8pCXbPF#=Y3*^@pcd zH8qCq&BUL3cD+@8JK$?z0r2-O<98-4JwyF+NzACK($4NYqNJecX?+B(wnYPW0gjwJ zJf)66Bu~F^{D1icoR_6^JqM)07{Eh}r>k974LtW-Rph4l=5AFFJHIlM7@L^ft;YMn zq(Tt29H;B~+9yRQg3g^7wDp6uuIz$-c`GmZJ%SDjK&vg$KwFD5zVrQKBZyAH>@U;M zk*pVALmD7QS~K_30GCi*R1`rZ7zsqZydMA+MF8w7y%&zb0Lj#-SLsgbD|vy;j$jN* z0W@C3K*l&NgL8sQUZbJeT6Y~f9}gb)J^T0Xvrd6>rZ;p<6Tl70;)t0$l=GtMvcC~G&D3-K$EQxI3k09fB?+BD_+3=P2fqBY*~SG$s@t@ zp``QO-Ts{J1N!)a40~@EBJpp}00PjIX=SL$!Y)2 zZ`CBxBAqpfkJR}2mY-V06+RpLQ?H}OeQM;VreccM$-lRkKhdJiU%qCFrHY|hbcE9n((YlSd(K?Xjciw5(nYr=(Mu_e- z_;<>wO^5ZtM*w#;wXxC1A^LC?%2mG>=F2%l>rd3{0+DI6fQMiB(aA|L2@CP_-;Z=T zIXLJ#Tl%Wp^#NeNWT(o&8x_zo@xOrx?KuJ3#{AQ71@C~gpXiRS(sDxPTmq443%h>x zFlF!JAQ*I}9|%VRHt{CKW9Q+)dQR$NNhUFEPccKUV~wZShgo-|8IxWlM$n`YTs5;O z0pD5#XKdxYvGE{iZQt`#|W730Cp+Dj~J-((GI6+VJpjj9#D# z9r@1cYPKZw$=?2p%c&5zEsZ_OX`;G(e0)3!uykDZ0~xY84WE@v|M#BCPy&|#QPSbG zv^1f8_kOq^7~uK{KoZ$3*%8|;XX2hjM-?Cb?FJQavxWB}kz z4RA1e#OuAUFZw|RE<#l#s-Q1?2Q{B1HL{0$Zsg@W1tbiBV+K3{=GQX=_-n|?;r5P$ z76+`av9Qnzo#Y$VscikbW=u^vxc+Al<1(3lLY*-b6%}!VWc+Qt=mPQp0yYZ9;iq9B z>HS&23rJg3pHxhjk+|R)^ph>uYikAUCA7VqrvkfN_cn(C?QeMvtKR+0=LjB#7Pm1 zGc@0W@VRdqGAM+FbFPS`^7oHe?OSV}Aw9JxuM4d;yV)lTi@ApC|9%|n;`sBr=aKfb zgoEUp77UCQd<^Kn+tsk7ibI1B1_lE6Dpp%&w4Gm4n559H{pcFGc^Zte^wjD6vA!?enQUldXu?A1FmzZ^0{y&VQ4;I6G6H+aN;!b*C3ukTAEw}!MwKS_QLhv@@xXB;aLsnj%xi5i-k-W>u z`Hr&_15P_t_m+0sPQ_2gP3n9~iZ~7HYiYHL=Ou3Bm_k0^0`6nW3+xHqHsHkzYIlAN4j?KNNO0M}s>k02+=MJB!SHRls5} zYaawP_MO_pUPW*U5G=P-@LM`R+_*IcflF9(#3g%gRM$>le)Be5aAr@9iHQx$3b7>m z`_TLJas?$#qhYIcy#~cz8-1j}$}9n4alw!{8oj14i0d&}Xgc8s}iHzk&JQt%($xsp!@f!}`Jq9}9>?QV)-g_*`ilx+0Gc z4%ql%_?-h|W2fv|`P;~)=WG;fgFiwiPE(_!qYKrEQox#dP?`FYFc_ek6-|FqQw??B ztTsAqMj!TlBZknW5%_BDAuS!Qb`iIfWTd1^w!pCudhE0hPUJlZ?(8Pw_R=s*h+bW` zm{)@UPI_bS&4jIDajmF+PYp86&`$+fiD;HTz5UxUY4+D!BR#(W0fX0_g7hL{-^&dN z*3CJX6uH}JkEK3ydTa{V$C(EO{0<8(j-ecH8f-C`)^?_AXm!iXRkQM(-n@BJ`efF4 zw%*IE5(4?fF2*{0J@$!DGxAqaJb1 zwq7TXmAsQqj6OU(?rk(`0!KoHY0by&-aBw`sI|3q4uAjjRDMA12&{YAg^mc2=)5a= zCt+H|i&1iO2wVjY0KaSzxB5b=i8Kq6+nlBakyYfh_96-SG(jZ* z%9e3R9y85-1VqatxNG0>=2*@ysK&^XzsAOzeoaoEMpqa+2T|_LDNuC$y8xZXlU0j! zl?MXWTEg($X|SMQM-b#C`x30*0Z3-+f}6cfl{~ zZ;EIQ4ZLc;8ibCyyM<5Cff^>nlczD)&k5Yy+q(vPY#r)Lp8>3>G}sj;-DQqG4-N+r1YYFt|8c@kwA7^75FXwSRWG=#ag zB)~&p`>5B?EJ@R`p8hRzhTa_kHlJQ>`lq!O4afTMo>1*l4(!*6+*knr%ElvHj5eNC8-vE-Ee zKK=#|Z?&KdbRNi*q!=Ogo(QlFo!0G|fbzx!NHQD^rus^)|NY7ZOgn7xsF)g%emUwS zQqt0qNR9D_ficgXKc79oNl2?7hO~n~yR@E+9%mh(*7JbR{0kQR$1i}*s*6IAUJT$w zvw|wP?H%;l+$Wo8YBiY%{N$|K{cbTUM3GQp7lb!3$-{) z*dmpI#C{jxNfRT*hA1*2rBqq~9o0l^f;172nK|b-$hmD-Jns|WwWwVJwi%-atk&m1 za1uEA4ripApoSj^6Wlhyva#w1^q3=Pu+RX7L2mOu#poBAi1>Zb7CFg&K z^>uHngMenCU0UCz3UKNUV!;OP7RX~e|Iu3j!5b9zDQDP9-b3mH!yP$ShS1>nB^{T($`J*s-1M-$t;_-T;#N`wbXfd+dUNy`RT46&EaR`;d3G&1Kil zTPHKt^KeZ3Oa^Dxql1?0QGcH(`Hx&#~>DR<$?V>S5` z#Rh@Aco?N%?wDsjRo(Yvu02<8VT7Z;nwBzNrh4Vc8Spt(mGNpm#; z7Pb2Mbte}fMJpx&9>msD%@Tq(^G9M#6i6P#%&!r^X`;ye)F)GcfsxT;&MDv`mKHxc z_bJ#9p?jOzh4a#w_;@Gwc1yete}nuCxvu}QkC|!&A;}*{h|h1SqxfUd-wGC{=ZA3J z1Y_gkK7VIpvrX_eE9!E5)jK+-ayv+Hz&47u3J-hR&jr1sWuy6{M# zazu!qrL4FxG+b2p*E*&b{MZT>GN^-rv8QV*JdY^QTM30YdJDu&3UN_Uk zhE7F=Vb~JgsT~L>t)-7F^0|&*>7o&)f!9Rb2>{%)cz_P?I(;rItn(^W>>6T^+G*F} zUh7t$BZt2q2kJ8lwUkKc{Fh$<`MQ+>$Kkcurx`20mD|GGzrStAYhCPt^IH0yjfk#L zmA?V2O3cc34ci;T-F&jkcvQKdpTFTKR905!tv9w;%ZnC3B`4#&JtP_*rotSrbTDBq zSz`oVzsa9BuOua?VLZ`LGKWK;PfP@&ykWozPn21WDHT=#5b2yw>)%?Q3WuSfpdf!C z5OA5d@j$R9LA=;bcqsa4LG)}frsMkNo87k}7P+NcWz;|aGfA3#iptdspnhr`pbn`2 znAC3u^1U5O0qgNY5S5l1oa|q{d}-N_LfkUJKbrxi)WYiAoJ!ZF{}}b5YtDl@+P>%8 z+!q2;lY4?O*Ehw_tn}p@Hi17Y5oCcAoHr|j7xB4jU{QCMh`MXC6sf@YEVg!@UsvS} ziCtVh`JMf+FbF&0x@*`P*na(iN*{1_-e#v&chLGAt~9GzqN;XQ^W{L1DvG+f*!;EP zwL4W|_fPWX!$%VcyA+jZ?W@)ou7Zc#TXk;o?3j!c)DYt>v{2iJCEe(82$F*n+lV2y z&E&{T77-DVr&xFo=89rSK*ISO`s=jigt4?6K8cXuF5q7>oK*c+MgEr@Zn|1Xu8`*sh=ytm7_3aC%jf%5%3SSmeBjj1(!e?~F9 zX+h-5cm0&_Hz~kL^~0hE+9<+9Rw{#n4nqRJ38;K>3T7&Mwhp6WaPQq~!ki|eT3G&9 zcBotYs>l8E>$$0tg7kZ?d(er<_3=2Zo5ULeLfqLZX7wtZ{(C2ixju2?-n%qpvP}<` ziO5WY{qvIus;lXhL}cLNG)(lWBVW+cFdIk9uV|yt9SMM0=|3^L5<8YJ<&2nHUPq|n zzZCg7tN5<8gN^*yVcQiUS0uh!-6+oH7_ENB;mP}2O6*Rri8+Y>po4D#4#UEF@;IAM zFu0ItARQ<=1dY*>HUt~#JyN}{rLCNbMh-Alq%qp1Vl6+FD^B05@Nq!ZV1@9j)u(-w_xk6V--btK%CM@rF9|po4q9zu0O2!C%H; z@qZKff&HcO8~ES>b0CL|C4*p-LdU7?&FpXc4-9G^iM*uVkf)s@3QkR{FjOsK5pVG} z`sn>@|0@fjrOqshCJX#;?YrBS{!|}A1(p<^{||=vfCKT$AH~WS%YN2dOl;`qHwp>~ zpt7^Xwr?BECx(nbIVup^84?1c#KaR{3?RXYcNGr&;IQY$b;1U!O8S2?EkyUKh?eH4fHt~fVA45qavOMa`-J79*{#+OQ ziv|Bh1Tpb)ASr|&8+h@AeE$5Ir|a^3&um?(40dpz{l0wrmZzYg@L=&n)B5qhjA+FJ)_MYZwFN9kdDDU-Kqi`zq%I zwlfbM!f}5DTY&8G320KJI0D%5&ns6hElizU_82ZNE9kB)US*b*dE#!nb8c+{%_#pU z?#hefaF^CilKG{jtTM}ym!c7FKrZ;xw9VPk(Q%lMn?rBy;o$eKAY4)MS6CIh?&g*B zk+TScj2-e+W}gk20?N{qwexrS1H755a=3AnV@I#n&Ifo~el1?Qg0|Z+4R;+dp?v}x zc^n$3qX-q6qk{dLy@?yY9l@#Ql|N5jPSeitB9vS?vSm^C3v^#~kJLF(jr5%llZxkK zi6DmfXn(NL-3ug|)l*L;H(V2KeERO8eR!zONkcpS*hib`slB3wYZXd5PIAti_WAS5 z?SYP-$fEYzTmu$m*eE~RCPn0sll5?Jl;idUzv*4?0_A-j^Z51m++7x$c1L6PGrdv| z;?zY@dB=dJ9Sbr4-6o~et92ddPuP5Y@3XjpwS8$6&{?Ms5GEEeHs!QYOE%+`IxI;O z;piK@_hYOKEANR}5#=lay{s zLD{qh}W&)g2Qq}Dp{hZp?S|{M0~8@LAF^> zx5RN@Dj`D7wU5QXfwGlthIv%d)3f%%+BN0q6{~nP&5Pi#;?=E`x3(rnNx67OoDBAf zL8%+@s6Lp8-Q?ehm)b$U5P$c?7CZvh7k;;Y!XGPk7XRU~-A6)^w|Oc4+(zc?s%ns; zoA=sWaXIs2&WP38^dd?h?G}IFH-m%1aTns$HB3g>j(l%AXO1p%#?b;*s;!=Sem0@3f^4e&60w8kq@B@GTQlHD`Gp7|2mF2s7^rC()oN5-zncCm|7vb}fivMzjVmxt3=6v9kG$^6(R7bxKvdgR_qP7) z+%>+qKT;F*abJzf9lA!vPJ1KPvibrgUK`Lsd;0Et-P6*nWmO8*3FO+bY>6$~xOYeB z7j;GkhM1b2>h8GC^+)LP zDkhyAIK+e*i4TgK57fklw;L!w{d%<~JTTe7fv|L~+WdB+`W+y-mkmFF1z#6H`D4JT z$ObwaJX**pwpHVxu9r=Iy3+68IXqG8dm~|*CfsB;T4wR4705*BqWdS2eYl;pk`_Zv z{*aMxae~Mc!s){RjqcdSX1Tn}z%Z(utBXa3%%=nAS9Sygx|uG@we=krOI9!wV^BP+^rngzXJu#Sj|D9Rh(949#lI3vTjozcYB78NQb>R2CgOWJ+Zr7{ zF(K1I%c@M@OOduz+UgsJ+3&2c!w)7zGLx$bzSkM*5AB9~OD~u;te;9R#P5X9AP!Rc zJzgC*Aaa;UT`OpPgHk?oW&5CbJmpJm8U+Hp+mFQ5$QytN`|=^z&Y(bl1I+mfh;eVm2Z+<_U3b`+t^B-)u`OkSW2z?OKcm@wslt#9$01q) zJ>x>q`+FV`kl+E8p_j1uf)TtLjfK3)=7cz|z7capBw*QbSN-Ipqm$6j#BLybGJotG z=}fpFH7fL@xekxPA?Pzh+w(LJTHxww%EH_Rp?kRSyTz#^w4u|_!$XD(Z?_pF7gwdm zd5*JtJR1VR5K)HZey8K_`OGt>^kUKnqy4k4DA!#rtnvHE6o@AcaE1_sKi(m)Nml5m zV|hxG`kd>x!MRNO=TULdzSK5B?xUz%M;D}HJM|})e=3hTMb^Isk%_mu@0lD`thJ`E z1{NWvQ1h_@axRoSHHm|EqXy{TP{5Bc!2OcDFwydQs4lwW@qjey8LWt>SdJ6_n$xf+ zsM)C>x4)$>iuUwOwa!>O*G9r<;!LnU@yxn}#R*Wn@_zs>yOg$aQ<35K`AKW(=ZfA~+PkF++p)+f zhUgxvV_K%cN#Q|KNURSI#0&cHsi*T>%=+ug-CKzjCj}qBt{t0)PTU<1u(>)sfa=#ay@|nyo z)~#;$V9w#0lTiwz6M&N(; zH0Fncp=+ct)0YgNPF$q8(ba&lU&!e2#>mfDAivbX$E@`{|3xnp3Pb0AsblLkupwjb zOV%mJWFQ<^;9wh0ui)CxKXu(_f>wF|HS~oDnvFD!eWTJixu--hb60d_5Bg%d6E z_y3~Juxl`dTI^n}5s4a-a8IMMFWF4uKIEO(R9CZc-G0ji`>Y#NF^=7Pr)%}CD27M$ zv7(QEMm+KpR>Hj%JkKXe(-H1DHR(xyHn%F}x}wHs9Q#_NrL+c2_U?$EQU zVXp>%>R`OCK$(Z-^q}9OcAM>#>y_r-a9Hx1!RLvp(_L)>yXhCZ+~PF_YJ-4uO;2+& z0c&%8g>}*t4B>l!FjWHD7Q7j?Vio zd3^g{s}7htGOaJdL@;V~{V3!LQdd(vlpfuJ7Gf?we>5KdmB{N-v0SQQt!VD_BCLW> zpbHYK(Et5Wqkp?aB?JH9xIOW_mG-S?LaM>-pS2eQziz6q(~2OXTu?-WPAQrQq0&w5 zWKc(AS@Hk4kdp?e3kBZU(>}BicKc#T;Bz zlfc6`*o3A0(60M=W%_&-k~e4(yi@M0`e=M$%HEUpXH67tLR?nu=w4T@`|tW4m$aKS zuI7`Pjd0dSklYD#sM9$>gPEM{)m;YtpvXxg{4#c}#1#QzwZV_|+r z<`*|SDfbQp&?vL_CC7j&zuEF2$3T*uJ8)t8tV-iQC+4e8D}4oAk2OK3EhECDO)e6x z-gS?W3qAEYWHV*zpKC2pk_XTUZoF(f%-teY_ixEqJ{hB!<%&S#zW+CSwY4D~xdxR; z!`4DwVSU|=e=CSptV=V@O8h)>9WMH$wj1feXTJ6bzq#dFi*~hW`8shl_Rx}D-nes? zTKrksk`IP*R<}P!6y@&N<1`5m;Qg=O7xp{T@0|e}&sxmjQk?kl<(m{z^oG+zTSy`D zZ9a+EQDoPZ<~)4a_NO7f>9&_m9`TS-(!lR;I^sIo_M+nwCwfVTkhh%um0z;GoM}dY z`l$~xYiO-ovJF&sNYz}RE}-~TqCgKE-oc?3f?+~Gk|iOGewmX+A5s;8Sb$o&S%6Q=L#G?_XL1ECmunC?xj z-8TxYLVBdT0i;j}Qx)5uXs;o6|FJBj;-+l%kJ9+D;@I%x&l;I z_ZXfV1ei%E$Um|TwF{Oa&sU1szNfSp)I4h598fl2L(KSO`Unqj&; zqnSd`fPg6Z%x+(2s<<-ek^*1QG{jhwAhNNsI<$;B9ky~~&0^OuBHLvU&m5K#sj#Dd zDzi51ACP7X>5A`2V)we3R-vPd!=1#lexAE)*nQD{(OkqA{QWM=;@%4kfhzJnr=ca1 zxXu^a!pn4R52wu05tI3lwC9H2q@O#5mMy1eU8bPW|3yYbiK+Y2_XW%o^vAnEXWEvJ zFF1{SO-2f-%RNP{Y-Rh;UXW!ue0}eQl50Q3O}LAH(L1X6-p+`fhm_}_)X104U=ihc z>>Em5Eid+yw&F_m*W4&`@KcVaVG^kOC~8!|R|fgEF4Pt=@Uy9J(Kv#HlA8Hv)Z6fmzd9>YcK){=qcHNWubF^P#rv8ijWe-7P6rx7d)~F%_!@ck9vM)9H ztcFfg^b|5D@Q0SqgQff-@ITs^U20r7{=B3tSw_rH4k|7C0z;HRxWQvVYC?*PZ(}+Y zwuL(7m4x?2Q*&jzdwTTUhlBQPAp;xCr);N?ASr=M;HP5B9s5pSkdPjRD# z%kPhWFq%v^yAV6r(T0T-C^5N_xwJvpp$J0oT+jGM`}6Ijj1fMHn}&*gYTR z7^&s?)854g9hS>)?-O1za$&`4gi$d6g6m%=vA3jqCmqRVtj3{0p0N{Ux9QW7f(xzd z^kw0wLrS=pd%8YIZb#p5aS9p^pSrbeBU$1rt0!9Zb&(x{9M)fH>Y2oxk6k?cOwotl z*m-NTm~$VugHe=`HzGx|ycI=O$x_m?e>?oTIrRxGMnmdPAy$=n!Didq#7Ch)N$uV$ zg$UyOfJ&k#ihri`dl9D2H{{PG*jeIiTJ^MBt)hsfE4lvA_Q#*5(X1ZWB zf-1?*=g8Ihxtu)olfZW==*ht!MWy(voAa}50zvjU79NX1f}X;ySEsUow=>u zFcU(2VbE60H5dFK)uq}UoQ#G=ka>nKEc`1{T=#s+#rkjWMy>8k0*P5`v+$C>cCB5K zpBSd>ZqhGJsh77WZ@qKwUOb@rDqNUP>l?x!c2 z5Jj;A-_z5>)hdh1lT$QorM}zr^?&JR&4Ka@z9?ulmb;+atSCPH>mzR>wb_CdALBXX zrHtQkpZv*Mu!DEfO1{KFyUF-!qz(2Aa|Bh>01?Ca*Gbw{l14})I50t@=&toheD}x% zOJ`mIx(8c<;zo|FAq)`ahDPP3g)AW!v|LHLF_cgh!FHyR2+bK|b@vMho$DOAe$L6^ z2Qx}B+`GA6zVsek2Co(@uerC$Ybm1)-aDf|1j__z(D5{!1OP2d@7XTIrd1V}H*~rC@SQHBKYyF+uW%zstM?=ix&N z6B>9BJTu(w_IJv%GCub){~?Y?6rAyJ5m&Jo z?NuO0h3eyv^syNbTcgd6kzLn4xq8!_-X;mONROt6Orh z|5tIhjuOu^O1X63B@}RNl+8Gi08{F_NAReE2ibjYq>#W(4TX?m46}ekt8_Q_>IHk+ z1BoAzV?%IpOOXz#$DskbE4A}E0Rc6ipFxeArAdPJFrR`as$5UdM^(hteDk1^=}y=s zDYW8Q3lpJ85^+U|f66(;j3d-syl!(hLA7Edb{toB-WlX9T!)-;5}al%F8jyHV@Nh4 zD^qJB3_EV`v(z5S*j)4&uzH|G7K4q^^LSO&4&X!yjeya>k&<9w0%!ncEpJl}%JUhI|s*(T}kb5-+_)tewB=T6q-Vxs02+f4CZUkXq9GqRPN|T4grU z4#g90ucWHP8b$wc-rQ>`5Djw9JGL<@IVZYM^Lkg*XMxyU3~eK$X_oGI;1Hsi1XWQb z$eR=$L}?mRMhIppTh*%=B9vxOl41Y8J{?BJNDpz0vw0LN1F#EUt5N_8JmA zfdz6_R^Vck39!VLxq}v3A=xWOWr#pF3%rC2j@Wtt`;2fpWsDNzAJ3SiXv_=?E?L`r zx3t3~DXF@DMPZ3_aEu4Ze4ajWEmZakmQ?p6C48=(MM*?iDnmzJ6!XuZsZ1s{8LN=l zuWQj+f>GON) z9l9R_5-s06gOEwr+FoTpvxbo4{#7)!0G>?56bTZ(2(5gOW^QvYn^6(-)MSoU`=Ll$ zc<4QAPla52qBPm7B)ZDy`k_Ic=n_lHPLKP)$fA)y#x6~ibl!JFgC8>4K~Vv^*Lo0h z?XzWin?k2^F{i=#&o4lC)9+@c#L_MA@Z3$^?23vL?&f~w7F}~S9!B$F`gB^{@NHk} zz+u6=RNuB5oS3c(zwk1nV^=GyPe#5_Z1& zUfU<#0y8br;EB89=h+@s*jxUaN(W-PUm1Jy%2Bt&=KZVMs@QW1Q0p(;kCCUrhdfiY z`{|gG3@fZixTIlcrkE?~!vrz@By0w}S0p)ftyTEBo4#K;xkBUU=34$Wns$U7`Ene#E-T{Pd-Yz*glnrDLwHgx-~*0 zJ;`59Waesdk$wHSr|FLeHdb+6oaT0+i==zWD`-J5<`JCg1&4bkq>!b&Cg(~>0}k@w zUO0x&><_!xv&UcY&sE1rY%?{XhuP~*4vn)yA&{%Op?Pz&yx-Ip-+l!tdiiaq&rrrHj0|Vm}8JAYuR_7$mS10E8SX_)Ltrf(fcdC)zyIdeJj}V zzPhOR%9Ql8MG)`1U8+c|IbJk|;k8@hzY*pzi)9Lo`)bb|N;zv)Z=V@GJT!ZRY2|n- zPPjrUo4VE`X*9z}kps{p%ugb;@`9Xqoq)Wx|8XU6`g!@bzpfP=#aq#Q=xXV2T{ zJ=8zEqHL8NJ=Qz^v^`RgM_a^-e-LA2aONT3eS0?<<1Uif2J=}L*!=uF<|alVW7!UC zmsVJt+yS$yL^FD+Ps3)1aXSB}cWR71fz9tsq`eNt`_sNO7&QaN^9dNy%U$oe61Wj? zQ(raF5WLBF;a`b8Hv^fupLU;IB3wlKT8s0>MZ){mbC8xnU#Z{NuQ-;FD^r0u-+#0^ z{pFObzyA;HkkKS_*=~KNciy%A?Ho#q*n7@ThJL8Lb^msF)PByml~cf*)gvEHs`Ws* z`EM#0k@%=U*X6m;z8j^hwuKKf!Vgkdu=uN3f1jiVvSzpc zBs`;oq?HcZ{c4uU^_A#PX{(WH>Wpy zwC&CyHG`#ykE8v5JSZJ|j%a)7W0n_oZAyXl&Z9KB=z>3hxOuRGlhziqf|+K;O$ODE z_7q|`MYA7VS!xu8yyrQ7wvc6>9Ct`q1sU4@#N1%k4%OCwP|GUM zb@nibHd6IQA)4ZA_1*#f`r&H9z1(Oo78naxsg-1{+*HmZVSB^T)zkR%BKm>DBK+z& zsqspW6kMD_vrY`=WW~N$xJx&yr!9*@-0&}h*{6{yjI_O-HIXb|-u(O-{K+sXsKM)O zhXFd2lj%U|FG!w9YOZLU5GmvK`}|$AXpiuE=KCh`{O6qN9v|4>o(M&146Pd0WVw-W z>M2Ds?vl@NP4fmrwjMV)+&o}LenopROno=CDKT?Q?9wk@YPby+B(tt~+EWXk)R~O_ z!Ddv6&m)8~&ND?$7sfru{5+tuMe zGRt!$Jl#wCpVL0YR{JhJ5)>0skKcJ1<#{km$T7`%YB1`->fj-H4}uds`5=0&2kRcR zuYHWo1;e$N$s6F~XsqC>wmg4JzcURe27OuLQq{hDNr$g=g>J!JQqlD?8X)iq6DZdN zbQYIaKhFzv3XOZcO~IFWomZv#=HvrY@=aeSN{4CHU|*f-ZvfLpVrcc;z136sX{t^& zJOy=Pkd;XaX-}GMZHZ3zjt)|YQ?6+LdH`*0@Y>P0SH10h!q&0|5K<-(2^a_wU?hrF8e0=MO^@eyzy*#(%#1T!Zod!7LF*|HIZ>KvlW6 z?V@wipmZq`69GX|MY@qtQ4m2=N<_N5TM(2`38hQAyF*Hl5J^et?vOtBT>svCoO8xz zjBkCzwG=1s`#g7Eq0khXsXc6O`N(^TntJiW=w;6i=9eFI3Qn#oFLN9mcSXqpDq^Z$M5OcB=E7W`P7>cW`}K#U)SXZ-9@fk|#JF?L zsy`7S0b!A)QEqA#nz$!D&vAFsVw)Vy|5WKc+AqJsXUdkW*4Plx+7kdn3EKF6ana(t z>!hA|#Wx*BK7A*4(Ap&c~Ip9;U`r(J9jnH&76hQNjr+zZO3)?c@z$ zdw0Hc_5oSa;+@%)&qcb0_tQMA0`Vf4d8Dc)FnVRn1BJL7ay{^msvcX%cy2twlm{CqQR zJ&@Z`7*vI4(|xDd_1U#NRr7wHRh`qgNY+LB`^$6>F7%Q<^q0x;EE!*~AUE?Jr^n}H zXYXbfLqAIaQk_Ph^jn2bVJpio7r3``vUWZKRCU0)Ez8OX}7_NldQ?llA4Tc zAYdyzEKFQWL*p9abbMLI9acvaf|(DuTwuPfLx=dbE009XGO0#p-7gX`0x{HLzz8=R z&yB)ke=pDAStey;VsiUNS5+{4_N3+5xf;w2f!KUKgZWc%{KXB z-gpqXixyXob>FP+U`W(4lq+LD7ND#D)=37H&4OozJlJ?#CnI|8(U@31&*PL;RbTrCfSZGZ&sJYoSIJ`l=<=KNCu`K31HCfz!Tl%@ zA@9iq@NLw|H@|CGu`w~~!Kb@>dyYE&lF!Vx>Eg`=adn1SMkvunbq4z9IDUeo2jDZh ziRiAy<9wxr+Ckys1{DE@doU|*3C&HDOjSf-U_kZUFb8}+bqh;NE`Vr%#Z65?d*!u= ziir)Cw5*Ku)K|h#I3duD)5pQR?rgW)-9Y5ZK=}pu!Ek#57p~yq;(`liSF|56{Poo7 zd76NB^gQK4I4Ces$8Jo717)-_BDaD*tmA-yxn6T;DJ~1v=QU51wWo|v!S$ELKiC5E zSkD5CQ}0Od8?*%ELd+AwZ^>Z@uDL(Dkn-@MDkCI3*(_tDT&Du>B-*HS)fB|cJVR7s zmGfCj+lAJWLBm!V-R#qaEK3_ZyPQj~$jkbE0+PtFMX!I5T`3w^uW{b8T4n_@?w)Ru zAEjWSkjBziLjH(=jN01MjINK~jx*p@QHkKFIEO18hHRz@KFH2Y>;e{-LKk?bkl%ri zE{b|}P)mYxDFH?&at%yPJHRw&qjs&J_Uee}ZYNV0fNf*&I_g2$-o9IfXx|D=YQ$9w zL!k;P%!b%MhaDdu8~nirpPC7LHTyyk|D@dHoO>&px{PR1qEyWNx86C ze_X-8pDWC(O%Sk7WEeE%jPTAL{Q~_ZzmNNS%$?skF5SFuogZmk{;$ZT zfzmB{j$Kq<*85@WGMetZ{(QGsjC?lca8Tqjb5N`qh4@_K^M9oSuGMV&GEYd|kN4|g z-(V^v@EMx21dirAF}M9YA&jQ|$-cZAe@-v59>Hdn8~}0X9Cv zIg+=1AsgqQYX-d6+3Ia&b!t@6feVb6H8?1@HQ>9_m6wzd%$!PHyMOiSRnfH19v+Xd zv?_LXcJgrvX(;k1-d+PwB}J6?Q*~#sZ^^UqgmCbL1hLuxqq8a`H^?gM3OpmiQdU6y z$^xO`1A$6f@Dq3Zj;ey3=lpS9_ zjG4E-igMe6WI{K0x{Cn%+%6rw0^RzxrW~=zq$J|Ew(@IeDz67f5-0)-V~R_wOpt+u z#O7kmIOzbjcA}OHytJBPOwf%rb4QA*OP(gLTLK%#2Cr&#{`4>@qiasuxn3F_#B3%W3yT2cu^&iezotF~qwM>g%cNmeC08HoXix>KB4`i?vAhwLjF1_ zSAfq>E#U$6jT`QyBe3VSvi;kFJp#;aCNO(>C*gfjH@UmejoEaw9BqROi}65fN|Sjy zl7bq!k6k~0WP+_$FXDm5rP^|P5Q__!jkGI3+PK&gCWSR`0 zy5lTSQU=sJ&nl(8eYn0Sz&EJGqn8ogg0_zdmMcg6$L5P>(r{Zjiw9D(0O~sja8m9L zVrsNIoyEt-UW<|i+(lBEelrF&a7`ajOFw`9ybEpHaM8t4hy@9Dy#XB8-J>ewfzxMp zBAMLwm=E(2`Q#<20p$-QE5%zQf=&+B$M*g~`g4zU)zq`koM#~g|5Rd3+jCAvVR@x`>tv&K%w>G)}XYsbQqd=Jqb+J-}`c6C?F`Q6pF5Zm<2r)7z)Pm zxJ@84@n}ob{*m6<%R5%YH2ZHmz(^zt3b1dh_=t5q2wildU#V_i>o>{y`C`XndfC0V zlXpqvxX^+j$(P!Bb7JLL;xRdNmO%e2$CDgw%RRfr?i4-zl~7j7Hsxt%Rwbd@h5v{1 zh_e@@O+hD~?)>-G(>}#@ZTNljm{yud9(;rFt)S&IQ@uHjOXVyDwD41?WtI;wnIuJH z&xDhMf`U$=%ws|y>11FDmI0$J%6wfwBZi*Es4NlO39{$(d+3uSyc;xigcoPvVwYb+ zHedJISA+sDwRrfdCz=9QXZrh=jzU9LmV-+ZxZ3OE2CM*Tg25_?|oBNj2+*#Pxt&-$?&yn$2P0i~q~4lZsSGrs-I#)eMtT3K1y z{w@Kc?0)V4X7=ezZPf$Zld!cGn1Qj-7P&U=4=5D>BLuq>=1)?ViwI*uEWePZM}7ds zqvnIR6}HTkG6xCt{TD&oSEaC+Wtf7x^_M$@x@3ZO;LcQ|-JZ=P&H#s7F#GSQzyFy8 z$(_HjLb5G_LJH`H8JwP@C~gZ}Z%SKFe37F}N-lx?|*(zT5+@P|~zA3A3lGunngf)EnH;aPfcj zPUbWb)6OR)Q0*mUlG8s=O)kYkCPQI^zG6n5+&{bRib@>M$|j4vP>B0|KX|yy77`H{ zA1~ME8ef1Cb5rhH<#k|AChU(ITU%NALMStKjdRQjyxiY;ff2(A+HdVkkpr5b&!PQt z0j>UFY-)NPl+Ig{L4;(+(?S-)q|*Obaay$ zz(I}yi{E=b%SxDIIT#K=r#*jj_$aVuls3M^q13G>-1NK!hG?(PyLt1^lN)aMcRoV? zGQ<;YyG}SaJNqXC<4Kg}J`SiORDK}iRaxQVJ}v@xt|=&?Cl!IW4az&0oxq&llp|*# z<$hW9%ttC@A)n*I;wh;FatXhvq=fxvSJy|&a+#v22?+n5o!6T?18L+NU zahdT|UF{q(ov5;n1o(CThP5?BBur6Uyh_q(V#il#0iGFx!syagc7~{|*3K2dIp$!RX<$Hq6$>m02&e{95ETaJndprx9OJSH9S3*# zZj*!{L^qe_9oe||o&*LZ7>Ix7$+L)p^^Y@s^z+m5iJqG_YSkcuGjikJ>xXj?Yw1_V zD+?nPM5z(!RJiTtdPTFv3T@-6Zt4XuoAt%rQMJz#md_#2VT5j$g9sdvPusSDmm5_k z4}TPc#&^0I+Q~UVIv&oi9W#mRcT1+Bn(53%rnc+>ChV|IswLBBXG;0V{@%Xo=iX=P zXZI%U{7CHgx3@cK0m@hV>XwW(Vm4Raaz<{LeJQ$di1`hLX3@0^{-rTpUA~g3M4@_x z+n;I|X~l{`*`)SHbq;)d26LAsj0&ilrK zLCEg4iRKE@mj3Mv!-w69)v`^*hd&cU1Z*Kp)L`razndCz`c@bKW`*&*X*_4Egd1#} z!OEr5$H#~2u~6~?Au5)4K+>b)k;Lb(WVM65Ee`O)DtLsBQ!2oLx+36+u>>S#WDgGy zqoG;l`Ue_29xxOW_Pus>YdC-0Qh+BwrU#Y>;i z+A88sCW_9*rU%4g4&84|Ct#fQ18wiF2PpsVXf`Bg&A+>B)?lY*ytlh63~VC>LZbW5 z%eu14yJIs1zA>^2S7ZMWZ;$>n( z6D(|OI^8mpUx5(DVPG=^<65)ZL^=v9McYsQ9X`@+PT}S!Dv-x8UV>`HGEJ5Aj+prs zj;Ojj44pixk$0{D`Q8}~PkCU_oLqFjD5B8C)wQ~|y4vyiPZ={aGk=I_fyitelLO^x z&KVLuw(dh{J8q*j#+6(kMyPxhur7!;zPvbZ({FR??d!Al+#Nr)_>5^?slfeCAs?T2 z{0kG#Po&~JW}lk6Po>#V*Og%1a}6@D--ym>D*sF9w0jb;9!aN4qi@+thH{gjW)BC%o9a1A

_k^}F=Zh$dLF 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

n+~W1p!NW&{-FZ8^Zbiu|_*Quyo+Ym_UgA1M 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 O0000XZl_S&R0q$B-++QU(ZEZ4paUcog$%? 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

brAxE%v5|DCg2_s2l zu1uB>Ra*&K>8A=5uTM_q%;dghlg(m{zDOi|2^X^5|GZc0*ovz836yov?uwcnE;egu zT1bffd{^(1vb)w@Jy2A);#%#vB;%TO3Nfn@Y=9c3>_~TSo|iYmIzUM#*jQ^BeOH!Mmn?OH)AmtDsf1lWJ9gC-xe+m4PO8 zc%sxyCy#R1?9iqHKEXI_u18Ni&6l%weJ{A9ShGu=8X+qycS&ld5{W5KPa)O7Nn#&-<1qPu62 zdf!0MLrtLslHkh@`9$;92CE#b)g-_s0gH|&8O(h|At0N{IsB%xo^aT+As@Wjde2F!agW6k z;wx`^G2|KniQgqZNk6_5b~lvXuNn_F2D7$WXkgv+<9%TQIi!{pp6!nO_;R{v;N_$F z8vC@|ty4l-U`xxrhJb^J3yTs^$qtSqy+7cC@B)qlH(`;?=vIwIG-U0#O<{(YAEw-8 z{$((=+O3JDzQZvEY9h$O6aJfJ-{0i3E0`SCtq~7Q;fSK3JiC%7Ld!}mI3bON4?LVM z&-F(&9=5PMc&$Z=zLbQgR!`=YDR`$>&0wt^a^&9ambP-KEo;w@PRM%ll6qwWxN#o? zkqZoT3~_(t)~wEQ7a2537R&4XZroqzt1%v|WptzUBOif{r|`3VocW^VW|ukp+G`{d znrd9)PRqd5XyqKlbh4_aJMclkBO8z-#gXd~rGy=KI4l!+J}T-y3=~j-#}cZZQf0fe zJIp_pJiJMJ@&rI~FK7=28W)5F=j` z;SKtzXFnJ2*J&i|t!R+hVXqTrj(NP_`&T!|@`E50R_Z$z9T*5lf#fX)+d*v?I(jvB z`jdGSMP0aB)-N+8w6CgP<9%3(g0oKob#W=mE83| zUj2>a89UANv0r-Sh zkDx+9YITNvmk?;D=laC7<5&9RZ-3a4E~oRJ<-Yzq{V7jnQ?Vf17Cpe%pngWZ~x&)2J;`6u~h@2<>NsmvT<)P<2?p?i;7fTyh6bsDUa z=uzvf%gIKv@lGu(UhGp{G(vx-_g>=5a(Xf42k;$_xEq0d;Ac2y_|BjB^%>upS@qEeZq{3F{aZirMQ6BJb(fJ-C;Bw z4+u5LPY)7=_<+J!q6$utWo{^cb#OQ6O3G14pfqh+_U@Pj_4<2I=8Vu&{*JbyPS#Qq zcq1~ujO~+}m3cXS9&P&sNCyR#c>zD3syKZyDYSWpct(u9{9hEYp6PQiR!c{eu{0t1 z4G$4(*Rwl>Ui3NCmx=K{W?`1Eu>wf#{e0W8Xv|BJlae;))e|oDCRcGmlNTBQOG}{Y z=N*X9ZbfxfcMix{B;n#7J8F`&L2o{@&ly-_=77M+=YNDiVStk5JFktzr!l#B5g$VV z4h(t+39uC`o!r*CpTd^V9=|kC zeAx}71jsOTC&KAC$=p`Lnq@1!0HXl?^U?KCcs<*<_hZ9XOI}+U^ZOdlJs^R)GljnD zktLs?ef&Onbj`C6%xqlZrKP`*frTLH{e`)`(CD$*kdUAPerbCS$gmZn$s?y;^t_@4 zbUMmTLbRoZFq{-1@!iXv*Ck>1#ZtGpPjiV_N$Ju$wIqWDQD0T_Iz#W#??cl5Q~nVu&Gk)KcwHql$y+=dv! zDSHFqp3uxm1pfP;U}rq{D@ezS#MOi;uw7WNIAw%_N~i9n}KL1*IA| zculzrM9~x+=hIR!w%bCoP4=q{dck990-Y|R6ErPrh#Ie`hx`bhG)+96*PAQF%Cnpo ztOmcHDamRvm$8!ZZacz>f|tRSjMH0OygF9mK&1u{INI$}+NxOdb?12WbtqPy&1tcB z;1ra8Z*k_>>X+#ND0KiYlK95>XE0?1FJ-H+vD*YHUm^ZOTjNt+$k{X`4}I@@jSac z1|(*Ids6(xC{h+OUAbegAA;yce;XYmoSEz8O{l0< z<;y7W#ld85`6QbcG}_DZW-0RA&It=v1lk5**|`Z`>|gXf;l0+k&?_IX-@%~$jY02! z*(O6FPJ$p43G(Ih#v;eT$L!*DSH^sQ%=TZ1^`p-%M(QLy|DsahIjvrul#}VC`-dxt z)4W5uK(eZfKqnK9tLj;-2tVu>UGi1<&$t!sA1#!RKJG;$r&`ZmxF4JN?m_*oy2Aw6 zeR`ZqEA=O~lY9wm_aA_L+*x}MoE;#D0Uf;(Ck^O`j!@<7G}fcdw5ix1uW-_2dcJbQ zSjl)?v}pMm-QIHo_t~k4N$#ml^Lf3+$1S-3wnz&1KJltLRIPi$hQJ9|B+C|Iz8jXg zv9rmtzr8vZ&kf#31c)iaOxB^w)w)BN_GKUzfl3%LW8LNW9f)DqXpHY0qh>u*k+*?b zPMoQ(|LopyOg=-m#X}$?Vokj*f}o%XE2olEUQA zH$7)EMyt7TposnKb}}g(;Cd+*K*-RYC2>{TihB4)*kl7$0EOtA66b_vriA*L8Fl2g zFa!%?3?=R`FZQ66fL93rkVDvRS|Bj|bUi_xd?LW;ibZSM51)`k_(BT0pC*b}rS0n3 zInDwRvH$39PP5Q2{~I|dF#SD}49WQTEC&Vm49&tRs?*Qz z09OAV%{nM=y)~^36!E9lMs&%ld@v_!rqhV0O~MVwfCi88*C!UwHyh^Ul`Th{cj&7y zf)Q`XOwwn6yxo)d(0li42;tWcZllUya8+WwAGYlDh-|u;wOxleLycd0xmy|IXcpoe zF88&47!e2{4i~0tXiuoxCP;Gd+P(@dCsx~XCw%6=tE`%`ov(W=pN^5kM+wNpe|ar8 zIh|(831`t^TK}$~vM9I-t2@VLIl$}RuKdcmpdSIKVERI!u zN|DWUn)eG`*NEiO$wF6Z;i=>6TU%Debv@DcFO;MC1~S3T6f5`*-w_=)5&e37Ax{_D zpw?mk$_qq}XYnO~<~C25=Z-bH>i&?muk|piM~hp=F4o~v^P4QsJP6Su)EiQD!oY=5 z!s;OHIm zVEa$OUolwOp$1T|IH3B>a_h+_1GCbGoM#kWMclJmQNbfV4PKIR zZ?nM#l4%(%sWnF|hUlQB7(;0&d6ag=b3;{$lZHI>5PI^y%h`OsE>V1rHS)ck^B#4t zuGnr-WgU2FT4C~N8>YgdX*|=}Z>ZGy9;eGZ;Wd5%^3d#Ftl0)rD7kc{r^MN1mXJ;S z+5^zN?Ip~}V5Lm2zGBd6JW;BY87PCg41;dgmp=dUlJT2c^dE&Vb4K)%X#idK_`7BL zTP`f+y%#B zGOHTO>oiX9J8lU)zP~d zXRO**wGuB%oW>j#}14=cj49v|I@3yFUVf+efSjM)SWl_MkyvFBTTUyyLAV z_5*nAI@#F=*@DY8qVn=5x0Lsn)*=A>;AG`U+PLZ=9)(2$DN%7+^oYz&GCx?KH1E`DAkH4XuT0I}j)2RtnKmY9VzN}IDv6b=W2OkM$zHEh zxInU&G^MSKWl?V#Oi-6eQA~`Hy%T!@6d5*(?8@xaBQ{?a(Hc~LNL;(@_#qIJXnMhBrH662xrk-o!(C?K{U(IoEe(8Y}}fW?}T(Feu#l%^Te3z za&5i^_#YF^-TG70eMIlr;zd+{++7>-rtPQ^icQZ>!8Z^=6$@OC$At;FtgAeMz?ZSn zoVAoW$88E8#(md-1~W0!+l~2ZAQ)rGu~A?$b_a1roIqMzV%Lv&2df!Aybn zJOGcZn@}~boxB9IkEVH@h#NOkZR;+_S_oMtp=qS;0!1YhUE&d>fYGsQb_|7Q_Y%)d zH3;?NF*PwJh-`VHc z?&e+Lobxw1Rq?~Rbr7^yfy&Mi1}r1;m`Aw1NARwnI6$l(Ec=sYGOU!Mu3Q6hqQs}TMTxzDV?v^^c@rcL8Wd+<{ zGZEOAvF~UL_}nY{J~M-@gQ(4c;2+Yh#!x`(wcI`95IW*E&g6F;3kO_bEC(uNfTojZ zq4iOeU{mwc5FnVez^aX(4566zWtG?eKKPkjSy!0B-JFP|sy|ViNY3Z>6y%}P)59Q9 zz73{O>up#nB6=c4qMSM|uLm_dpUA%LR1f$Nmgd+Zz9wA$7M0daf+CD91)@T%Uzn2K zYZAa&9~;HTnmraMP(wJgJz)Z5*RC|frrsc^sGX@`zc}Fy|K_QMeo}T{S(s7hi~PqM za(dc7EPT)=?tX3FM&kPjq@C@&7|-AE1MK_F&Sj+huU2qec7En>+IjrJS*aL!G(i4T z2hD#j!EzE23CLX`a>6F=f=FakG9gJi-qS}3Q>#cJ6PD}bCz%|Lpa%uzJfX4zt81El zJ1;)R!f_Mu(Itz<)2ytRi@oJonZ{nYW%|O+G#bd@l(U&E(g!z@{~_CfEytOP5V7Ae z4}NLlV97;hvjOfORn}KB&Y}(^C08BxEOg-on^3@V4m_CBCOgI|YH9Kb(*dehkmRH~sq<>jzNtVWR(} z+T@EsncYmqASlusm(8~PtFVOp`Fr=*ZXw;WG|&`h6|CWhr-!GC*r$@gVlxIQU?!tp zY}ur`=K*7l9*#>Cu(6}YATD?`eRX6nBp6y)^RxWDhcBRp#FYd;<}a8h8{%9_PreZu zkP|j7bvyPP#uLv{32qIitp7;cn@bCl`w^5JnH2yI;gn%022!-G0aW<^%?#X*0hJ%z zGfba!D4`vZ(%w(4J#hlffHU${M*(Jb+lRQRSb`r8#P`HBS`WSt%tLh)^VIKLX0fk| z-&WWC*N;%k^hesPicT5%6mywd#zTu1JGJ`!VJa_5zgeI~z8SFFyZR3S@Pip8clt8u+tc(OL>gmo!_4zV+^%8B8Z(aj--Gh9 z;iBj$cJC;P8^rI=F|>!oj1sYjt?10&jt`UYc`Lhyt{Y$XuOS-Sm``i>L-bcCF>zHQ zfuqj_Mf6!d2jY~l$8aK0psYJ<6X*88L$Ei5#^~#e8pVvFKSk!T2R_FtI7Wzs8gEt$ z&tx&0gS8t}@|z)_vXcyz8SIDslwtBY+So@KTE*UumXf=4Wt~N9tMyx%w#a)b)V`2! z?}fGwGOIK8(dvzN<)O?A=z0!ipscz~%G0%7DZr9Wnr&2{R0$zzK7x4!r}szagf8OJ z`ZEdTE<6)aN_9<8>-rI~ z1-``mcw+QG=r%xZ?RVRL?Z`j_5#4$lh<@<|+PnfD*yyORvrJaT_& zJrv2kD-8V##im2>Z+PV~&R_8gHuzZ{HU{{7!5jjIkvGprKEdJL8Khv+ZqrrCi$9J=ejbX9|G-Psd4o0q5{e%8>x@1R;GhLr+*two&UjYL^fOzlxzMNiB?SU7 z1(-w?Im>o5!uqN+%t~+O1AT*pal^--{$6wThY6|)YFzBk9#Q#&Bzh=@0PX7l44hzN zj4oh*b&Jw0h}GSb1SkX%7u&UgC3iV7vU4Qu{L@H(S=IhuS)3X$()GSagpNQ+^g`4p zQh*Gou69t#cjiJE9QnMV7<&XZEyXb1D1;I|-j{|KfI5@uu zo#8s~3^BKf!4HwUrxL^+Bnyt2driIl(Y;wEs^af7bc+&0&XCg*iJX`T1O%t|Fs*IvB z65P~G!GlRc^)hM zBnAi1&$k==Qy&VKEyORQXdgd4G-mmfc*E>@IwV@z0a%LlaXWQ>eQaNc=9hys4SnPa zcI_2o$E${8oM0)SwCUveruW~<1x;KQcSS!Tfz^A30&-C9{HnIxPi{I`Omd_RayqL1 zmyJsn`&Qrd4`OlT0{$qfybhMRX?g&9FN;$;qmA7rw2^t%TQa2PT28&ze>vXk*4WP} zn+J5a5{?^3m)teSzZT+RI4?syFQD|fJkj5?gq!?Uyj_VJuW@;(aOl7NZ1lS-#R){i z4C)?`(So0fPDDgT5b!APNM}DIzGldxK?+j!yYu;oJ9_P0SilTQ7k*r}gB~gY_!zBn zYhbYAI>3Vajrsu=4Ym|NBCYP^o6_fA2v6C0w_4wvF*wx8A8t5q-l6~HXY~qCy~|~= zulaHe)jo&c;iw0fU&k7LV8Tpzp!znd6}JT+p~b^QHzQO? zBTpLqr+AKgJ;mzLXo=gHx?|@KbEGWLJ%dYo8bQImY4=|bg0Z#-E7w-4_37Tiv^kE> z&hoc>UiS}!8AY?qepML%mO%L*|6&#pjN8daS?Jqmt;C5~q@kWrUDhzcNs@Y};Fy)9zdZFqw#M$6SH5EY4?}xreVI9j8TwM9pPvP5-DR7)gvF(`Tpo#yA8OQH{^*UyD z05UIM-#N8oA>E8_m-;b)35=slXt`mxwmrn*g0U4!c+LC2Jea=$A$IZ}PLB3WhYEy9 zMH${mKMZI);=@UpY?Fc_V#E;IwoKm-2?>E5)p`#GCUDyak7||v0smj(l$Vz1i@#7B zp&(q_I5(2;_c=hf7|r2D)OJe6_3G^ zN-cB2Au|EBQJ9=T-D^KA29mt{mP!m8&z8!cC-Owgkc7iM9b0cG%d(GYIwvt=yFGaV zyOOdy?&++ysgB>95Oc~VcyRygi$^V2k5B%*;MbR_fqHb}bI6PR7fcW#O;7ZK`ezS~ zKfC|Sl`Ayo?&}Z2ChC7Dy6b<{L4bay9C`l*6XqIp*12t^i8?aX?^O)EJoJ#b?R{u{ z`+Ef+sHqQmqru1G%uOuK!SYww=fdtATqnxBgA|7p8=#RbMb%V-mrMte9(vPG^YORyF5|5bJ#c~3gI2g3KsF5! z0kRog8WbwZ&1$|54l7^zVyi`gV=0$n2+hcshhYKAKsbTGcgi^GR?D(d_Qr=Nb`?2le}>C8(W2Jzo19Zv zBz2XP$vH_`p_$&q_`h#_0&a&Bx-lPLNuhQMOV4Cy@xOAR&kq*TZj54^4~5Fs+`B2Ty+6DJCe}<<-!nTtfYL6Rky&j_?DAfPV-36LtClXbd zG>a4=DtB^Buu724tbBj~A!0hGUuJdci|N#v0gx$8DbZ>=YL11^W+gS2#32zWW087DZhbKG5&EO&KQFw?MEH zS{E1=V|QhYM|$Rg%5rT|P#3$QC-~`az;QYr*ZQ`ZeF!7=sWSpIa%s)PYB)V5@nKYf z>L^i~wUrDhoGsxZ^8fc$y4~Niyg=Wf+=NMhbSr2d%AN;+>~_+B-AZrTHbO|<&{b$$ z`j>~M7>QnN;&iG7e8T2k_9unel~N%M6VS3$AdW%&(fFtWA!SAV5TgCzefVF_ZP)nl zb}QX!ssBjRJzXhUoyre@9!dcv^W}OdOLpm`y8Vr2tH0lB=>A--l_9+sv`ce-+$mKw zi_?0&nfBuwMil5NhxPd<4k9XXHW91a{=D=9gGr@%P8}{rD@0=V`muEGD?_p>;<*e# zylJOD7X??kT&ifT1NnIe0sl6<4VU96MVn0HvNZz|`e0Y2x3jSiqDPdt}}npcXnD(_4d@9vrr<834_(EKD>hU^*D!x0i3noYr57 zE|_~~mH+B`pwaSdrqQExZR>QHRZHb%rthc13E8s)n#b!JR4`L`oT-A;=+%t^9}?(& zy2-*qs$2xq&44;W{~oF~KXKd9f|Bad?wUL`(#K~yawqY8WWeZKCt6I5UL(!=-w`=6 z$Q@bSHBj9uhqj!-+Kg|>EI@#AP9yAhUD*@31=z%dmhwC#Dfh`03y2#Tor!^;;sPp9 zgy6>@)0w!tNf5KJJPctT%L$MCO6SZ&*hGJwy__*0^~iuXW*Py*czNjhhk&J|(3`Bo z=sJj1a2IE+&KZ;@0V=kKoYortYkP(3dgg#wZ)>jFaXhSVHZN25EtOGFlVXH}zalrR z`3nae(#RVQkZY)L!lMV#M;yQ?idpuS93~a`g<8HgFSFzQMuZ&GheM zQ(}cO_c`}{U)S||y`Hz)9GC5B%jt2_0otvO zKOOyspKaj-u&@w@?;$$0&B!5-qswp4r;M*mOHt$~%!wgAB6Vf$hv3AQM`J;JDa$v; z>(15sgINB-3$}pEnG7WHhi=;sE`r4CFQuf2yr=d# zTQdC6#i|e3pL%ca!iGnJrVz)cqsk23w0&k}#^QJEP?Kc@jg|efK2>(Iw#7bZChHgT zlzYZ+Og{{Cz7Xkh1Z!&zNJ_tj#x*LUun*&c*wgk{jSiLnHo=|$H`4oLc-+V$<=A4;|XM_L6 zVSr~_GH86s$(!8?D4K)%P(JM90i;DB=Fvug0`efpCr}nhBQ+74TABcPKhKZkvv;tN4N`-9`;_r-3YdmG zl9xuLTa=O$E)+W}fAFEzSKDcoVfYUxm+ruvUQ9UE?h$}!wdeSj(JznqpVCSA{WlTV z#pB?F=sx=Cc@Vv++|2NqQznenK7BJ@`$z{qzjWE<%o*dxHoCGGYiaP|;hXRGnZzJRH(`oiLg1s)jgF{nG_-dcBekNE1i7zf_7NDOAOAo; z{u20Nvn3Z8RmGQwexy{s1a81@(=d5V! zyx*9KX2>WGSjh-_*X9;Pu^JXd8Jj!!^TRWJ^Ex?vOz+4h_XZO3>US%Fwbrt;@I!gfc;Y~i%Kd2YB zFY_FZYd5%>6G9bcl^?gqk}_M7rChMvfnO1XKd%uBm8V}kkA}A8|MH{C|BK;O!En#t zv25!weGrzW+e$B{lT0HpM_~89{k`;}=a4LMCz>6q*+fiLTYnpBpyAYf_jdO`jbB>Q zg^1tKUdv_R0LW_SlNh`EZ@>i9GySHaH@+9X90I8rpXQRDK}O;l3r`G#$&Xl&#^mM^ zdy^r2d(BlX+}!~bO&+S0RK#fD1F^Q~F5SE|7UO_?c;706@5uIghvKy(egsXj_!T^k zoW$;Yuf*9q|2L#?C$;%m=DkP|Xt?qkO&C6{a&34N`^8-SoHw`nG(?ypv+V?vjcL&w z=Ll7u()ev5+{O3E2grO1ED9mPcB%FzcpE~)Kzt0C7E20DY_aYMf2bjO=LL5}n5 z*+A(4!{eA98aGe2H+C-IFh`wjzadRXv;_HIkt&1dg*$?&8l}$P?;vbs>%kFZcRa(S z{I16Vgv6fh^~Fcxf);OmlO8+ad6Ue-6z?3iEPR|Q(invf$g}K?nD!fHU6uBVCP1}+ zkZ<<8mH;r zDBR!er{u@E{{qF~=j1<6kcf9lVLSG$EWN{ar;-9)eD@4|92XuaPlZO0e?l=Ie{Hjc zx|hf?sHU52kCTPM5ctS&Zd*SqC^<4aL{M^IBS{ce(=ep)^J*E}JRS2cTF!a)DD3LD z;CUATiC$fS2n8d(g{VR(-6*i}M9~d3s5ClBMt`K?>;t!;* z>rpbXed(khAdVen!tr+Y-6&0HtkHGz**=bDMU~Fqc*%r0+aCYlK+MMJnQ*pp8QWq`?`#1;BQ^|@;{JLcpgy1>CAe=C)nCqH!E~5^NGlpxXTqzO76CgV3 z3ZpWuOhw??cncxDcqxHWTu-TkfMg6)av|mHY0cf^@#~E#*p;#vD0F+!&=}O$RCC%( zY!8fr}B5<&VxTo*de+W@hTA2qkA!Y zt!&e<*HRZ+{r)!TYo)`NE!l_M1jzT+y0*86%rl?MBRWW|Z10kyCo^QrN_w8}D z0G93Xz=kMWE(LcATtm!;pyK4%L`dPp4aQrc2L3Kv8FcPl-@CWOPB11AL2Hs}xQc=Z znw5-LYA3=iu1xOv`?3r;{egYaOnYjmL)C}a7M;j)bdnkAr}yikP4T9MR2vez!)YsPh&nZ??!_b8dGEuwT} zF`hAl`+^2;5|E48RZPci&p0Ot?>Xe_xTB6p(QX4Z)=C0RLSC-C zHxr&-4o_G$COBEbJv5fD995%KL{s9XWqpnq<+t$kHhP&I#F-1Pb&2?D=K?}>s+m3*qwi;d|Rhk+Tvs4hcW zEMolk{LuSmcW_$^$77E8ss<8godbC-6Mp^1c=NrievJG8~u%$UP^N|)|VrqJR zoh%Uc3D%~jRBUzU()o(KZ~OxVMTNGSsMs?OR}F%#J*X~)S2Hh}Tls!D#1(P;W3u_;&jZ;T(J?QkvIOu$QFsRg?hjP(;5PZ4Arsg3k`5Wqk1TRvaKPj} zFjIkecbZ;oX$xVAR|#mSR1!7NT|s{12g5jhDH?}_$f2ksxEN)L8h#HWd=|=59-)jd z9dH&KwB%-pLFr)N07)oHio8JMjvS|g4@L*m5i#@woPeqGRf0&76MomSH z2$|iJJweYwEn9FH_z^ma7`+Cg5*Q*xw>wQBmtFZNtWoIx?-L7Zov|7tG*lr?riU{6 z8t{FBS|`l|vn}u~8$!QhBE4XhIE+-h6Y>Z z^_gqUQfSyTMdW-QK8~K}<9i1A$-gsuQTsZV7#VH}p-4cQ`nP4))>VtH%Gk{d8s6`9 z%n_#ecaS>Qs8bzkx$>(~$vm%s)7L3$wkP9W79e>cwHCq_@PG#C6J(_$j~(L~_+SxZ zp%ho$!tx|+c1*+7LmL+)lw5C?9EFv9Ve>qrLFxi6M+nQ{2#u+jMywQkIaD}IJSJ;( ziaW3MA){ennnC+($?JL0bHa6am1VM&-8fpqU;RDCoZUSSM{iMgM;zBDL@RK?cqXgS97h_6GaE9WdlfNfOWF_< z6T@2R+@kcP>%34u#NKAA$D8XEd3?AR^uY^*H$L-taN{n;T_}t3N%&bNRVZTKw!0jY z!%m)@FwT>y#Q=o3?hR@jFCnpNuK4j|p`RyMJpo^Z6UR!_nG92wJwX^Os8Xmj!S-K} z-55(;boNy^A{v%9Gn7DiFWnnp@32&6^rf1g$9vn zty0g<8h7}%;1I>w|FzD5)OE$MA6~8Q+92pNC1i_|oA7+``O1@-6YnMP>X7cFhT4_w z&m{2eF1{;-If^|Ku*hTpXUM@CSywYrj*c>(IaTZ3-+Oz5 zzDiW$*%U&EU36Fs3^;EdYt5V(-i)L$5jdrmV0{k}VUPeJSH`78 zATkV!0QypIxP{hVA)T~xcfa<}(YBi9myR)_aPlzszXap5yQeK?km<66F1FBRGaiW* z8BK^;n{51*WB)O}939i(5mt*Qhp5Q|Lx1Fz0uV;xm!M3dGGT;5C1vUnEin6`tdNZ= zO?;x)MUmJZx7LRjwt+m81`-Cw;SB2OBxZE!6r^x)54gwCOyU;W$8Xk3m=ZSOq!`4J z+SnfyE<;}j*#G_c1TP5LQ=!T?2)vw~k&Zt5FTv~tOI221|Int~G7`@ynm`D4(}@d& z5Heit=%)`8VgUQdsO9l{3y%#F8N-qjGChFII?fw`txeSe{6$F*A~|C^L4c$gO!d(g z*}B@GU_;O)@os}Rk`W5yKSbqP8eQ6H1)hrXpNRdG9BL7>&^k)6N7)l(@iDQi)J@@J zdpV(q2J&EH-=-xa3q(aW;Z~m9PHvm7!hp>yj?5uTrhj?|5-01NG)GnKad$bY`=3z) z8zPgC7?g%*wqeH!eJ6577~wN@teS(o)6p1Qj3-lhDV(IrsuZ~m%vVTZG<8DUI zPwgL9cO|Uge`X2{xtJmfJz537*>m1iF(w_efsIQ@9P+ep5-KSzEiLf3Spt75I&LYoyAxy*q}A z`Bs?)aNThJ1-1p`sbrubtKK?A`6}9d`swu8>R2t+91jbW$lo}Y?7$2c121>xMCg1< z?l_PhZV4kv%(%xw1-K1lDp_kcst zbJ^vbu#gH^+^UrhwQ&mm-@oRJuw6-^6lSUO8_Q@|yGSeK`GoJS2+sH?(7Y7h_lplQ z1m0hjAxvjOkI13h!!yZ$LlFXy3^gLG%_Xw~Sd1K=w6v647T4B$-V%B2hQp&dQo>zb z`E@X?_HVCVTvXH4)&?P>iLRQil;MFYFp*&ria=9AwV}OKq6NjL{F}n!5~ydK$F@@1dcMpfzp$#JQf*X)Jhr9mdpv`vj&9>o^(56xHP+oBu0lZ0R4SkC;W06Q~C$7{i1LCjp?tfIE z^hAzZT;IX0c-D@+7A{~ZlPNyebmQIf(}J#Mpb-lBmUB^{{{*R?^)^7H$@Hp020SuH zP@^vO0oLr9Ebnp_C)IDHSmMQ+7glF1tpb(?_~(@CUw4s#q~AhGXOST_b1Wuyvj)yf zhz7=1f)HZq^32m)jH*a#NGJ(}=<}vRe!m@IVy)&D7q|eUlFz8hS+O#p-SCP!DFV}) z;0(E}rm9+yZ_8R3>oDFRaS70LxMz(=V&xDcr7=hSrf!&q|omR5ht(H z7^1SFdq;Ux1SBNuxi-;&sut~|z;z~*qpICN<@@Llo8?3!aMHVde0n~A+w}W8CHi_* ztAQa{0(<}VD4IzLi%gpVd!ap(@VxZZg6C)#x<;OV;uY~s6^)BRD{C-lpD`CQfU!v& zQ2+_JEcWK+j)TeWC&!<$TK{kpYrpDM&l)tC&H{E%P@N_Sh@f=RmcX;u*`Gv-T@6%u z=5BN>c3L)V#}b1Bl!As9X&x;Pcu%|7QbkwIt-@DZTlyE{&ADtcn|1Qy164*~!&mLC zaUGk*n|FT-3gPJ1s5;)ICK-P-EnrP<;R&9OAIoa1ciRm&^dC5{Cg3KVofsle%$E&; zC-}Tu4PCgl1O~4nfjVz7k;KgqJ;(1*d+1NmN1;*JsYngpXHHOurWg!imMzOXMNvoP z*KU9M$JNTFB)PGjG9c$eINo53e|z!WU6rkI@1mdmIsEdnvi#t65SSP0Q~7&7U?;U4 z1-ek>nGW>JAWvNjBirLs&p)fR{fk6Gm3gu&brdj5j5qVGpHMN2cuY2H8`Mq>;smTH z$Z=7Gx>sH#Y6}Wjj#f+a`F%d^scqSacT|nHmNPC;K?2YT0XV->%5_J1k%Aj{tk3Q+ zb9q`d-@C(P7lly<@Fo-$NJ&x60U*I=#};7JsaY$V`-lAX0LAdDX)?JVzwyvrw~~ar zPALXybYXxP-7zU8C*OLbv`Qcx_$u9gkOy3f;v$EKmd1=wVh|Ix1cgtRcJ4-U)8u?v z^G&~r`f31|CPo+Ms@=XS!T{+UMV%w>FoC?m5_~*g=j3FALZu5!=IwVSa$6xTT)JdZ zN)bx*h)uw}HJ)G5mMw;e{7q7X>lBAEgA^*X7EriFg$Y50w5uNQ9hHz_ZlCNdVuij7 zm%AKQv&eugGsX-vIasPREG@}66I;%y@BT+Hy%8q&{4_TO0=Q0}q;$pGB-}WQ!hsz37pn9@@JB3=VRM|3ji)Jct}K&ynB6jRW?p{c9nJ%>;2u2HsvrKF}@Zg068d@J-9Re|YWQ9e}>@$P94ehQWa-Bwk*( zwGOx*%hkBq^Oypaz|!r0HxV{~vkbx;iL+|HW=K_db>vQ`xFvJzQ)ur$aOJ_1o4n9L zYh}Q9n-A+eg^EX#!J^6%96lA*t%!az?tj(gsM@oYBrWued#uy6#Wo+ZGs01$MNM4CK=@(o(8d~^W1>LUAR!Gqub)>GPKb;nNP^E*!w3AIG0buw7VB|@u6R87T=dm7wu}ft>Aur zM$8#Mfp36Bva={DE=&=HAy_YL&UgLl`S=(9`xj*_lJ>LBH25cL96 zaj!D;^RXS9k2$iXB7F8IWqpSp7x)Lh6p4Ho_V5WIqV-yN3{@Rbd|w1g77gdDFn^>n z^Wj^H!+%a{!q1`n(I{i7o;XqJRg6C|8bzia^}Svup$x72RT#s78wfvrZb_gGCsm4S zV?56N$GU2)^E5qvWG)twK`44eX%8nHrlX7k-GQe3Wm#&ul)tjNeTGVw>;pm|U!&2s zZd>T*H-AsBBOoj}&53^9A9rQt+7JvXQrsrqo!|-yGe5{|xExfdBZG z7$S*hJ0nqW6*0ppRlA^2m-1p76hr19Eq=?@OZns1hwhlH!R?xxdlha1uoy1wK(V9q z#?sFR{3si#{Fjy`Q8$ZHSt-psv4SV1LlJEfwwSRjQB`tkO zF+L_$Xgh+dCvWn%k{C{uxAiAnG#8KCAWwwwF#}!*zsYymLX7tMU5d0bPi~*9dwK5K zIqqk{L+ClyRq`3>NGb@n>>W6p)TY!(m89~(hSNO~?2&?WTnboV2-@9#hf#6~bXy&) zPXYr(oiyy`6$qd`i0U4mCsAk@S8m)LMoOOvMz$95yBJiu1^CwN+*A@7)?8OsxX$CH zn#$~cA8qk&>}F%H|1+47HN33P5}iM3KPW+lGHbNw_?; zy!4Xb-QW3rR4B4kC*1n^e!rj0pDf&UCMJzeYtq1re|vk^CqPpSG2RTyC!=!4xLQ^chEhz%|1cvVoku3&Z}Xe4FD*OV<} zW9EVHC&)Ji)3LHaffc!Y8Y++6FER%9QmoB1&(hl;i(Y|nWAfYTj65vEAn6MdqH7``h((l(zamLbD@{Z}5 z;LQD*g(P0+4o9ji>wVPX23MjBSsK%JFP$&8UT&bSGqaH)>~$Lf zJ88rogqtIa3q2CPKK(Kg7ADvV5P)M@@i-%g8JmyIW5|B+z$@i=_X2H)FW=L@f)FcN zH8U7S#>D3^i}1u{nU{$LMG?`E2O7cJNE60LW;{7k&VfUuVX*RddLPO@au&T$nY;;` zM5#OJuevpY5f;NjPH23g9kMJ*xbehrV;nTiG1K)h;bAUUQGG#qBsP{XMw!VyNj(P*iAps*aN}%Ua}d zmlrUqk@De&lf}Nm{M+2#DT0KR&LLkke@dse<<_4+dUw}>*VJp$t3L@#Us?Q!@jDF( z{l<%pj?A(*u8Dt>vQJ$X`44o{R`^oPK>1N}<^)Jvy>z}rg-~0KvfF~{b2H+wr7kBB zpNzt4NOYI0l+BrkcdW;P${hs7pCO?HiW!~PFc?LBs{@CDdKld!x(AC0!Vs-=wffiw ziyTzMtaAjVzz#KM?=b}XJC=Oo(ga_=D#n zsRGy@*8sT7WFZETwt=&ItLk&~Th`QoCK04O?WbCRi8BgYlkV`Zqy>)O(9r}q0GO$e zB#U;2M`Y={_t8=1RJ2HI&4jC^m z8R+v)ZlF?Nsixykue{XD!%d$K)~~($1>SAUl_9>J-s&#GOQ_iMwkv^(KzvcW+5ZGY z>pfP!K6e-o+gp3-yd`&1CMG_(A$Z-gL|DcGD>jdi5kZ!2<`}|UcI!q2%d%7ygTjui z1bGLZv@c+7-mS-pAVqaLYrR;Mj7b{T2HUp-#czw@OKG@H_h%J;jsI@=2RK0` zXn*+dVc{VRII^$x1l1)x#-D&*sBJSymmPZ3qWuZ{)zHpoxwvp>1bz%@e zMzCn>@mzBb1qwblmRVYDl=D(?dp6pmA(JEa-+i zZs~SGXH#7u{A5WCWk9XmA26b+8-h z`DT3hl+QxDWMPQO6zH~gB(lXXe)Ow!ZfTA%UJpw2c`sP`Jvz#(wM&r&n+GjD=7ce? zPL7pNjQ8(fe2Gjuhfb#9W4nk?v_QOAD0$^~ z1ep^PVqOZ>dub)nHcZmFxj+KhBqetAeS}F6;tk)nn(g9(C5P#nS7Ca>@~s*@%K8IC zJh9a7f0kz9*QeAj4+gs8$VSJ_V@bWa@m zA}c$mbINyC;lzZoGdcL#zHI&!_25B>#JAVZMYr>ntZruf`gaIkL2K-ex}$nJ85n0+ z(t~op8$Q4%J9n^m27k`>Q%VfNcvSCG3Z$jm2=yI^gRmXwYrVm2J z{(m*XbYEJP#YLzBO3R$+GRphpnI&!PePX)dY$BeMRUd2-mJZ<+9@}@~9#L|@jODX? z$U}I%`uH@Z(@B4x@2j_N^c>G1*RliErJhFq*oxE9vOF}gk`gjk^E%IM-}^MELFa3C zek&I&v!Njk^>65Xezm!!s(6Yx~29f(L%f}(KHEJ|=Q9uI1Gcr=K zh_f|uh4I=-z{(Zj;%IPzNY{H9c(Qfj*O{He+H;KVS{7U>E$%i`>=!}{jJi_L8-o2J zyGT;<*$Yn>{Qk6Wka=}F$K_Acsts`h;qhDq2h2kBESE7DvqKbLWyd|*+@l$M*y3#8 zaQX!-`RrS?<}or*Z`bZ~dh6EbE=TE0 z^V>pawy7SxI*AUOuFpokIB0ony_9W)ELnt96b~eumLt5>{Rdug52t7=a9Z=9cnBE@ zGTAN3;-uqgIWzunjA;rF)x$6JP^4qLrYv8J;66Uq60`E4)K3bj5VDBw1h zQrlt*iv)4hgi8PYMjk}6Cc}p97M_q8_w99m%wa;_Xc*+tIRWEST9WsIt{xR~+(B6} z{@0ZgUzwZ4_o093{rNJ`!@zGy^ZZ;SF z<!Jb}4<7FghdIm;?yEqd!CuQpqt6JaeVrm*1-7=X=Dy>1rX#9e&}UvcYS^1e_FEG# zL{zkVwE7=A?(GLFaj+5|&ly1=U{>-})-R)etBx7{qIBpZPJjMJ{G>Opdaz{3;r`~l z=KFO$22DITxeVs|08*2glRC|q%kK1}pb%7i%@Oc@(NP^KVq7qz!TmGF;x^@JwS{mP z{oDa8*hhK?-}skhdkn-%p%)$~!X~A==so=p`wTp4S>`bz(M&Y+7lj1SZ zbdu3omx;g?@}=?rq;=+KI1cO60mHDJ;Yx+i&lf$K6ue~w`{QKT9Dr4e*&%VGt_QzyaPRC?mmcZu+(i)F}wdc>87mWlzsGlf zV-{t_;2^pPU@+1_g}*QIGa(Z*u3g?ny)12&r$&uMkvl~e%^iDZdt}TKZKfxVKX923wMbsfd;ZSGQN|RB> zh|xu3nPRgU43v743k_adatch}ouKoucf+n3D5tcE-&_zvByydCQxTW}gex=oxFUWE z84C&dHzT5|mS7~|8>+UGkM7=nsQ>qjTcNi%h-X&uNX7D*6M(j0vf{Bt4_qe@Rg5u~ zSKi&~arFMQ5b5;(#QCl_Qs~3&#lMw}74HtpS5^*-8-4Q@S+F==O6=vl9YysTem& zT2TwyV)Zy%AzmR~yA>w==TQHa&Bb8bZxxljd&e#7;p=y&pBQx{)|^_E@M$6v2n{+C zqL(Du5?Kc5M7_F^$t`thTx&|X+uBOkqc%^Qj1&*TaNpiS0Z>ZAC)FEv>mC=@RDxM_ z@xUStIa)Gs#aR&bLy(YiFq0!ACMOr^mZi)A-$TdhCi3!iHS9n*CI@mzeKPTe2^(KI zJ@kSSTB5X_%$G@|@L&>gIYK~Ks+ayxv-=z6a=ZBiqcos9gC=N?t&*kZ50(qoRU0Iw67J>%L8R+b%SW>NDJ{j#d3&z>0$ zTz#7>ZW$%BpOK8jPJ6uWauvtQ;vqZkty#>Eegu zxR)R$2!-K3-G=Pfi^xNC4Bg`C2ctdezi2kZY3yk5KU?@m?>G$dkvXXWP<#t3U{o(P z>LRwIfqy&dE1sl@`8GxLW$xg`RlMvG;AZ%BiKAYY^3-3U+Fo7-5v zbw1h_Es`&s$5SMOi$YN`XLO500pbT4(twiALH8bwQhlKw1v0-e(&CY|Vgxh+`8e?@ z2n1RQwJJ*x7*e~K*ImX`hlI@cxb*X#27^Ky$NCw3(;oRNbhkgNTZ{ZNN4wCS+r;v`2tlfs{)Ru6ZFFMGR z4r`mz08UV*Cv3S9dU9wHC;XB=Wk4cwSq(|zg3tWbTAYng zl*vslH1b6$U7s*|52U-qGnZ57VeV4MqfL|=GZqt>H;8b=Lz_tKNY-1Mem2UQb7^f( zp;yhHUpPEd!hQY~r!a`mJ%Q#+>P8q$k-x}F+DQ2BUgUG|fZZ%`O6`@wQN<68uTF45 zZng%Q*Gvj+`1yqzxu3=(p9id$+$)28KJrgjlD$=TKWhbX#fcKJBQkIl(;(tZDb!&I z-AuQ5=-|_qz|rsUx%#G*N$=;MQ;^G1q?s7w!N|wrlNm~FBk^F5+OeTs9SN?r$hYz~ z`aOTn30=ayBQXm@3t|*szkPckQ=l>&+(-HK#8Cj3Jo~KmGKAKDeogrT3yx+#xp~m) zDnhitU$4$5a`X`QON;=EA+S3lhMh$;a*w8k9Z?*))zz}pPz*sJ5F4F;ZGqrK<}I@l#b1Gu3(Hv$Hv67FWNrn$ z>>lwc(_&)seCrNMN#F4pBJ!=JKGd79EbX6fGyj@J=4l}hyfFt!U48W?x8Is{6C1MR z{Jct)ul}cy-)L22$X!E2$|rXFc!X|J6fRE)F?aPS(nId{8s>oVX1Z@@QkED*>_4(^ ziFjWuhFkgjLGw}rC6knJSv_R5b26-OBIcXUVzfL#+9z|9ZLP|Aiq%WFc3R{6MdZBc z>|B)XPn;D3E`G8}x!`^4Ru+=rxFDzW*8ovNMU`cCZ(~NsM110TgquzgA9bu4?#!1j zUu=MqPF|kshQwW)Pp#Oqa1)pTw|g2P2n9206428FUos-(Z;6L`$@=*qTC($*viHbm z=!nn`<2k6KZ*rtL|1VIFjG<0|D z19@mW>y20iG?TynTImqoePr>O43b6BR=%*X(8k?e z@Cl^{Gr04Kp}YsUke>g;Dk8yO+|px15Rx{h4F{TFYXT>P6OsFoQ1AGvBS`iWZ1t1* z(s0lR707v;i9Ah{64HQizG-_5Gsag{_|6%ou&Fc3?c4@+Nz-_?y)6fc?D|rX!ZsEu zL<5~QhHr}8?4CWYXrPDscA$Vq2&l8j>~ZoRZy;(6v7yMIf;1rrJI`my%wlf9qxBW) zqBNR0WV$7_byi5|$w4t=E4+Lns!8qg`Hd0k5>9|yg%f_+d6KT9%Wr-bM=8#`anMcj zkn7`utk>LM`QY3L7I+W!h}*YsPZdpm`J#vD6m~~p$5*WWkp3v6{)#}0OsO{!zY4gM z9NTVy5m61;gxE6l?}dklA8mp=5G?$n4^7Sp86t$}5Hl`EZ?{h1A85TnwLd4ra!2T; z#~Tz0UCqr}CjI4iPQ|ZC&)T&dT+Bd2$vRWWnF567siw?sZ&F?yABj(pWv}(ep)u7d z*_O^#7OeEY2W;1VXxFwh^6i_*SEcK}sECE&aq!77DbT5^L)t=#&>#|LJ}<2hX`bXE zARDKBp*qvd9exmoej7QftaMK|o#M4KQ-c>7Nd`Kx8UA}>H%kM*JbkF(+z zL-kY1uZ*sDa2+}t6fq=y$Ot{@7EJBndwZXSxJmuBd#u!DSd3`*K$TZVL0pWNrUc4A}6=BriU0?j85X~Z~-+mz-fSMS~BXWSjlg~9B_f18Y(Li6>ajXV<{gk@utK49G)tU=qE4( z!U<1YUNY=dd0kGu6A*y0$uTfeEPdWac-?NKsde^R&=-Z!8@TJ+5rt{Cq(^saPU=-sde+eHw6K4K1l9Jab?6?*#IyTGh zXSKE`N>Gpv?Q-g&nMR!k?L@1dY&3C&vPwTD3z<@)b+A;%0?kCFd02gYRvaLu`-#5< zz*UnX7)(UIGG%GxnmqORXwyy)xmAIZp}u?AL64WP84Ad5<$y+Wb@y-gXi!RsR;Tv# zUP=FF#cM`l_nhRXs|O>>;Igu)`^%f5Vvv#W=kW82Czy;`N_|R> z(x>$f3|Km%dh-;Mz%07i>A zs?;;_g@^s-o1eTnu&hEzOeF?arHj3g&WoW~#_%Dab!P{kjVVOtVmLFKbu3Q&UK?qt zln{!*ih^%m89lFtQ6{)_2E}Zlp_6$wg8RI4=q&jPs4Aa95dV{)UPW3G?=(s`ox{>z zyGV?1rX6y$)`yT(k>F~DB&`SO0_1oasH`M*lM;H1V1yRPl%;Xkc}JE@Lp!JmyqW0{ zd^~^gh-73s5}UbcI3J+GLZ*A+!lSU2v#p0+Zq=cgpCfo+qj+#;k;%);8^2B;^aco0 zlg>SUcWL(AEqX_(obm$H+CCd-K17We5hnWcZ}!*R3e)fXfwCy@xa&k_=xY4Q1-kc^ z9UNf|O)yICL`5&!&O5XNkH8o-dyPIeRcz5ts7;oO#FGHh;XL99BMN2BfE=Rh(6zJ5 zmzCDxB8Gxw?X@Xwy4X|&;kVS-!@CRONFmCbi4fCB!H6!pJTjvl0x!h`SvQ^92=>Ph zDd!Y)YO%iB+&erjojygzoFIBj_`~oEs-wUWd^GrLN2D$#IoXo1ixGyl_;Y4+{&;;{ zJdWNxW#Ur1DUdFY6b$CTzNO23RRop|d?C!4mF1@obZr$Ohc$q@Rd-)^rbN?fsXyrS zK9U#yOr*28nR3c~qbWVG#jJ1-#KTMxkS~GQVTyjdd)e`ZHao~W@N}RF_tJv=wApi; zPW>LWe62Qm?tXHK3tl4aL~_la<`S+gQ7FVQBAPMUb}Yp|C&Kq1#K$ja{;-0tKnt}@ zE?QIC*N)ycon25kxtH5uP3?#mU|a#1rZNa85kpT#yb@JkQK#>4*N^GK7_A{dqU;@y{Y_(aP zhZf{_ix^byoKECIkT!s$Lbplh(cPX$tusGU#gVv|9okP6$AIngyHbG^xQa~KtB@KE z{oU%7?-vVRy>|^c(+x&5)yc?!5?_sYimiVTz0EFgeYa(1UL+PKrxJil4EeDaQE%S7 zvCNTnewrLLhs3?ubGEk5*gf6c+P%;_`D zR|jp>A*K?0rxQvWk?{AK+lp_fCZ)&sJg4|L9=^L3eA6o+Ohk@TBG6X-Mbh{0tKW1P zJfYqv?R-{G7cSQPgB0w~|7b1C`majsr)o!GqJE}fWncMx`)*Ok+SLdjR(;4Z@MSg! zF5O1e)+WY>pu)lm{pC14ce&2;A71hGtas0@_5(V#orRr_XYk}{<2jDpj}Pga$``rg z7i}lz|CZ15t?9nzY}af>6W$z~?>1jnWOd?Mu@Of1gZ* zzw~M>QeB)CehUZ2_jpwQxZ2*f@|sWAz<4R*-f7vg&%W98p<(~-@$NTEg#d{?*o?a^ z=;OLp&6{2I|GrDY^pg+t?gEXbJVblCAecTi_2kS%sBrbm)r_*)m-^~g# zcYM#mm&@vs$l`6;RX+kdPg4lp6SB1G{&?3sm}fToy0NsMromuZw}wDUF4DsYxMam zsz1H){CH24>%GMHhr2Aa79nrph+|E8L8i(eiM57Iftba`#eMhYINr^jJL7$gzndQi z?SGqnZ~Rk+gVtI|&qNBR^p%TuhMJ-}n=c44NPv6Am=L3Y^}Mv#eOxw=G$Ut;`#g8+ z(r0pEjlyIfgbeGMJeQ)JM(@4xbA1gRh}y)tLOZ*Dbi{ zi`^-_Uc$knq?j{l^@%71cXfBow{~aw2Y&vq=}F)BZ?nsN^1s`=;*Vzm&2bpxQ&As6 z9zx`2qA?Glg>T;Uc{j`BKpexIbxyjGYJ_#ZtnMU zm)MdXGXEC4mapbfH}G`xy7$Wu28T(%ygNVFmHRK0j&m1O)v;y8^~;RM4elTJK2($m zdlEnne|gmQb+5DSX{vB=Ju;} z=8>m*?zOq{%%@HxqJ8IO+-3)Uj!a!{zy6C;^Zf0Ud%>IBfByR{mtT2*Gq-tn+Bx*g z4~Epq&k+>ge>$GF&`+rEtxL%w-RSq^MZ77rt~+Y3RW+6cZnilZkC;0j50?aywVc1^ zJ+cHR^c`L*N;H!7o9&5uI`lVMq!X9JI%3i9sD5>w{_rK;lXLT;CV3(wrQxq491p0% zwm;{qEfmVX&%2Z76aR_5H1Std;Ur(Ez+jr{)cv7Ol3SAI;<*>O4PIx7pS@m1?KhmQ zL|cHjpZ4GFt8|T@udqH&yc)&cm>TLmD4Q7_4!{1yu5}|wztHQlx#N{J@{{)G9)Bi!?%1B7%(&rh)+aexk*@DbBh}tKqA^UX<3kT>fEa`{#GQDJrY+ zGTQ-lev3=f%UD^ptM6qt;)i?rE-r|z=h@wHw!8LWrSWeJ>2?3w_D}(_UGv=Z?@F<} zAXB*cFop8fxQc&XG0Met;_~8Qu0+x$ixC#-Gwb;p>&;q$yWbLWPdaPETQr;Atf?E{ zK_+j$zAk<_uySCH=Klh26OrsO-*H(u8Yuq!+W%WyX!l-g_N}_uGkU6iEO2c$9WeVg4K z_s|)?o2D6oRl~INBHPaDq6OpqQx_8LJlFrb!%J$voi(4~H|JjRGCx7$9Ik18w3B&= zpTlU#;v2uFhtQe<`kF%!}inD z{f=>`FMsGRtO(s|D~vRHTl|Ly&$^S^!5IJQscQwtLerLzr#Z09Y>gF{&7E2s30SU; z_Y_<#st(rMiz2ff#gW$H`k4>LyPOH5?I;Piq0gD>bBw#IXFN`Ccrhag>O8akhlb9% z*YbT&lZg)h;^Q3_TVYi2`6dZcqXI&P1&ojjpnq8%05JUWkUAAmiDE!%L=PC_k>nlQ z%+3#Ev5d3svWfsSzQ3mB(aMjz{_&IEvCmhqp73OFaMmZ?@5~yXxqf6t!+%N>7oljN zi2Irm54;T!x?hxzB=QU_W}BcMa&gU{uWj%wLtfz$^qLVX<3B&WDT0K|;8c5Q>i%v= zS%2@HP5&c4%rG4qKH4$-@trr1(>~jG$68-u)2Dy9v^PFScGTZXU$uU5ia8JQsT2#V zzy8POvQ;+?Bz^qwTzx(;-csqxtJJQ;jN;o!?bPj1M`G3a_47gX>`zDSTRt0W%`U1-T^f;O^F5LI9A|MuvaJJt_?sM9T%h$F%Rz4LR>ggT(V|&NQ#}&iV zUGvcYE({#Hdv3)&oy7%}jojlct$+Q_;R0JxVs=1@vzj+FD|EjCYf<9+_(P+DT_m@o zytZrfvtx|c9t?!WEhfs$h?uA~5(_%Az0YMYjeL0wU)&-&Z6ygq%NS?Vh`-0rzCPLe z?i%KgeVItWW7;~(HArEVL42p+*Lqub>ZLOzvoU(9PEg#v+zdnl^<^CfbIk@J)wC zt`*w7HSGUGk(ud0WW3_WD-+Ea{-m*3#t_!GRerUeG5FDcz4*h^{gW4#poqZ4O#eWt z0BGO7cx4FX;Gb07@=cp!t zy=>xm@lQhXFK+02s|%hM52opiS2BnDdm~ zLzSBg}?%9JU3hU^2Sk>#QzhJkx+E5*UPrBbZ+Eli(xWav7 zY~ExtTg;40$R3vQJ{2#zQ&+lVX@RYHi1Chnf$(_cCtd$^bXDVjU5N@1-TZR&7uSDt zm}#T8qNru@RB*8SgZ96_71ai)DVhJ-qUt|)_zGvuefRUHCx2$blVx(WsidH~fPG$D zvS@Ho%{SKyeea7ez8HT*?L6A~(|9|pi|2i|uBImNVtR_uIb!dei$&UpWE&p+(9TxA|w`SuG< zS`+8{!e@hnPk;WGt!pU&zy#F=zcjT8eSh)x4IjU=fAZqB>XP|a7|)L$ zn7mXq7Pz_=)d#q5?#46z@Yob%R2$H~%(14*-A&7`j-NRZiUbx@GEq~qa4{=N&i4Oy zxzk?yAtRi8Ogo0o^u1rxcAn|`b=o=n>xaMQdgsT#iJwEO zXY^QM<$p}vs~^8od@33;TYQT~${B~bsBHe|*@2V3DqB%FYddOcYN)^6abw!&&rffn zf{`i8gq43GuPi2+GHv|i=Z~}QEyN0o$fm*%4997pMUdd_s@MSpzz zY}&Mmdj0j+sp+25HK9@G1y|kJpuHqeZ*hiaMFQ5I@PxD5-&-sH8bPp#FV|^Yk_XF%{<8W*|oK`D2dLT+EzZ; z{rH$)b>EtL&n=zj`~T2&ed5e4G=Ei&*}tDT=G6wzvgz5rJ6D-a7Asm5X!Y!eKl%LH zd&P_)YAY?M>6lk_?`WajSMpiU&wGmse2J-qY8$2&wuFY>rZ^f zjpKN0?{0o5-p2jcndxXwuet z{dh&v8`L^q2#eVkLVtA_m_K$?TK50y+3V6uLfm+C_2V=Z zP>EUqz?Yz?L?$wb13)0`8X~EJgAb@B0szubq!J+TC6N7ZEcnC$E5`Ivj@fAN4Ico; zLyyA%npFY>09;WjdcvK9&lL*0cI~2Y1=Z^i;*IMWzTpEoNq>zT;y8?%3skk!Y&KH> zS%3gg5Usp_t^qTW*GXa?1Oi3PcJJQJ`#ih7B8JKdzCDKdIio6f%heGeivlKr!?b1&}NN_JT@Hz?}(DSy{=u zPf-!?bazQuEEZnlPeam~A(1m`VD#~SsYsa_1W^D$Y2pH4FRWQ~+eGV%aFBzpg7 zx?r~Hgh~NX1LaVn4+RkbD2b#909=^-@Ol42j6WE8@qcVKVCY{7vPBJWMC%#=YC23H z0`NmG!vJ{;0JtE9!)N@JHugpf|HF!lfKuQP9R(4fJSZ7A(}F;HMOU)`a72kQf8YkJ z_m7M}_c0X`gu#loRkPfOoD0eM@^*ZtF^(q2z>$8j^0$>P$gIrUd!1Grgsm4FwSAYBBI(qHL03ZUBvKq_JI{E3Ei<1Yl53J8dWR1rWE=B(rr0i;F& z3?TrFVEk!XzhnPsxm+WF+iI2o8IUFdNbRfi7zq$80QL|%{VxojKk;Ph+;AKRMhR-D zgHv+JX8<6TY{s92a@2Yg0lJO=;A;HQO@Hv(Yp=;x$6l(UMu~u8QS)Za^!;!X212H& z0m69et+%Kro_K-+gh4_SfYwQrGLr}(aTq|i0^sSVpH6!H{qKKY*5l*i`O~!ir~Qd# zFUAprKp;RlolXh>0c7i>c+w${4Y0roh6#;Dx~5d z*Z9*AkpW~Tld;bQpQzotmz-ol5>Pu5AT=SH{r%Z~O1DD*xB>=(9MEr-shAHd4CMCw zVJe_;*}s2(;<-}&M5-uIS`ZkazkeffIDoC=lT`qK_m3|$2nkJLg@M?z(%F8uEnBwa z+y~GHTw^aMC|dB?V~+{mU&$ze7T2*7<$#5tmIy#LS6a2R?DYDt{6e zg97jYjD0$^4h3YYV`&sYK!3__nIr(f_~T3J1tm1>FE`od85>O$h)n=AIwTlZ@HJhe&K}|sOO%0F5~}UDxmVSWy5rNPVm^0v2FU8r0zmK}x60cY{YF5z)s%h65Q89N`-m1b=le9#jXPhI|6q z*pIna55Kj12=Ry)ELcG41*uU0T^9mMEeFhpP~$Il-)uBBHA#B^dJI>=%LJ?LYinz% zZQHg{dIdUhJlMY<5?4tD88W^ofZWT0pE+|zS0X?td=Xk*3xHh6Wzfaa(a{lq%x6s0 zL&LqHIe!oZFrWY+(fFr&|6a-72G>WZVdn>+N#%?0J#=wp$%XdX)ykV0A1^7aUz^ClmIZA@y|r6D3DVS$YK0( z28IEY0}LaeHb##E7)Ah)X#9OXpDLZATu}n9@rTYF5Q>qm6&mgf03!?`0G=dt%wNmS zLaZpr=J^|C1}lJ2jBEtB>$My0JgEJBArVj*Mvemf82)P|(?9>}m9K&~4cCV4*(94r-iNdkpHc5;;eRbB)2WoDEr&Z#q`BCXPG?nI$)uEA?4lIg$)#4WNu^eoG)<(mY$>u* zJU_tq_jODl?1^{65w~GW_+=ti6YHDe zGou`zv&3DrL~0`0Q7PhxxLB{j(u`P z{0{yO@T-T|i)HK|)|x!Ty%PLxL!^wbcib25{}Min|4zncpWIcE_z`}FYXzIw#|;<% zjkV0M)E9l+6S5KFE00oHxY7{U^Q8M>Uu=9Yn-*9bu5kkQ5KHncJm+^^DUS*%4{d3E zt0J^~O%pc9As9-(kp%?FEVqqOlj6H>%EuLPG z$-FPlNlR+YhIFs?<=A&t<-vbEz+vRF^rubfy+46$;`47pRz8HB;tYbb_%)sd=0O^3 zR2WO9`*R7P8}2d4=5^=;*39Ra{Vo+J6uKAY0y0|!RzJXG>vR}kR1!Ul|%o^sqr!HG1$Qa z>?59`@~kJ>0rUd<**sO1_C@T$&4IY<1@yEKx9-A^q&VL|BK)1l*z2?O1({@!)!+p@ z4|I*T(?4d5kF%J*kNZR3f+a`hU*a?8pwpHkQ6Fb$ThjQJC(#Y)&=Jn+FIy7sCo=jZ zmZk63B%P-+-$|qxU|)|{Wcu!0Y^=ZFqhY-03uu1PWiN z1!|oS_Iq&fby=vnGM!AM;=$3wc_dLH9XEiJ#`mx=SSg`Ymaq_x=Sxn@&NT+UOlP<` zer+5%WC&l#@$WoOaV;2h00RdHn?H5*Pf-9KxKJ=)(ZS>AHVh-3F+oO-`&kxEJT@aU zpn!mZfTFAydKhzTl8wW+Vf3zn7{J5ExgfK=}{w_ZTY!0N~Fv2!%kg%DFM}{z*qc5skcE_oVTPFX;qA z4?sc(?zk3|C08IqKr!!y>MUtUeK^d~hne)FP&_ID9OLYErUE^_mMfs35sOE7&Id5| zFmgGBnINTd)|cq>T$)#^634LaaBTZO9*c!*(m>FuX4to28G?Buk?L6w$LvW0hh9Qy zJvbL)tMR)4c34a8SzoF+`*E|7z4s0!!!vq|5PUv2o=sM8un?^38DHwB0-1aoN&9P6 z?6W6+%aemiPg*ZQQn4o!$U*fO0F38K?QBhUf6|lSQXrczF3a@RSbEn&X}?*K`4b4G zi~U})^nAI_GB(CC+e#!H8o=P6v1PP7hQY(SpMmi_2S7890Sro6da)t>t&x-i$ZHNT za}|nr7GU$MsTAATi<7p@w?pwaD>$D-JSqe3YX_DLZ%n28=&l0UHk96P0`Pnl$|9U# z|Jq1$>|uORO1zuEsLb^IcR$=!KuLM4zvf5^gBzTLlRDu_W3?q^GnM|0k<{GCS zbE8w=g^oeyTq~Cp&P$yzoZqNFkrd99x;7gnaK13U$Dw!Y(7PT0=Ry^vqa)3=ij1an z={*=q=MP{AA4M>X_$&}}ahSkSVqd;tNd#vSP}i^?e-#FJ!IRO$SQ=*ofMj2~qmeXT zuE0jvGCT^!S%X6WaPYjA+DnaQdp!5U=pR-z8t4qQ3>FAy~7f9CZSxX^aC&IbUJ*iQoYwO9^+6~mt3 z`jGP&azG;<00<9m&j4(51!6LVZRO`1Bt4lry>wvKc=)I%F~D3EgeCI;1OW1iq?Cof z2^7FaS1oBm2MQ1Wh7A) z!hsec?8G9-DgaFX%Yo$Js5n|l?_nu+rq4aznoE3S2u>}BzW{j)U~`jw=qP{yfxX*; zL&p968gvA{hI`Kom23FSXBl|_p1YLk2Z^MQOVy*pw?-1<`!23^_znmk)F}d46$A;_1Vj3<%{dLp#%{ay(g6-m2P){4fC)O#00j~ z95P0iyjZdn1_F|umBt}EaOPy?1&kaGT^t$2BkHtskZAlKhK;Nyh2yqTJV36o1+ae0 zpb300OX=JTWjy>JMh*%E8@MiiR${}jmMjB^a6OFzl=v=%x9kH5U_ z76{VxE&#<8`|X2;L|~HV0*w$95LTk+`F5gUBga0J8)S1pk*9HeIG_lu&BnEA7XvW+ zRC>RjD@(^}#KyU_Pu6AbBvO0chf^tJn#NK&FEaccj0c4iaxi} zV^3eTWPUx7!Tp&OWK_dg9{k<5vM+ntmgyHW05%^&?#RaPEx<_tguXS9(l*YuamkaT zpUl+QJ6L15gZtpjtsucVp#+(Uz8w5+2u9#Y^AB*7i7N}=0h6Ak-EdEP7xI*!SCG zX}-z`6xjSn31p9e3kSCdLyhY(@A)GkzqU&HvphM*_7_X zkyHU1Y!5nH1Bil-$Jb#XVHCr7D)mcq8FukA7PEc_`4F>(tiji)nor$56m7QUY2rcf1?7q1{i*fJ;gJ} zuoDHqe^|`q`afRRNLl42SN49>2cRc#G;F&6Rjwe3tQsE$=prjDRQFx0BCCH4*7b|A ztbEUxjI51%@J=i~?p1=7k~oxDZ};ajl@lMpY7UTJ^zd)&=jdu;q6N?iC&vl_qJraa zWFQ?j3}FR~vc6JNXAt#46prH`J{&8Yi_?SsgTr&%o-+45E~Ivuw#vt~A%pMEq=R$J zaj*6U7~|v!;16J>527|Rk*M(pk;K=qG=PQ**3f?|(r0U&uSf)EW5J%+R|2(t%9FvSDds-}q?W&oo83S^UZ0cS+lF~u5|{%ApxS*m0*hFz{a0|vhfojgj! zg3UUF-Y{ZGkTAu4(g)mg=vp1R4gg0!OP_B~0C2wnQu7{UXa+qXA=!W*Xu*cU4!MwB zt8dBTl?8e7^Pa5zVF$n}HoCCxtKV4v+6j5`;WmhmBefGX>3%lSy<;>h@oASU;z37_ zW&=2MM?qt;pX>AH8Wp7-0HFIF)=mfg9KamxzIO)X;<*4MgwhLtjBA7-0y z4-&r*vWCwPAjvWKz7Yu}u6Z)o2r6Zdkp$XpCu9U*Je77UkfTAWq52Y#r3FMj=mMFz zg)F-*8NiUA0Ly4C)MX6j$k4H++s2)PLP7y;4dGkH`5QuSKml%8X0|$TWf6)E3T62@ z?!}*JbRUl0_06F&S>Qk_av&9OY<2h=4=D*aT>Tk=0l(Kc(~#m^F2h?>XR!W!z`jkhNtj{%-+K91V$A!OrDDN47lADP*Fv>0z=kV8X1lac+f|ml}Q2~oNj6@D& z4P#<|6V8{>hElg3LX{~S%GZo>$Zi681A)&!g;uVZ^ac_5?Bhsm7$yhL*?cjO`f^?R zcP294n@J9o8{mGuUq>2tB{<6{PcR!%qN$(=Y?Prn7yG?@xg)!WJ+J|Q3R?%0|-P0kVmnM-;a%a zUtt)yl)+9U0pvRd&>j8;|6YdPCYc1)Ds&BJx<&$l`>rG!QLzAA6Wo`KDV^mSCQe`& zSO2mqW2V)x{^rFBWCQkR1}FUj&J?;g_yk}A`bY4b5b%QOmUR#XfIUVq+&Z5dhA=g0 zs-t&O1?k-L^jfU!jX-F6J(QFoR>*$5hrPBnPW5g)`N2RJElHl3H6 zQt$Zk=(j!G55~Pg51FdX3cH!w3bMui`(tIDjGj#2#{-QSC9~CzrjHq=fRhKo2={Vb zyWn$^-=_K<%0BJfIGsxEWJR4jBdbmuHk-DAd$JL!z36`w>KQlyr*i69sOSN_UJR@>p7qVZ+{!BBe0bGthj-~x(RhB9na&+rJ=GgZH>x=IdD%0U8 zlHqS7BY&G>fXURHcYFgjvO4) zNW*T=Zh>(Fhz>uWihCS307NGMQ5DFucMP9d1vt8$XnNKyKwN78C-7k*>`Vq-aG@ik zpG_5XSN{+;<~Gi?1EK?R$>{hH=gPDIN#X2$Jj;s!b!$(kdI$dbFU#&>L*M~DI<;3fMYYKs^RMExLD8uZ*Tn_FH)zJ=a0_i~s>7bda82ltTfRKC|YpAlL&w< z0;5ue0qeK1uhECOhWMF+30`(&3=}_!VVqA{8oJ_sOu&F?z#7lPSsnvxKZ<4Jo2!z5 zF^`)wX+MV#;eG=sj}HZH0PR*b;LO1AnkNETIJY1xi*5PhXJ1KexgzJEJ0*AC-jNsw zL8qDl>|}8!J{(6fPZSZSx0yq=RaW{x4mE|6Ytj!$7>2$AVia(VPV*wvvetADYi&hn_&mFAh6bS^K-?%M}aJ?n@^MvT2=2;K#WNw-)p zAyR)n&~yiB>f(i#j9^FRpM|pcVpCv$r9bLR`%fy892GJ<0HZzza`7`b1%P`w2AzOo z$ru?uAqsHGq-$4vdGgO)B`*F;cn;XVbRVDTl(O++8{pWHCqEv51-fd0)BBm!zwOK3 zkNa>;*nilAbPId4H<#tVXn-_KrNH@-5fW@ESz&|#z`=pT_u!0NkhkQ?T$yuL%e6j- z42pI#{ya4df7#^rL*}>VWVBd6z>x(w4q~@eSpM8FlB%j3!7N7mdwfJ;om>e6hd&qR@>fxx6rV-hqH)j z(+@<6p@6Vh29tpl;#SO|KkIAFghSGI9QR#OGC7nfj4#>y(gF&G}PMG<;twt1k&0N)u18NtOsO@VitQKMUqe)%9bTn$|Ipim~Bvrd9Vd{FujMv~Pq8I)`COE?|Y$>Cgw|2kD=%r`PQ_}7sF zQF|I|I8qiw3o27ahc%yWDT6!y+NSJZJp{H)W%@CA{@f@!Ww!acuf=A|ueD_FT`Iuk40+{>E5Vv0)&sDyH`>bB4nOTf z9x76YOg#B%SK8mG$`l5;1PDF-f2_&9|GX_FWMtuc4X|2`mwh-M zA8ghW3$omK5%Srt$dh+^(ta7bF@Xa(1A+lCn`0fxBTrU-A3q1knSYVX8f1bs8PZCX zDU+Nyxc4ZWN%ckM*zx?6L<=papJVkPGs%Nofzk9PbOd`y(v&kQTsl%)0AqwrRFJS< zYTjNxk|q{mdjR*zD3>bCRQuHm?801$gaCYGAVuS^ zOvIAuhXlmHh78j_hrOruS0Hw*2V(JV{bU6|-IkSCmgVt#TeA2Ei_%$Nlp1gs@I4?p77%3ZasZnGaGXLuAB<%6^}2)rMj>oBffY&b zGz}Gul^N^^_SdPFnwrXXMEzSCS~9#n!E?1F1PEY+m|)w+pUG#579b|DIZR?$$FZ02 zT{+1!!4`|CaW(|N-M6PI`%Fts??Fxh4hrn?;Flwu^LkA~Oi7N1!-lRbw-o8800Cs# zn;VNV8qcL(ham^U4@MKP4;o1*B8vm(sd%2JArYd1JU6*{htQr3g@t`Ax&a(;;lWe> z-X!|9h_AqPtfy?=(#n6_8_d(jHIpPUIYoTO2kcK%7!erfgtc__s)-D=ZA}*tuw)N& zr4%`k8I55!Dm9E%Rv<)7Q3NJmF90dC&w$DxuuKEc8Yj#xbIFt!u<8^{w10gn$r$%u zH7JxJU8iZ-e(hpa!{5W*5KNrK6@gS?H2aT7%4`@N$+4d3z$in4=S*PqrdP)*?8ApJ z=wPc13wxWm-(I1q0}5*nkincI4GICy;AUS749Dy~Xfkh59c#n58s7=R+maO&Iyewl-j~QcDK54Y?h+zU=0qQ#s;jp0>he)GrbPQ zkKil>Pz+An^6($#8opimR!b`5j_f?zk_8xu@eO!QIF15NIT?hSZ?)m%$3F)<0qgg{ zkn__((vRi<2${qKJUdub`h2FvPWhY;)t34*p6*GwlWOtN3X2f0KO6H<3ix)RBX;;pF z>y+GkcT0lvOo!mvz$|8mQ`!G%Upo|vRxU?>Hxvu9HQ5PePG$kadGBAgBnBh)@R<>0 zIo>xZyZ{a?>jLOphm(8(27D4r_cx)bfX5kzzbTZZw!o?*FdzRS9Nwv_hAam^p8;H& z^hxC^F%>+EcJM%_ znWNTD4A_V8fhll|ubUJV^*8#=gjfmA7}cX~@L7z;y!{O42f$T-6%M)K$zYHuSZ2hj zHyMK&!!h25{el6oX0FTtl12cw)NdA)J-L&J2i>ra3Ju+g4M+97FX?B6I*>&GC>Dn9 z!w|3kc}vFk;SiuNtUEY-k9GvQcnJpl-J#YUIHw&Myg!q8r_i*;$ev5`snL@mJ=rKV zY8SG`31DLF;)+anClXe%Pas%T0LQ^6iH7GJ7r=a@iB#Z7S)@(ni)WVFOV;eL#IL~T zCbv?J2xUxN0E{M#PB109pKJFO!}h@m5M0=Q8w7xtSq0&&)!^Z_!XuS(UMD2i~7k=B!C z2hf`x17?qo&m??1gROP|Bmw|n*z(ksg>!YB6TZKYD)^W`#^)E|7xwUd0Av!$$-^o1 zqR<*TMk7iRtYr%T3vvMe*S-KCIERb@Y%pbQRbeMPuBJt8oJIH`5_bXq2uP|50G@!b z)HgtUmI86>RbOWFNIEMmE&9tR4cP#~*n2XR84zOyzfXZIA3ofZ;{XHQGM09$BctI| z>eULIFc1iou2Kz@8MoRs83Hhrr3rJZL(f=`srCF4M?l{2I-@=|7T>8>0&PKLIFo>b zPCfwu@L93z;ba+EfZ`{N7U1uS=bMl_!G@*>3axU_vC%BrVLMp5muuBJ*^qn2))tsz z&(th<=OL_nPg`6dK-#XdM??P9kzR6D*m9sshCr(?x>`M1!WdG)E;wl9^2P|@J~6id_ijwi7sS3*TiWzT1Y-egU9zz!sJePBPJSfPV^Tk$EWy+Symm z$`fdBOhX9(!Rp$y2S<4h4wT_Fp#EYAMLp$7=M4a_84NE3@hFcqRX~Fj0Wg(pw_~1c zU}@O))L*X1!~gF9Wev$e?2bxK)zxhc&~OYU#)%F;JmGCUe;2v=}+t#1Yj!X{-T zEdU=EXKW9FbRlcZBRX(=$>TzE!0t)13b1MxFg{J0kg>?jj zzh9_xjTvcz!uRgONxeOg8tyxIi^k29*2RjfoL`cApKn9K*v1xV&bNNN3i-snSiOA7 zm*$zej6ND`J8U+}G~&|VpQv)PNJsOgjBY^2c7HNb=itBSX`!dvhJpcf#YslHFU||L z)}quPU*#nNW9I*C4f}a;XfqhT%4J8UAI4g>PO#=O8qhSLwe~ez3j`TzokH?Mk!%*%aO*LFsh(jgYBW}*QD|qkVuM{2}8Xf5~_^^x~wWvO}Gi09>|kmKKP|U^gg?nOZ@u@? zP@+$A%~gl@B4x}BM+;aWQ+{sMQaNJ#934gdYd9l-$$SCMyyG+*Fp2LK-B`Dy;b7ke;DeMOThqgb$9u)v2xkEzaN);g6!_WEv4nAxG3x;WbtU7Et zh)Dq;KkU!Ny)0V%%GxNa!-C#a*?;XNSLVOTv@O~KfSuf&Ylz*udw9PJTsSTUXoITgcbouPPOwWW- z!0uNgE!3{V)(r2?bZ?garY3WMcWV#Nlfgf>@b4+k0J>am<|fTuE49mlZQB`$TY9_D zsH;Q!yak&+O{50j6<^I1*cMf$zjC!#9(>;D)=Y*VUTzKs6jbV19M}$w(!xSRT0uig zn)li6fCmkrl#@v)2R+jkn3t|hXQ6Z!JLG8q1Pw6URGB;rQdo8DX%Szk#b;S&l$`B0 zHQ!gK&F%rRw%KIub$SSGkEE%3> zL6yH|YdazH+}0|TFvW)SHHIV(oK_8JaB`HwQJ2anQ#fuKD57W=zb7(f=rm{H4vaOO zwUxs#Gc*qb-T6jC=3wt3HdwX`O*d#oIMz0~4)n{MQG|8AXEvLH?}PpC|7@xSCQMOi zJ{N3W9w%C$(O3t%+z#OYTvnEWPHg)CT^MVql}Yvp_EVpTy=vXn}Du_7LrDp^|Zms73b zs9&l`_rHy_9-(OB`M$snSn%{5uKRwfX_WfQuCzB6B)-{`_Su?*N1oR=Pl{~cBrhEd&)@B9FEYd+1jpDLJ!Kv zMD(tvDRMYu7F{J%c958&wG03=xmTDz^i`8*58pBlqX4>RsGhYPtUDn9;T!;`W>R9V z1$llG;N(WEq4T7HKVX}#_6HZHJ?yS%D`#pY+D^s_ZtaSIF=oe1wNug5TQrRDdpz?_ zAI8X(&yXq62zzx`Tb}A?ePt0bYJ_+GdtYj=fOP|iO&-iO z+GE4mb+3&j2as#vcZXLeQiU<3F=A!4y9mQrjUemLzs*2;!?84%0NAe2GzZJjo}Je@^gFmE9k9RrkWaDbQp=?qq)|{ZGf@!j?s_VIkYc_`Gm7DeOeB z$nG0oYpaT2F4eOYS%94PKAC9&+|n}uE6EY|$kX`;{dYrYzQESmRO@Ss7uc2yJJj*D zO_Fx7bEPSLXDpoyb%|i7Sl}4IwltP1(g#EJ3tMN6Fe=H-D`5}fTht5e9qiMb#kudo zKC*U<1>Y>@hCDT%g)?%Y&t)QOFD=O!{$K)|WWlZo7%E#%ZO9(GL8#iagYr4{?9dcL z=MQpqLW~%dTbXtVu?`{M&rN!lKaWlQ76C9LhYp>(#ncgW&@;6N706=iQXmn?;^eKl zEWXr~8T?UtguUH>4>)GYqqp{;SEbG^n14QkUYhpn(c8JUS#yR+9y4NT+5xMu!HqSN z*O4@i;a)S4t990%MVba_1IV#u(1n~eFVv(rm})C!3}VQZ!p84cBzzc4WzE&LK3=!I z3R0ax4(pXTsr__B5w`P*tCb~TQG?w^NlhWdxj`$XQ;JKh7)grD)FaS`*5%<(b}!dLYY(8( zt+yL6&M*>Ss;y34hxgD?T)MC!0WQ9?b)W{J3J{P%;KxxW9c%(S0S4o-EH5pIAGq3& zPpUGV#1gO$1uVk{yDOQe$MxyN>F_H)44iAK{WV+<$JuVxbgTewt)+pi;LagIT0>Z? zXy1Rc5@=q(G=TyI8${#Pg;2)vTtj4R;fZ!(z(%FACU%vx#YbqInKOrgutkkk{xnW> zg4)ZUnw&L5E7Ok>%@P!vx5~9hkd^1GVt0dF`dGV5d<~93%1Fj9ZcsOhEhEp z01)I_@W#Gk4!cNqGF2E>j7D$}EG{8auK)-wya+^kJ(7hNY8nnt4pSLyj%56QKW$g) zo1C2wg=7mP_b^y71w0mj1#?dfAE!XTZ1JHoGk-V##5jGTQUbVy(VBrluVVl+hNGF! z4;v8htE{AVw6M^B1}tG50ae4vKkv)Ri*0#u>kx(m#;j{18WgG-4sQ7CLj?{T7DCHX zd()TBdPDaAX&{x?us?CCYwz6}>AJOm^DGp%>q*JhBgjmJ?N0MpL&@b7;P?jFpCvvV z98q@xnQDF;%sU8V0YLcCWJj4W`{1W+>4YH&fVztyF)0y-@8NjZnrcDuA{fBt%g_nV z&A2-kXQ1P)IEn^}T))5S$5yWP!-rm$v0=~5@!O#Tz{sLH z0Eif}_6XN~wFbvRJ%{u98kjQ}@oZ-fQ0oG)Wpen_sn$lM>!}R?lc_x_D!GIJ>DC_Z zahZjuaLW5|@*H*shWg~6`kJfm((!GXmP4j&4}UmSu+w-QpymK_7Q^v@4OZJ#aZh?O z*cxepVG27@(q^_T$@&0*QxLB8T;|ZR=o-@!wjA7o0e}OyL6A5csj>`GcpC(dEqMDW zbhwn&*E^C=TXE2>k<=Ba1T68n)9Y~|y zloOlFnwP(K_pv(nYP~AaB$4gyeXwyD9p97XPDjJ`<E}s?+I!p}!^{00F zG;{=}*EIu2SRkTJpK$onvdkpHz%sq+xhdo)!i}DYqInKlVJ|3i_GGHev5?sm2f69> zC9@~%I|7Q=u#j=!^Q)=M9mBjvKyia>b7gzHrVSLV4B0nGr97=U6jLzhcN13Ya5=lo^Jwe$1=#k zfR;_fhZWOg&}5voV{pm`J=yr%mQIk2<3#pv^tH{8VNEhM7AyMau`MjZx?|d5W>D7g z-{YXL@vW~nOpfpjn9-FA6d6Xk7V8?BpD&nuWuf3lJxvXG0HqmVuywqYV7(%fUrn_Q zFoWW<`dZ+eYcD(Mq!?0NezPV0TN52XLc_oU!D!c1rI$eYTv!6Jte68tSe)gb3QUd1n4JY+T<3fl0jPBTs3rlMLv~wbgLDXK~~$hOaZ7wZZCi*21K;6ELnZTT2@N{x?UaLx+2F!c1_a z60ydCvoUC{@N${K9(7)?NaJix_I^E(r9Y|3oO42wQuTlx9^u`onxx^wnZ({Qk*u#T$yqR* zz;oo*t%s7uu~zz*_)fC{BoQX^=<$J^zi>hgD2zt?2= zOE@YBj<@Mc>qJB1jk!#($Kt~Iu>~rAx=%jyyQ5SQoj{38jdVmZ9TJ(BrVy`ghO*S3 zb#)pVC-kvch^;XU&v9KWgp9$!gy|CezyeU2zYAw@$&$ra+7Ozl^gf0&TP$_FsI_VK zltp)oueZg!DAIpx4%BSRd9y=HUP|&Axb!iW~$u- z0tY>QGy>~`V_gk&%r7G;)t3OYj@990`euAGYXS~pDNo`y@66SRd(T-w>*iUS_oF%=FhT<-G(tfrowd0}3J!o@ z?<1@WYa|mp{0*E36&42m@Pmmu5-KL;n@oku$VNy0z3~Uja`fx5RxLC9IUR<0cF6fW zh4IF@kkPSrWeqa(3Y_ozLy@&YhXv&Tfy_H{0$uYtuqhaZ+%2@glG6u00KpW@k*!q4 z9J5X-XKh89T#F`h5MxKJLJ4>!~o~=l>4PtRPlg?iPu=zN*4`(_Vj>fKp zQxET8?Ti?0!9lVj9bkljB{+|>fdg3iW>bznok+mSt_6ms3w2eMl2ux`7kGa;fOdA=1nrO`ii}l8P#yGO2jORq+LSQn3EVFXmKgn4g zbj$#amjG<2Qx8+AK3@?#x3qQBs#Yb0eiJmX0)3j#<>4u;7d zLq{Qx>CsFA*tg`%ObR-@Ra=M8a3nM3Vs@@7gFWa+)z&<8I>>bR8t2Bu_e)J9$W%14 zNBhPBW^Bb|*Fjolrrn#)iz(x~nc8>OL2ytPS-Ix*A?G$&Gbf1>*fr1BWlE4Zv<>;_ zMF4WX%&w=}3FJ*YP35xsJ>*o;)1qBwM?X&iW^wHqu01w`yBH11?-uGCNEBFr8^v(C zaM-Qm4QVd5<>9X$%ffd#e$`RJvHFKgGQ2s~g2UFIEy&>)16ep8NOx~!>drV)ov9p> zj0)s>9LnO$Ef8UV*Sj%5RUj1{_~_n1D$fE0JqWe1oN;;W0N50b>N503=gwkfI`#;p zu6D+g@n^YC^y3T#mJbC0APXQoGst1q;2xGFNI^-5g^R58gR?I!0G?1Z6hLRk(Ri&II5X%k&V>G$MEc(u5OXsBZML zSiuEnSts2@ui>QZS$DOVgLA!U2!@}|7146?Q|*!?`)_q6=OB_Za7+(kEz}|NWAPJv zrWL5*a`D8>hGrjV^Tmp?mEek**{y9-06T|2nCNrYj*%Uf8am>;19I$v4TtD+G;+qm zwn9@O3m0oL_t>{rA{&1gXe~m3>-Pu_;GjYPJdWng0FuV<;V@H6^PI&DhAwk;59=@c zQs1aawphsU-b5PDcrpW<4^Eib06Yups|+C0RsdV>;~7qQFpQaoU|Dya_Vpog`!KtdSXD%kbxMhqu11i(tv$=J}dTcylhZ$5_t`c;-7ZO@VPf{`QX#wAY%3DLjZYXUOx8 zw@S?`vbUV$elw;4U|^rz-q*bC^a3=mzrNso3YG3GDiQIzgm^O z+arm=YFp3ZnI1+Oif8y&tJYyJx<8re9EH7KLRlI(^NUa4H?hF?X?U6w_7_}DJy!5J z%2pM7Mtcx%ms&_E9qLAI(j_!ETR0aMYC=C;>}%oXS~tLfEEY%&2mZ9z>zZ5TI}2#O zyvty#05qKNHvcTuSrqX&k?3o$nOTngsn&rT$Nm?+q3y$&#EWitIZ zXCW8%u_tq|XV#kp&sa)y2xe-WCi^1? z!?bboIVSaYmT2=Uf}@3^^#BBdD{s`?0Rc2f(C{2r^G+QPf`!AUmE!`*W#<%*}9cRRr(= z`M~-2J{`(I*w?9?(-DC2N4ch6hr>P?cBJHC98R?hV750?FfsiN_Lpsq#{o1M?msl( z?&yQQI&;bd$1ZcI8tX{XJ1hbh?P%iUR{8^`nOxXro#@B+>@0iQMqRWD?X+Y2I>}Rx z{ieNaT=1p$_FM})oi(QIOC@{FlL6LJXtdH9kq@G;9^yTc2^n*~8hgb^ucB zS{i~kFZ7|M9QJ!st5sxubx|Hacp$aTBG`EvdsiQOCtti;J9Ny ztr0Nq3gARPn;@bzIXo6_Y3RRVUSh$#w<5hJU({fX0yIz;!!R3%Ys4di1Q|wS-}&$t zGtJF%vp5zv#6Ju`CG=Ll9j*K`m{0HntPJ}XHi=u|Y1%+@c4 zXX`+kaX(c98Qx5^&5+Rn7U5+&5>s^MIYZsWE*SB}ip1U6tUodZYbn^`;<>s^`jK?s z;WRZo$_t*>LCih}ldhNvhMYI#9ClSg*pB5syv+T%duCN>M!lxY|JFF#7|xFGwrE=#7K|bE_kOSgXIAQU zd>FuB1$zp>+qmLriXej{oF9OxUxU-zeA-#UQ0gvUz?ABAGLAiFj=r2mvi`>{4c*Tx zaInXH>?PAHg^sUvJVz!;tl(?`IyAZ!avSB?*O#SxIFamRE^AARviC(_DscR)xtY9| zCDpPuUm5b|7^+V<51`N*DkoSaJD4}AM|xbs|}DEUjwTfn>JklW09jr#Q2~+^hB_C%1J?W~*YK@Z@pL$g^L&B*OJ(u-7M`&R1~!)ZSy$hKGO_1i zptcr`S-B@}#gXoNV>1o#6pZ>;xyeaGe`1^k0VUIX#V~(boeppY#$0}3Mw#>741Ceg ze338k^7I`E0)yUuz9RkK&CJVmPGC=WK>}`zf{*U+`l@3Li*xL8LQnv}7(Oxm`h3An z03?!e9W6ZD$kBFf1uj4bHgw3XWI8a0HZ_CO2ykysF)bctrZ}11MgRj$b?VTtCu}8U zY7zIj4dx8i!TBhSQ-R9n433^Ht?ou4qxWKML-bf@DEWgo#O^;<-#L{>^P*9g>8=b9SKV)HU0OC;tqyH>a z8|bdV1>!t8T+LNK0y{r0HAPxr-=tS+y-Wc>5p`3I;<6CDaRy|$8!1^)I~zg|o2DCq zL(9@yp%Y43#Hz_^*kQ7Hw)RTR*4i>nJ!G+j`-aT5U#rRZqqz=x@=n5Tu#Vyc&f-#_ z`Tm5dZ^BNx&ULnzr~0xb0zQF#qU{QUR3nS*AYn(0J>j6cr=T}5?~~xLD86@qAcgC7 z7uq%L*$r$uS133bI)}2%=Wqf*TPYARU)~c*~K5`H|xzheiz7~-nSh05dgmD>(444cJ$)~LeoFtF zp)6YmNB`S^Ayd@Ek%(Hr#C+$nc~J%*nC&=ZopJ;e4`axwX&l|h7tMc%naM9Dp=HKcK|GEXIvOoFkZHbKzKQ!k9q$jf@6lH6c@n6?q|A}EZFLQ z3dg@{dTiM?$TwkG5Rl?=Y5IL{7TSeSKU>#X4g`Yj=R7$8$}g)hV4w1>SC$&a{ZEb* z5C(86_pv_qhZ7u?MOU(4!JskRM&Z1cD6eTttlL`!L0^`ggK3264 z-Z3LhSxH-tOfjoYVCY!A1!D zShdS(b$k&DSuO`{v6;4Z$=AYEzOW_f;+zl6ybHenq2TydtY`V+qI9nfW%;E=8D1Mp z3d1-DfS6v*G$oPTH9h^EuT^FEQ7rA(J#EL0*IjLioZO!&nB=(K{a=g~)U)`p|J$)v z?N?v4Wp+2!LOssw=bP9Lzdu$0?m&(neE(1f1+D)3w)DYF6WEk002`;GExz8ABb;${ z)x69r+YZHoBj5j|tA@UG63LfR8U1vw(WKfH0x;2HGe#*Gc}W1OywhOoZT5Q?Iweov znedfqUF_ePN`69MA!z&Y^kf7J^B>I<&EWo?j#QU^fK2C)`~)?W8z{z0q`)jjSgNM!pWo#fyK z&MPxRqvkN+9E)9n&EdCvyDFMTzW4`keOV+*yb3x$Jcu-cNP`;z2(l+8w}_#DXob0M+%2rVU%i#N*l)-}%9}e=g(-3>#MfEU(R_1^Ho>d-R}` zMZO0?BL)@e_A@CL%)3BXAfRtl1gQycnt^Q^jp5r3Q_)o6{8p;P){G8RI{;KnfeQh? ztKs{i4SiY$5Sc)(CwyU=t7m63Y78d9v~O~sFJ!Z2g`>8AFwjV)e+9(#kdf$2i~TqO zoat?ExiFDswr+!&)lXG5+FD-%u%7^Y0!T1&5JKC}wb_D%yB-GKUmXu21$ zwyoSmtzeI89rH4!qKb zVDCOrVDD%krlsFF_d=e&7$`Tzbc{H-Iuh8tP|#6aV=9!juupqLGBd8xyzEN*|M~I* zevZQqv}KU6fq((%hgG+bZtbZi!Y#&T^li8gApm=17~ie%1vr-WA2R~r(xEh-zW$ek zLsm@Bbxe;M=9AaLFz6 zqKwh~Q1>yw0duHCX6IV0l|*2GU@FmB{zhiZoQ8Z~`31A0FeCY^klwRiETw>_w197?iD((K}Q?I$?sq7nL;aquSO>Q&to$O zyCXW>X$r?}9|O?&o-a-WhxJt~^~-^@{JL!Y8bV*UW&CS6sV_^-mmU1eMA=U8tP3O! z;Ph}Vm1nRgJDGXy>ok+-1M}9p2^?Rg?djCS%FEb07~~-lon>hbz4w)oUq)fdZlWRP z)>)k&Y*K{NtRA`Fr2A||;u)CO z)sd9=Y_|tiIWqGCA~^r*DHd1S=5^VS#PRh=BObH81dcH_19R3zd)s+NHm7BBx*rP@ z89iW%kszmQ>$qiphR&_rhtAba-+JzYv2mad-+^&BmD$hX=opz}9`<1_yPpkl1_5L} z)|wPXMiu~OVAen`rjP^Ew#%u9EOLq=Kj|&gVUTfd0beG=wD4uf{~jk}g2{i|)gfj> z=w;=yudgF6l0;{GFpB8fo z9?VS+8esD1?Xl+jYd|{Pe;i4+WSIH**3`^{XHCufu?cIp&FEg8pfF8~l zApT5MhbB#iGwmIp{0cUpOr-PI4cYp|p&1Sahc~+w!48}BpToL^?LyCI&etdJJS{>z zk(<;YUoXtzGq5w0omk((#}VGFMrZ0K{3?+V_MC%>_~t>{?1(8MR<^_D7pF?q$qJn7 z3^H!f24ZgrYH}8n!l6gE3o}Ci0A>hV)&F>=5zO!iLeJNx10=KNgCTf=ABOTP9kAt3 zW;$0xTOlE9j7;$zE+cCq8{g{4{;h!|k5Vn@<=l}JXV-kDt*^6Y`iw=1=@bxYe}OBR6gO_d-!9E4?`&Sc666<-PlyWG1w)wkU?FV{8lz(_e|>HjC{ zy}BejuPeb5_J)u2Sy>hZltH230XhJrC`#l|>~4*EP507T^Dw>Eyv`q)$9bKlhw0Xu z(MW7bBqb4aAPDbmd0&}TnUywt#Et9X-r0M9DNT2)1yjh3xcB?cIs2S__AZi5i4~ znas)fANmuk*Q+|Q1(^n!3WxMxXZx&VNzEtfN1}J!0sirVR=ZeyB$=xaRHU$LkfRO+ z9uNWrRox^Gyj>9Uzj&nJ2o{{10kv^9MD?|Vd-)tiv{R`;vCYL5n!@qCeCR3F8NVO8 zRYSg%b2-huRSg3{Qt681DYAIEb5H5r;Tu!6CE3<3pGak+s>TM31qAX(za8Oo0YyLO z;WAY4GVo`RY|s3~3822I4Tl5Ce?IoZiGYZY9Q)89W~H{QNy-Wm>V-P0<{1j&k? zH`}vWbY&~Z8uC7bmBt})f_xEFw znP@O#KpOSqWt%@fZ`*ec#5gCfr2jUGntDzOU-3f}a8G*@bs~Y090ihvCN!*xQ&s1b z5@xXHvt!rAzZ#1v%zga+!;W>Xb#&GyC?L3sCR3my0@?2)O^fz!4J<(5E{3l4PlQq` zq>@)H;H=G`YuFIqQ)JW22p~YPgZG@icY>;)j&RhgDAhTx!nyj|c@j`Hk1hH33p zWJ`axX!~y+Xbq0M2L`F)sxvPk`o&8?l9_2P-8faYZU^`EOjXjSX(N+)&EiW9d-VIE zEqxmS1Ob0APOb67GR{`j#@7PvXoo|Il3MNv)z1)Yj1B?prtGefV@7keu`BL}Z%_4Z*jgq+SBychq|@NEgR?b?fQgG)1Q z4X|)Zop7oE7bSS}{X=>7ek!3J@+W95&+cT7w##j5l;A<^Ol;wg>sognY@)#bMoFuI zMHCG*oRb&Bk(|028KtYKJW8N?E7kXr`fMSz5x@?~gq@v8ox<|Pyw)+9o0^E>6|bTq zmZDrUDlmFZ*lzYHl_rS_BRc;jw6y1HcBU}~#qfN(;XaDhG44MYIgSDqSRX`^iIHvt$Y`xyL_4g;%Txg?uD_bxPwXRjI)opbNi6_Y0CVudAJhciE6~#x! zGy+0mCgY!Wz(o3W6bLa&H2!;%{g}O-9Z;&4zsI7WSok*&0g?pO7sxr&p~9g>a6-?4 z#BCRt=mcyF7bJtL?g0V=Nf#7(VCydHrDBEW*hb@e$GYPN!aveATngn^Roone!#5`rlUKe5n_ir8AL2 z)W*P}pKgRg9gO~WE&e^NZYr=Nerj1ctGK%6IBl1*N%R&;` z*T>IPG0FsnATz)_9U-746Wo?7PU)@jY*k`S_0ttga1STQAi<49L8jHL+vM!j=5bbg zUpR58yJzsL$AH$SLR+h3RU~E-z2?s2us?y~b(}~H zX$tU>;%sa`+H>}&QyeZ-Bp>vewdCpmc4= z5hmFo2_8zk(h74039+zCws>~Q7G5ga{n4H^&$KO>MHX&E@=);aO>Ok~SX>T{DWWZQ zvdnhB99VQe)d-B;#TmgwyP!DLItzpx#MXR@vs9nw~V6U zmna&}myr|z062!h*@^Et^Pr5K26iDkPM*E;91_nbiGW!1#fnVO#`9A=r?Nf%@+sT+ z-2<}~C#&w$r4DhwSSiU-kPf2dIZt9#rKL=Tm)7bY=Ux|LcZ^s+AF5;MHHsqea*RtQ z$Nq4lHRXlp@ZP%xNn86sujS_ph!^;NNSM91#@Z-gmp1OrZ1RPvLn#A+oRcr+ccJt< z8+!Z>6#}0gK5r$H1aTYZYVc`jb0|6kB+CjCx8Ft4@M0NgoH2W53*W9;?+;UpZzd9m zW1~1;pJ*&J>`yJi{mbwylR63@5m`|j;_!rXk%Z1TZlJvR+>xr}*gK7ZHILK~Gh;n_ zlsmXjsaxHq3EFM9fx=`z!_V)oPqlf&0)$)+7OMns>~wSGkW%s-7m+;6&y?bvv3Q?< zxo!Jj_AN(#QZ+AeqX?k6f%6mWA#TpH#3tNG;BGV-8A+nGK;Mt0)J9!r>KoU+vi17| zROl6(jK-SJXDC+}7aF!Tb*MQELf8H;IOI=U&Y8M)JiUTe5=c-Z16>9kn!n^6IZ&n7 z?2r?{MS`Ms{$(VDf2WPO7!RM073?;UPe6&`;!I)=s`MT>#VPulbGA=Q-#k!r7S4p2 zc$mGXi;gTebs`d6IV)0rmPif=wKSj89EnO!T%Tim&2X^ehl%Z9?YlV{3k_VBeY#_H z-A*Ojz&unEKsi~m+T%r3Jk(`gEqZVkab&7Jdc+I1@qYY$) zfL&J2t&PlXNkSQwp4I zFSx3+Tsh=*K0Mq40Ai&Nd@^yP046?qR{14d$iY+uB=2a!7M^V3!sE6ai!_(@@5Gh>sr$DQB(*Kev(Ur& z@UKtI`({})=+-xCD#VIN>H!5FOHC^l+&AvCt&hkakjZQ%!1vEK{L#1_8O&`fYL3$1mrEyW8FGTWbRYKc_R67^j_#x_*f9`AvkjNmxewA*SXP{FLf%AY+JH(k!y#N zTqU!bU4qFMvChrPD|0rBQX#W(aiT7v#JH%;$X|nJx^ruc=K)|rscg#OzFf9!?BP5B z(MCw33*4i`U*C$X_85}o0Tme;(B1BRN`fp&zDU4c+36!l?;r1q8wC14-ADyiyHdBE z2kfX6RMZ5w0tNm(6dOBVajh2x?ya6{?{XC}8)&PC_oo;*-;DyzkGK(pvsyYNh}8j} z_Hf>of4pG5`yK0l=v(nr(J6_(gWquyAnLAfdtZ;FSHd{Kb6t=EB;xFNrr1Wp8tsy~ zEalD5tdH{}VJ0FwoS!KQT^77yF_C7$6iKlPR6KeQMM{Qb!tTg6V@6S}`^t%u_!W6T zY?2zOA@BDBN&F^!?`&*RntTvh@V?`cWDAMi`rRGJ7eOW>QI=|_+?px@$WY^c9_V<8 zA~{b>^`xj|OvS~yi*q*G8rtxysnuRBs?#DdIokE&-43<`Irb40ZvWgp*yR_RmZC`P z{Wel}WXk6-aIcN4uLb~-XI#WPqnt6vQw4y!`s1E*iGL)q*7IeXbU1cR<&4Ue-@Shx zxU;&1_kia*zU~O(ecY{p;O-;<7w%oyIqb61KcVAzWE?g6ms1rN>P}pmiFikH=J;W# zkuQ_9kMG06G@3@*W}49rGYRyJax^)8nk-~|9>jl=(78%D#5pD>fKu4Dgukw55_+Q3 z>uiPMea_n2dpR#Zhj25r!czsOFxWQnG7hc{Et~RZ7WJLE>Q2zqdn-|wy2v*4((-W| z?DwqRU<=6?`et=8odz~RqMe^_i+w*g*TOHIqO!r|$|74@tlP#`&(@AE*?2rv^4{I< z0Hu_aY}w{7NzljjI#?vIg)b!KFuYo?Dc1+3_3yX?P7^KXcmXUlC^STxCN-F;`)_XC zz?DD_{KDN>51Exag>y>vg0#{r$AAWb)Q7htkq~)M`B0`v<`jM@Eazo3q89$1WAv9! z7nG#9i>yroR8>^3q5u+g2+H%^y)VfHkZXZND*P_;B9ENhWYUYR^F}O*RqdQlGaR6? z#r;HTQWt;ngx$Zojth(gaH4AGzjNAdeQ;mwPVY$3Mjr%PD{LRD+V<8nux2@?Lh ziF0w8C&QPvp0uJYi8@D6S@ z0(ZV$P@ZUy7nybb?h-*rmP3 zH=R6e>1@tAUrlunAO3nz>}yE}PhDbSH;ruK@|m)o`2Lb@{L79I!_s$v?16%kL9DG9 z{@>aWpv_$*kv7odmnhz~pXt$R#Q%0hU)?vDkdNY-!Iq(Y2qe*um?8&7Z58=I_kqQXI3pk9_tO=SS~A8ciB#y58>SnMr(8p&~SBSa~tD%x9 z?;-gQPAfMrQeOLX!{Q!d{#xjywK-~7h&78}e0JV;-#oC^D?sp_P-rb>JgHQjPs%Ce zobE_MaP)|ggY802#T!Su{?$kk~?FMd}%&2GJMG{KmeVo!{uYt30CMV>dU#=sb z$KsAuaTZ8eALGAK=L+{Bci^m4uAT&P%5rA46BoGq-Z;1C_yjL06*mu;^+P}1Duf=xok>P)SCqIGrz z4*G%H-YYuNo8$XCr$3^3lJ}$&K*XPS$Xu;|N(8F|QIrk#`Prum!-AhyOi*Pcf*-md{U?T1LVbzL|TVkYYdw)k_Er+_v;La+wX zpwR`5WJZ^KAqAqKPyf#wLU=~$-2BwO{KXAbsC?uBkT_Ak|1c0KjGsHe!MyV~Zm~F? zPqg~S`B}C_!b%DGrC{;nsoKxn^-S8R(syDjE*5kMG;8I0*_z{0q+9{Rp$@qnk>*5D zV`Ar@BjGYY;xFRh(TswHSsMp^crCQ~*QyqWu}$}9>Lhf2<|}yju8h_Golx_>=2ivw zUmxo`))2sw`-$EE=l}u4+%v}oAPI%*dBdx|-RkaXCRKf*r2Tzf2=63NL{hNT=ay`8 zE0Fj{bFFDX`9Pl7&9-OLp;Ih0_J{XkT4&lLmorjHAWZM;2@mci87{gqdBQuGLV#`l7Z0F8E>xfIwHSJ(?glChN z7k$4vQIQsQ5}TAF>;CPIMl<8LLv@z8D@=`x#@VWnVuqq4zTq_VDfd|d+G#zHMDbbQ z;&LL6OYT&PBF8yR(hioE&y6Q4Kmv&v(QNTjLwv@hwv2c651Ve)4M}U`zjmDtK0a%7 z!;P~le~JY2g)?YkC#LuL#AY8)th`W=Yo$-ta@)1oL!i&SJtU@A%TgpO;JHzZr$~T6 zj{|DWBiWBWo2gS^K&(DTOP3wNjw`|)6uv}BFa2*%qfowQei&N)B2B?kwO>o;ogfpH zK&iAx{{^nzaqTr5g+k`}Rj2$JbrRQ6tDrz8RT~9E*{4oG=I&=NFQPCJ14*UicQE0% zzg!;l=rJ0K|3svjW6gPW1)ruq?F6%G3Hh1ss)VZB*=56=JNJ^ZuwY+`xe_TaT2S9W@ z6CpH?LdyUyeB1?}N{U=1GZf&DW=afHze{_GnC{+Vo>h-lto!j$ekgor@kT6mKI3xQ zp*p-mc(9waD~Dko6zJXKSoOOKI&khl5A>(={fi~^); z;W6KWw{T~kSg_qs`gZ;HJ-6D2bJRsK$sKy?LridXdL8^u8bEy-sJNz7JV#&UHV(mZ ztnIPlB6U&{S%L>$JagPHj=akp;o;1cSBkQ=<{Dvil}1^(OBYhx-`uwuil|XjZK2Rp#r=HF_bCX*PVZpRx0Qd_z<&qu`CediG+isqpR4Iy`Bx$< zpDT#xGx%^S_0eKX@=B>}je5h1sG7X8XQO~g7>NMr%d2?S?+qpAI|cgZY@Y!-z@MQ- zZykT0A_Ub^$*KNv#SCAYZGHw;JN)aS$bC6k4|Bz-h$JsXs4R1P)$9&vO)w|KPfvn` z<#FCg3a7I~7mydo4h4tU3n2TIMnSAR!`(z8D$-A%bGz#~KHN%P&Qxr174g?}bmD$0 zH370Y*M8z9cYI53ffzSRh-KQbhZAc8(e%HVYF<6Q7s|DcGq*JHHDci&EDyRp zFq{8Y6M>~<2X_X}y^ng^l|*g!0+N63ctI+M@%>P{)(aPb_P^{~@le%`~72e>O$nqhsD4K5xwS zI2FNu;@RFm`uhF!u9x0K;`Z<7zFE@@)Xt{j8Q1{GpkCCvS`TN4$dN0QC(&w>tpc&ywI~8rB09GK6P=Ps);@N zzj}^@Mb8HW*Ty$+cfWG$Nhy9_!nxv+#1fp1ToLztBT}Mb!i}!Q`fKXPb3=e+JqjPy z)^OM#PQ|caI$F2gkEz$5YS)}3S6Yo4kSu4PMb_IDBh87FS^2< zJ`QW&pR>+K6BI&7NZv%<3MTS2bQ=&H#c<42!WsDTlPeZ}ma6p{P(%w!Q=JB0exAY8 zsV$#g(%R_5pYPeqpUv5L1NYgVT6M0htp=L}mOqtu>Y(^cp%#@~m&&g)finqLwI#M(L-y%n? zDD3LQ1^_R2-AZY+&+RHlte*8F0S&qWN}z_t2NDadm90vWBml{Vubp;E0OkTGlg_a~ z9YA1808r1Y{Z!R@TOFGpJMcWAQ~9;H#JJNTsv);YbEeolq z$ry!LiBx+OoIxXUqH)1k9c1bSkeEbm4|X0C6f_*m<`9d5yiOj7#L9JrNY zj(oYw$*ngQ>APQzEWMv;V`8%9?B_}Rl@`iIrqT6mme<<5Hsf$!ifmg@-<;?@U!XFS zZwvGD*4sz5i8oOMatZv2d99|KQPUNwQMd7!yQ)2{1Qu~B$Qj_+AaUx3O87G^*-|^X zBspCc2t_gcou)tA|0)c!OsGffYW`U_=Eu)2X^c=r=}z*UTP4g%pPHSB?2tde)>$5h zOuL>ec)wMa4J38r!@gttlch}253=TIm8sod9Q@fm=i{75V5Z_yeAlUz@qt&5mn8Me z<^gpCnn3m=R8b_QQIooWgin%GvYv`_!Y)Q|;3_{BH2v$o?cZ^Uvdp>a)tMZ!I6qzZ zVa>+tky^);Dy^NW<1@qnWTDzLWcm&|K2UE92Plx=`2r1kHl}jo^RLAEJ-jSDR8x=TMmfwUbGW%hL+1`C`?g z%M?E^3N=0PzaF*CcXlMeG4~fO1^qG#(9XtK^ShPrF507yIu;aw{{Q4qG-TUZf(Dm3 z*G%*rcW(h54oJGe@4|PQztFPo!O&XV@&Bi(d8hDPC}wV-PZs1z)C2} z+o$GiZ)?XYH0xO?BRNs8Inr|}rbzO8p@?GSZV-5~Ly`<*JGkn)r5?_H?+%JnBn);V zN+>kPA4FDtyeK3+xE2WLP!KCmgjUOG1(rN07WqcKdL*5E(}^{4T|)^H2{<_jog3YW zyxQm7q#Wl=X{^yJ52uMl6cLsrK{|Y#8c%l>ZslR$9q_i^mrop;XHwX)i7jjv}byRL=lKrr|{DeC!Gcv9qC3ILzeu zqLKhcat~+)%=X_Q5m4qnX(o0ii;x(ngvCM;**iTn6ruW?Bo%>76(N%yF6RUZ zONUb$0H{>=$+Dek2h?f^mM4nR!H$AN!Fc7xMce(uz6@z7)kw4xaqkt0wR9q<-!C6% z&cLotEux(*S^UqS8oWFZy5f%Q;5HHWROTgnR}ZW(c4U*i5BsvRES+=}dHIQgf*$?O zxy#D~A5&lcJ}Pe{5?W}|2A3m`;+cXv9dt_L1@q1zC{WDji%8oG3uhOt<9Dt1+o?3` zDWlB8RPjhqif!)6w(OQEoRgo=?BF*ewX_EBO?7xZJd>|~dl^43J`_MgkRTI!I~Ag_ z^3+1^_|GEE`ELE)hUWH6rh~1CcC;x-QlNU?k(q70v8_Q>xjw`$$-fpxF( zoq1C48={aYQ-N+$6iKc9Y*kwq)9azggL4<^O3+pOb+w9uIyY2Thiv|g^S#d^AOzgC zRaZ3d+%_sTo3!xXkMoii9e#xm;|FejiOReFLRqV-!DeLpe~UZOFtyv`E2dQ}c97VU zqvGQ7+MI2odLhD0_7ZFTsb`a~Y3c&Bi~_p*z?WfB!%W}7>;kC2>TBe>)~1S{ILXk$Tq(E&I2o-Et>}fU2WiQ;9Ru6=~-b7 z@9gJ+wj{D0cYlet2Ae=_TbVj=tqTa8+^RT@0`+?cq(JlGCeC^iiO0j35csjoZ_504 zNgy+@**)iK$lS-7vJ8(7NoWENbOc3=pkYwH>DIjHxln!ru`en}iIJl*7P4%E$G+PV zlG`OETv0e3ygrmwVs4>eAN}=ZCk1@WiO(?MaYHA!iv-TLe1i9$wyj$nY8`In6#%Wv z09O~wN_LYw_}xd!64M#HjzR%3>EeGm1rV`go4-E5xdJM_6WYOtzHG>+9B<-oQ(t64 zZ;jtLMqJ!P0lv8>MtSQ873qFN+W-Sd-X$dJwY4?7`{t%KahK=_IJ%GbbR;M31-h}2 zLy}Tf`@oH0gCXwnuSZIhjYh+^ub>!BD4K@zf6~)PMnu$F$;LNv?@)lSW8^P~0vGjV z{F!@^x(79!2Nv2jy1oL~Q9jwb=%kzL$2{GyX=f^cF{W3{yjufH#N5VR?IyN~7)xhb zibM7B%=--@lSpCyxo20j%8E6)YJw5SJHbRS@0oZ`Aw&nLBMkL zZ|-QmKD`pTu@7Pz{bI)VCraWRwUy=pqMj;coJ!vURxMPX|AZ3Gz#f*~xq2gTMoTXlJ$AG)zoCXuaIu6ab8nOw5yk?cKRR>vXQ#!q8K ziX>jFIyPy_Y8(iVD}Dd!%EctamOF#wY|qVmn6xR#FnBi+I$#HZ`|g|n@<^kB@(Vc? z1tc=jV1#?@M{(I7{Q+=sfP;RqYA2psva5GC)S6%XcFj6nyretVfoBSaW@k?6|B)F^;Wl+HiJ4cZuMh5(E zT8Yx1n`~{8<4SGLgle{bwWAp|$xh`p{C~I*N4^I%-JfXAIJ`Zw=*!gYQMYfS= zcTqT8%bY^i!!2F#olcsQYJVQMC63j&wI9bX5JoL-xcEYF9Wq~ z<#bkdFLx{B$3fU<&xWcn54HQg3_|N==dG7OfYn+Fsyj zZ2re(8~$`;34%5YxDe-(jPY!QvxCG?rWzQ6!rTj2eFFzQl44{ zcz=Icwb9K;=2Da@Uwi;XK)SzX%{KpO8z>pa=%RW@*JZIy zgd7Ky3|-++W=Hz<)Y-t}cis8?nuWe|Bcvjsb@AShauqfw zUIc$$esHL2$W?Q?(N5hsU^d{4#tGg9`1gDk95ct9*sn%hR)yxCglSVWt3Zne|10)Nfz3L1(He;>N__=4M{$?-cZ zmTZdRaBv&NT7NXPX1gv?n)=fll76p#< zH@lVCog4dFQOw~|_zxy_?N%TzMXsD#=Z%Ro4Fd9G5@U$nWuc~I=}91sKa7;n zi{B{O=(E_02mn){Ene=Ff-XQYwU;X{up*LhJ&b$W6(XXona~kueCz^n_Q&r+H?wFj zu*ohN)2aLhV!X5Hk(2J*|8Rs$G!tr|b~aH8#ai;~Ijf&8sqHuJBXQ!;RkE5j5xn-T zYb87{np+fbd6$0Fu<6~v`4b@dHqKUT$~Ld>G{+0i7p!@*Vmm`36cmtvs?BoSHnz8{ zilo(erfj zEiJ9ssJ3st_mCh_91J;o1oGy4XIr)Xt%hrfFPq)}`)!TQ3K0qv1fIwK?36wH)s_fW zBLo8)KE>NKgo)*j$dMH}Aag4~2kStfRIEJJ)H7^7SGDS~itUvKR$=hRIVqfXgx(z# zLn+-hpTjx16pM6P`hn*-5}}i8u5#rK$u-2gBuQzui|4wWXrrQazN8gRuG*HL#CKmu z;llzOcVy>{u`8UeC!hbN!J7I9e;yZO~kN=B$3! zcU-D`pJO1zvoj^R)I~d~1j?ww7obo%_rDym?W;YjtQBpTo2V;s?Bas;ZjP*RqH4Qe zjBNEOoY}VrR{tJ~tL=%CSEmg!?)Lbm1BNNkz4TMo9mmC|e#zKr#^g`qJA9E^dlh$S zduj{cs9WX9z{;J9<&I?*U(c&6Gxw9CWs8v={7qzQ-&s(XbyS?#(w{WVM*!}B>Wj}M z(;>=2qhO*-JfK~q+d3DcsGpx6!(@?`AW{9MbbbyU-UL77Z_%GwX6ny`BwJ)cA7r*?)bExaHU3O16Jn$MCDB8kt2|Y4jwpB|RA2w_I4V#UHh- z^K};$a;kPHOlk_Jgqqq`ZEu#zCuAs{h`b47VR6M>DLxJhx*q{3Egk(5Qd z+`JC>h3}uXE1%zyVZ~(6&3JQ3iO!HxYh;VExKL6@hrxmuw89lJROYrwB3Z@qk0%mq z(YR*M$ui_u5P%RQycbH^IVQ?up=2XmD2|SPAGx51@3wvGK*))LKr}cRk#NM9l^` zc7cT#-Vg$84Jc%aMC>))TIu39TekgH&sxs{xvtc0C+bV*p6H(RN3-burf+*6kF})2_c{F})}}?_WX=ZfgerK7 z9?mTN)4A4^WFkpTQH0X%Z`@7rz1>+Yy@KzAWXO(G{o6?D+-hNW zLkorgNvmZW-*Bpo4aAiPe>K!dMQvG*4>`u<{lxE`aoj?EmAfe?UzQ>G*DH04>OfqG zg@w3atu@4jFWA;c(cbWEdJT!^7*P3pQ*j99|Ds}@p9VG#-MEh9vhB~hhqBc)B6Ee) z1PGDqcO2Ob_QtXSr@9@xr^8!F%@cQ)_V6MZKFlrK}7y=aqSDuu?y=jsBeyIJ2VPZXr;np*-8 zdICQaF?{b=W5vkg*_j1z&SZ)1E#$5DLC!hN;yxVg_9Xnq7`^jZPl||Iuj80AKT{DY zjf)K@8IUXES>8;nP|K*bSv5 zJJ)BqZ-;6wZ>{E>O~a{>HrwuE;1U($!Hj7_0hm${tDj~Lc#wsXO^ft(@7V}}AVfBA zG;#S-m%PQWkzd8(e!tqEnMj2hpfA51Q9S>iDuLt&FujX7D29KgO7>s908*N0@N=0X zayYdB(-I{Eft2jnDL_?F!X;(ppLChb63z6>i=GZPTex(vBQJnie#YHrel1>HI)GF& zx3ZPwC3~B+UCW_Rb7mY|k)slpeU)j{V7GxJIR7C!K1bEBU7c4Z-jfxZd#Y^R8@{>)b^N{LF)7HzP6__|m{PwtoExDbfOY;3 z$(Ji+C<3^8mTRKGdU;N}$Om&>>6#}fRC0t_WXSHgVvohz?gw2Vg4L%MY`inI(HuS# zg2(8MKuJEpCG@|HR1`9R-~=H!P|T-kdq3}6Hs?B4(Y~{(rjGXB!>)xNrg|R5^O^0x zKh&izJ?<85Q}RWg!KM5xk{5a9Wdzt;GuvVdvTc%`o!kuNz&N@TAgN~m|8D+iRP&*y zaT3u*3?$GzXBqhQU3rQ5wb1wpc zT_0(-x%zzD`eZX#sXA7*-j`FWR1j22SVGVeYFsw07K@{kCowm*@UvL&sQJewdxStv zM>_8{8n5JS;q;;%ywTTMFX!MSNpe+f@Il}@DoC{bYf}_QZl~E_59AX-p_-%t)I{$9 z6qbz_3pTo&SNpo&Mxr54U=ZpY)1o(P&1~hBmhFE$QunX^biuZN+r?+aGfYP|ek*WY zP235}cMGMS#(SsJ=Mj^0XyqgdgAHfk#1&yCm}!OEBS`WWJ?pMbguYlr@a$BmS#dS8 zy^RCSWaDaRgSRGf>l0^)*0%WT-tPkOk(hMo3&9q$wrz-TY0ZlOp66;N(5+hXbfvr( zX#~cR4gnEEsVu^|ZcH8k1#0lu;Rf#3?L$&31(Np39M3Em%;a8JMjQ`huUxm`rz7dH zP&`S}wES5_{v=Wk=-Gl~{E*wOfr_;FMrLE=3hE&QNLbUa5eJUrv)xK;upgqhrrh$( zx^IpoDz^VgWFgNXxfz#Zi>|oj-MKgTH%|v@2=E;7>rpwUv z-NxfMtB8XfHILR&=x?MVuG3j`@9lx^B*&{EiNQB!dQLr{aX!;%=oBb(BQovHt&y^5 zeB^Xbn0vHgMiHq+M=qyU3`O5auw@sB*2q+~O?L6UIi{JKau?0!H0Gv&QQz4g(@MSQ z#mYqw-gsnluU0MH%0$-VN;F5*luDnW0Hd7yG{%{a5_NdfC2oV@9^v`Yxs`I_qpxNn z6&6-gyL0J|@1BS{pgs~%B3&U5?wC;;dfxU{lr-QLJX1K&=bUd6%dD|ypHfNFwVtzu!oaR9xh z_21^5Met0@fkh=_b^(gTyiU{*Cp`eSVNm4YE;q9}E?PuA{Cr;R zQX-BSD;(+pRGhJZHUMZ+Q**O5wgl-|X2nce;5<-VyCai6Uv{g5g?3Tq8cdXYDQu<0 z4`%8n(4DJ&q2f^Y#|SDw9^4u2Zj3b>rFVOCsb&ZNy^m^+^K7#&l1(lSG{fd>d+_#9 z2V(Jubp(--`TY=|t6+VK2Cbu_eJ(ErsbD+ND1rO`WSVmZn0Aq@a2X22yjtt+rz)17 zN~Lo>yo6wN8Ofg}KvP`MyUu30x?HjG-KliWP^iL3u;W3cF-v)w!9GL)8aQ{Y zBa~Op>4KK%K!vD8A{WV7)TwTtZd&&~ZbH3i3qNSt;JS}OB9SXvl{)cXhU&7+z1Fbt z{i!5Yz2{5fBqUv@*5zF+2<N80{^^^qO0_L&hGwp$0^)hC^~$fyr5O@Qq0}lsrSGlf5lqS!UUNJ9v`%Y+t(}r zqQbd;?1x8ft~q)wJone~+6YMT&nYka@E>|2?b%Av_TL)`(G#VR zcuR{~?guaac~zgIbKqP31w50ZH5=UYtwxOiBp<*gYobUaVfc%M>wZB`FTeJi72=-nR( zNtM|KzUHKU+4_I@i>?X@dRGj8Gu7<9dLC!M4>VtuanKQV5DJ>HQv070)cN!5h*5hT z?`j@*kge*ErV8@aXOKYe;S78ji0sE0#7+k1_}uB%Iv(jRb0urcRV~g9tbWq7#b@Vi ztFxy<%xjfw_+DtOa}|wU*?Fq1Hq}jSJzcTgLeJ)?YWI*HAu~Jj<3)?6q3z-Q(*J=m zon4{^SAe+|_Cbt0Sa$JX9nTH-pJrk-Pa`Q-o0LfC{fCK)4vy9QUk;ob=3|IA_cFQ7 zwV$pbc}D8~5RF&xxkq~wfdF+`s);(31c9>sUU8jV?xC}lol-4u2mc)Bs&c}l*HFdX zX4klSvVy`Ng#^BD8+UT&4?`(-wsG#b3OB*o<2|V^-~%3u<<_|W;SldN6Nkp|?sh*W zFhL=J?@FcEfZIjKsjD*mHyL)0N$J>Hrd>Pk+uU;%5&5{e(E3hQDwh7E*iQa2^4%$(Q=)!>UqyH-RV#ZHt2c0n6W7iUgQ=1YGkcaDCVPC4 zIL-d86Cj;{S)v3^M>@#@9})w3z%?@xM<8~jMEp)x){+Kh;-Exv!F3MU3!vUU*}OsQ z%q!Hd51bTmalRk|8Rtz*`rN%G>gT0rfTm~-D_kOeRcb|CD7yA-W)kIbdm}igsGzx? zM44WafMO({&ShV$Www7f8VSaZh(X!a_SvSLdFCnHO@5< z;9}doKT^==KJ?`F#I>wXr{-S{)XgYFCAGomgI-+AJImuqYVB`SZ2$z^Jc&TNn^@~f zAiR$US}`mhF9KoUa$j(SKL%n-OV*E6ZF0re2!m3s+AF13!@v=LWpcCoQvbf_sn}KELpAIZRGV}0^$6xyvAQ`7cCw!9Jifjs0ubo$* zYd&5E`$B`Okg*Lv4wRUxFCw^8?>ulkh{7DuUav35xa#q$Qw&1U<>9@w9{04O_ULv` z?>XwaaZTaqO!^MJU&mS}+xtUbz7w@?7xk{awbYJ0J!hNiUD^L~E*R3o;E6oYq%TX~ zrT@8U)y1-H{j?|dI%}ne^Ku*?3NyXoT5z>j3pThk7RsINmBiy9W0;+)3W^xoR!@<< zsdrAHl}RrIGO3#90U_TR*}}6mwJNE9Paki3cTc;C)>enV53KlPLFP0}6jeGCk|_G) zITdRG67>C_ZksKo`hHA;^=E1p=ObHq6=?R&zBT>?MdUAHp+X8+r6?LJui~YQ=((5L z%8!~h_{z5;lGBKacL=J3&eW!VaL#Ddq}ckKzKZ245^=occJb5N6oq0cCVUxZB*y)S zscD7JJ-X^!c*6y(5ef(T9}rwn`y%)>u>%C=CW9r&FqxQC77Eug&6;zl4;Px7t5 znaIG2_Q^z+Qcgqy-T&M+XKR8@j~IR$sa=9tvSSy;G6 zb(NjBgb4U>dkOz6^rqJTRV-jYZ-?-1pyG&YpVQ4q!eZ6Mn)$a7&yI8Zq-0U-)*Z=i zkMOftsFE*|d~I6aZ2`RW5Ss#k2aegOaJv5qgEnpnVs_`9vDQ3m&y}PaIH6)8_wZ5B z$vu>v0Xi1S1u#`&%Vtz{%pnftZSko&jU%>x-?uq-x$@kSa2gui@acbqqJ9Vv(D$fTlJv&9)O$Vzwx}f@8emAp>r6#jK2q9 z;CJ90K02dTyVu3VCBe!C??mC+6 zJWylB1}>g@7bs{KD~HlTI=DSk{w$$b9d1rU1ZNk9J{S2@B=6?68X3~En!e4wodcnP z@hGr*r7T?lGW*FWZR6!lMnD4i=bY7-5yUi;r-@0eVUt14JQM;;OI1~U<#JxLf2#at zGv^6FWX*!1O4mR-`*ACP<~|gyz|ZlDB?Qb+8UT*r=hg(dMVLcr8pjTW%Q-107J#LP zV!~-13Gp};;zAV&l-s1Em?kOsvXWrzq#{_c!_VZFJ>W}oj8ayvTn|d_y45dM>y?F) zDr>bI@EPV=u@Mn$A)rxKwTXa>m(L2<+`@#Vn?UsU59L8A>hj)!R`|GoX%wPa-!?wAAXCY{iz(F4*R9SB!Le0VG@WRrhR6*Eu}L$t55b z5=z2Y{s&Z?%XbUrk$qdkceoiS;O21IxWz%zgvfv%o+(Zg*OKTOM-(-^ld5Rb_=dt0 zcuwBad7!R9g@TVn<0J60RmpjnV=NaeL-n4Y#MU}pv&L%Of=f;yrLjOQ2E5<+bCx#2^xUR_Q<_Vpyf?m%^K%9X;u4;-*Vigt zjpK)eE;~rZ=bZCfipx#4GoNU2IkV9^>L6jF7&>3H`6rsT{Xs|V!SD)# zI4(%<+Q|0T%@w3uIiOacI49PJ{$I*!I+nL0Oc ztZeg7)olCkdoqe*!Y%kSwPzy)|F~lsRbank&sIncY{iz%tr?fEtlu= zN}j=hiiatZXIWbx1Cmwod6&N3)@Nrz8C;%-d*powD83ii?q7|ZKSl=t;_a!(zZ@%& zo4}w|Dg`1Ek{HT;`{8f!S!kNYH8%5-1c$al6D}$8u3qPWzIr@3XO;} zY)P*Ao>$ESM(yBS}1Sl*b#vu4*D5ugxn2BjLpJF{AU*OJR`T;U|Mt1PIR7$k#Rn|ld0ujqG=BADrw)JI4ToswM(dq+vjqxJ5p6c(N+evbGHhsAdKm6N$Z)aCv3a@W3=PV=pMde^0r_BAvik z3``1+0K+Jc3&z$rSCd%%8%-CGASX9MT}&Nd1im2xV0tfcn$6r2Si#e~Z}K7V)=IYW z@`CMtjT3(@H1FB6ikp->+@qXk>a1Gn&c&V4U>Aq|CVu`k)4n}f+M_r7N_MkhY~wAr zN1q=@cK`oISvil7isTvGiM0+=n0Gt`CL76d;xnKAqcvNf?O2rpKSMm9rOd`(1ll>x zg);@q+PRYasrlUt3q?x;*P2WUK$RaPvZ5?BOBySL_ae8`iq9S2b{4$R?Nla7-bGa5 z_#G8Z?G_R~_Lht2rbl=J@0Ij+-c?@I341%2F?*B3AVo&q~Xg zT)-BdoKuiUX#-1CbIqBJOm`z2zjI*qAC*;sbC-}?go;%@ry6xRd-MpKfsdZ=8kz?SZ5DEok&Qnk+Q?el` zFE>WgQ>(dv!m?;9PtDt-t0+wH0mdl2haUrR97WLR^>*uPwgk3iBQcu=v0+`bP&-pq^Bnk!g=x5t;ZX zlS?iEd5_UJET{eVQavw&9O`RiI2h&QC7VN#-+!-fD?s((PR@d4Y#s_R5^qV6E8r}! ztGV%CJK`BkQLqJXCmI9HCI#(ns|!tIA0*#`jKj*-JYBWn{zTg%locP|3ayQM!dCLy zw~pH8r`wVkmJ!-oAmvFBq#H9^{C73&jEBS`BsJqaMYzB97b{w&WIP>QpIGBU5$DUZ z&IkBCIrlE8n#paD9FiUt1NqNxd~{DMk>pyC_}5uSW_!zEmwh|-%|+V;z)JQLS?$gc%XaP!?BNvv zE+qKgB>tXb!mW2-z-mUhPElhCcqSAk|5jk5K6V=0qeIC<2z z?roBiF4;yA8t~V{J}&q|>rkRM(M+9)zSfwxhY$B`b!7n$rmVg?5q>6}-<;-{lJ6zq zZstUQN_poJ$cCyE0?n-)s>Lr`r-Hf-6c!@Z=YJ>qA^*GVu)|=^+=Gaf4_Y#x?EW)Y zAGZ3vm7+Z8*o7DXgskf8kB{7bkTV|)+1XcaXckkFEo|D;ZS5IbRq3=flYJ0e~l zP?~5=P*B9NTjwELeCyK#&0lhJnPzO%YiF>fo$vgUO`9H>SodwGr?Bu;+jf56RUlaV z(VXod&~fIo@WO&v&i)?~Yki{%Fn|My=TQe5%N@ZFyU7b$w1ubVELunMTJda3``}j5 z)_=Aw6g5U5W9y5TP2?+H^3PMDqNq=YHXu{#B6MPDg-1wGEK0bF#H|R*R>n6S64Z8Q z#VOg*2AK(tYgr`U@%@r<-+ldX_30WVnaNQYI}xFM9AXw>+!$Hrj<6}|^ZD_gg|}U+ zwE8rX=jXoDYDWaCy->2@TcP=Dw1IQGwbOoRwO6Wow{sV()}8nWAfAoaL#?*)_fP3H z{=hl)vwq-TU2wLqPXK$LQY+#@VYNVS9`7GSzie5}B>zw`!izXT=$&C&F+1$CB1Yo)-M(CXpUYqHTb7gk|^pgO9P)IA- zjb(0>k$-@qh!($Gt8BdDSs?}x7zGG)WpxQBS!73kuwcWx6Pw_lQ?Zis(wT?iRfrwm zalc^4UR|};@rr%@cQ>r@VihQ_V4c0OwZB)>o_dD!)cQUGF3#Kj>wPP)@!o~jp_V## z@VVZ3)uhqiKvCs?jRZ|S62`A_USqN||M`SXY-D?dku%%rva3`O*N7cFBEf7I&jUr) z!9V-Dlk6f7u8gh!(JiY!UzY0RxZ=#C*a3=eyR|lMsDuC$9y@a@xLemvcOuILHu&4A ziXb`+cHbrB3tYz&@h=`ZG8_xYc@zr0v9Ch9Jck17!;w0D1RNrgAz23iDqLL+@qQy@ z&PTITLN0|f9a#}POR~gho6~BcDz8lPsH!; zXJfSjimOH2|Jm4)+MWUs`)DRtR;nK|zQ+4g0j15WyB6ZEDZ_B5o}LTlJR3NQn7kdT zfNRm!@r9~weR5!>Sy7ht1KhF3Yo0a>e2y#mBj>BO_b&q!%>b6CxWP0AD2ufs%yre| z)>PYD#RZHSJar1OVn{x5Ub%urRS>U%Faj^5wL_e26qIUx*e;dz*ihsyoIkxyu;J$Sy z@|2)9NEAovh$?5*QagBOth*Lbb&%#%>{`zuPEN--)D_R3ef|mCeDKI7-GOE)GKC;I zN2Y5a(bMX;Tq(Qu|IF>>v$fBe{{CLyjvrsO5()+~6!S{(Mdy--fL--UQWTVXU*=Z& z!Z1M98o7}}mTJv`KRZq`XAjICQ^7I*%lcVvG{C?>^8{@qF#h9b(*28inyf)wYnLYy@umPTUU`BXEZgZH}Hzn*ZWPA0I{_A>1Y=R|5LS?wq< z7LfhzNJ*OsBmSC5zMvw2|7W(DNI*mLGW;X9xTqySg{lIrK96SV+!WSuA+GoqZgO7k zW_Tn_73AueNpiFGWW_o^pIYT?1wjPCy&hY4d#pBP^(@W|-nozS(?BAgJKwO)zr}Yz zadzb8mc>!dHm~gAvlM}zsO%TZzLwewyw&5cLy>sM%XskDQ}H;cC`SUPwhoZY3V8PZ zBov>6j)>~sQ2i}&h2!{PtW`v=u!)RBGZy@QUfdzOJ>)AOhy>SX+U39hvpq`>GF$mc z+XA4!;T>Q45cL;v$$&0$sLHv!Ov5R*vrBY2yBiDPr$MTVzW&QcV*bzlMwEt4(Dt7=jQ;z0pFBG#P`7@&FL5#&sRTQT013|sbsM-jlFcV} z?}H8PymK#nfu9DyAvS^IR|_EBig{U^_= zvjz$hoPUZHA;GpzHm!%GfA6DhXQDQu2MOZCrI~uf>=N*-4jv^U2#$X3yH&5p^3JX9 zs9W)*O>$us_gG?P04vl3;a0|xvQ_Z?Vjn+bHK(l%dT+2hQZE5S_S9*+J$qTYj9vNO37%c@)6KL6TV0No8V(C`C4gR$gjs8ukiT}5a^ z(w|bqhC&e=bW84%LyqGNfPwcw1C4F~jrR6=Q{LRVrtZ}_=X*nufdHoanX02OrG-AGzl}ec8zD` zUk_zf%>Da#G*ByA#8*_R(RJrUSU8@s;BshFDtRJ42Ipf*$@6E2*D_Lq5B| zQ1KcVF8U}Y0w?F5Sp!&Ko=WPQYNqV`Oz#KUnWwe~of^GHV)X|r5_+TSB)JVnx5ZkZ zqP2XGn+_ytyXQzh3G#%Pz%#B1q(X=Vda@GL8rgs^Duj!^K@ylz1fDX?yLxLcF2R%G; z>TBbtOeRS5GY#wa2THCaV#KLanW8meYOd9hagK?&d_bgJAwlVI^1&lOLe4@j#KkIb zT~C62snu&$G5a|R;06E*f~+7500rerSwBDbCvhKk?Mg29v(v!t0B3F)&k_4FsRZ}6 z4yX+Qyv9i>a%Lmn#Y*f9{1mOBhc;KE=xWCY7bAgjR_L)6c_2qs0%z7TlhZ?@#H0o@ zd#g8GE24pmMhOHLJOGP11lULwd*Q*XR+lE%0~u755Zq5Ze#EZ*?jF!ZY$yKgkZoSt zw(!bCHo1jEtPUczbn-xGB^;8$Cy~?@cW$*2*xmkczMR^4HPD02KjB&L;>f07;=dor z0YOilp%E4R5ENKJQ^{{qb?#Oq+wk?aKOV`-wKQL}pcKk)J%50vA4pEkg7oi+-U;+L zS7w#WxmWFpA`(2c1|}L6NMF6_oE&M!!oWPm>nt5B*zAKf+is!4#%1k(F;<&u<_Gw* z_jaXI?;+@r`RacW0-5Km_6q*{PHX|*E7`CV{aOD*8vzP{xa4`nwLpR&vSy@bF)|5{PmXOrvb5?w%s1?>p9T~0#fYZj) zF7G)I?&3A9;#;3|D}#l@R9vh9Fvd1SRnC^8gre|Kw(g-SxSHy$m2jE4(nQi~<0T|V zl)b!&!^oE$FwgH0)dgqUlr$!Qpn~M+k9gf9XN}7pK3%hok2sdq+V zx^um8az4>)SYkBzU6r|$U zq>i9v>v`KZvt-vdZzFIbS(;)RdIIOe zi-9Fv?;?@$qfqR9?ASj$u|Db zvnrBW61okB;<-Hj9Dr#jvC2XbaV-^~jTW=&d{(M8>(@uN@KD|2U}|H;hW&pzCve!g z*TEf6Wb{Vwh~eFlOGUjN)_TvT-{IlZv+^SuUgrvJqHzEPxk7v` z_xu^xdMklORn@bo#3{yu*VA~YY@Ig;BKTGjv-*i!?X5pYt&+SAzl$_>pup64Gg6gb ze;8*OiGZtW{g9i^kckt^J#C_1PN0Q_-Tz0AN0lM0%}Il05y)OLfJ8@nB#vT_1s9 zCUo@1bM$xdJhu~xPnDLPUk9zs7tV6)icYq%9scP76e$C1g?+6J(#|?X%+BC-acrJF z>Q+6u6-Du~BxDSTRJ3?Yo^?L#S^Wn^8(xSIC$ci`iyOFYyKY6XfcG+53&oFMM~2!I zMSvG>GEMF{8-4RE(3BvYy3Fe2zO+0z8bt?+q{pg{RwFDolhRrGB6 zd!AVR)E;134yF8;mHjVS)ok&n87F2zu#(Z}z-<(WG&ss?1&jORY|+x?_+46~)ZSs%`&c$8vNj zMDizdH*+Mh-H-cH2`oQQu*DN|wz;*fOD8K`cDk}0MIhncC7MTKLh^|_PTQQL0Dmpf zYA3r$^>YP@S#?Jf+yBD_>2YTdBywAuIn}WB&Xz<#IK$`XoZZh>VxZR=4r$|@s4Ag- zDM^-91dAx+J;g`I`*^=pL$z$@Nr#v~$51*^(7#|I);K0;L8k)uA-cxoJb=B^o%f z;wl_YkrW~T0|Xs@&t)ILFL9#-R$kxYzTH4R{ZDueSB7?X$G7Tp&TFK$Sh8eoWVJ_2 z;t56JSRNB>5tA54+wr6xIMO7id|VyAkjlPz=0_#V3{gcMN+mZvf4pI9@3yVN70)t& zVs2vP15~Li;~5WR$inyMAt2XqhbXGk&5ZE+0Yo^u0~l?-0HAskXBt3}XNPvy=<~>J$ecD+R|01T97to0#7<$k#1UL1`Q-VZRc!Ou16%y_GVT+S;x@TCNSrtW z)M1!ji!6t`x^-!YGf2U-oUOdQCBjzWFkT;$!1lW%JwrCOSS71F1n{x-hd@B0w!dHz z{`dI&RGp>q*q0F*rHgm3_AI#T+RAA&u(iMIYgLRE&;&>1mhAt@lD08gpY^3$$HYg_ z)4l1VQl{V-k{hb@9P&eZePH3ORE&2I=f{69Ht(#7@ayl85SYqDr}OK;wYLjd8{9^2 zIhxgd^_kRI+@JvJ{0ji%YKXIdM0pn#J*CNyzMHWDs!Y&H`wDJFzkD zFU8S@MZ|$`6RF_Rbd2$7_<7HAh)Lazu11%<^CT(~P)WL+sg1>DG1Yme%P4(ms6xB> z-bkb4Jc_M*f4HwAgj};=1#z?$SpE5ujXz1$aY<%Vt2FZ#9YU;JaaPos2i#hE(sJwT zgG&PiBT>+`{s&Se zw_KXZ+x+~DX6xNHy~vqBibBi7!N?H_@F_1Z&qyh7JoMFyrzSw4)weEQc-@-Kh6sbc z9|#x}Y4gVJf+T>Q55q3~CN%N9-&9a4=b#wnj zgZ@O-6n`H#2}p1YQ8lssk;od8)jQ|Brj|9gHAu`NDTR8=+zMceB13uEO4bqlXxm6Z z8&twve<8Zp3J|J(?g2YHNKaC$wPQ6T&d3Hp>D>S2!wSBQtoQq|R$SzJPvifXO)LhDc=k<1|bQMU4-&%!AZsd6PF`iPKe$RYoNLu?yQo&UhU4aBTxHNW-ip`6ux9{8-=VI?=9f=fGdvM3g@ z&yGyZv$-eh03IdVMA=TUl;b}+skS*Cx(32TTgsLTXlgZX`Atu}0*ZU^IZz9L z>t!iEFU~xpz>zw8S8z!Y*z>4NH!h%BzL%=CN71X^Ywk?gS}aF7wz4}HhECjuW;jV! z!KL|Q0KCuez5vv?KFKp0eueXWkPLrRLz9fny;8Lv0yM$u&gG6IV99@BcdptfvQ9-M zIU}um2KNCCrr6<{rT0671Au6dMobd`N|N6A?E0UKEaKUr>t_L#KLT%` z0UU{8cx|kivGnxsIP;y_DOCiiyF$0r`Il-ommaj8_9iN3--=BnQGi14kTd5o{5wMNQGKdn z<4XYbA9kIqV2q^rcBI(L-@6XbM1B&x!pUtU+ilnEDLvrHY)oCEBc1~?9K9e$a{ZJp zmN5y8`*IRX8UVD{pDm$aDO&prielw8t{tGl=4gXClZlw5(CjJaiJ^Q*;jNzAxEOpe zQAdu22tSAJTd_81SX3C^|J<`xkr~O~N@EvuL%hO#2*B4x%qmYMB1RsYG`e$~&(dP9G?$B~3o6h|W+7c$O+AdE0kz*@nFht9wWoRP0L~lQ9TGYt$>Y zzOikMnK~}?$Y$nn5LkSLKK{IiH(s}er9~m4kRKXJb<w;6Z?|*7qxn8t8?`_)jo2)(j7mpyq4(yx1zN^5vf8nUzzi>zEP!*ul5DzK5 zo@fR}CUJZ#RfU|v0}Z|el0An?h2j~0MvQuDVaNE4AO5S8w)xJkjdDW-f1ue%GS+{` z*L$arH03#Sr|{Vzxn*#m3P{qyeN;6E3UX?a|p=i zaJ+)ZZhX9k&lS%R)i*uE8Hj8?EAb4r<;hRTalPt+yaL?r8zU>@d2z*R7ZngmW2Hl$ zTG1RaB+bNj&bLt!r26{sbsY99sm=XW!}7J%cCHPrc+?X~D}&%GscDW}Q3VIX$hw;& zbr=fADJ7S)=EDt3a*^$_Rml!W&uIYU@ftVY?5n!VUK!FQT zJI>|t&d?^;LIKUfxuU$(NeryM=xm#*f>(V3F#y$htx}ZMImL=zQDVjKe_Ie zOC&`+#dDp0mPo*-^`UPGHzv4}cO!JR)d!f|04&P5@zc;2pJ>WxX?L@$z5if^vddEm zG<7Zv6wEzTz~mIrNQh*#WOJ&GfFcc%w%qKMfFnWBByiAvIs&}`M0)_I3s2A3?t6VJ zJeIZkS-dxH6Va6tXQ}_CGiS-7K-l`*ff8NioM*w+slXlYFbP*asH9R4`Ka!y^3^5azt@iX&P6h|{7 zE6xm(fnRXBSDUHsUv^r`6XNbd{?r!`6ZmAr5U@kvLX!x|DGgr z^f`BFpNrU*AU5QWpyKa$t#!LE0_y$O*2TzgJOhBb!B&6Hia+wKd8lNq&!#dJq8>{c zI-)BjeiM2rEKb!?7$g2vf8gS-EQQOs6N9WRe{a@S-)hS)dEwNe4Yqn#I_AlCnj$x4 z{J!qJiM6iZkho9rY3H%PRaUB0hLpU%m3!ad_JqLXj}b?hJ`_c@$5x zKdkC5^n(e&6h(1xAIeCy04;d{FLq9;YfwhvFr~8~>yJs^G7-)snfr7h^{JcE*I18* z48?lb9V(v0eZNgJB;11riWD4lb&98XtL;OzzT{iJU$FVg0o&-_)%tVxUcrLw*lok% ze#dP}kvsnqCVsAqb8VaBpXKLp$G_gi??J|_O|@~yk@xI_RRQtZGJfY|WaEKvF@Su9 zqn0M(?ynK6zdvvB9PO!l=Hd0_i1zD9@DtKbv$(8*D6GzW$K0%aFbZr8R8|8LqE&FI zQgL5{uWg|E?{o%k6%gOExG-z?)^?D%b4vV7zFbA|w1DWUcJ8n%g=gHVAG-`}=Wt^{ zO#iHF^YeGB)t}2d;+kNh?U5ACsRHt6@Zq2e=Zcwje(}agjAD@Sson+D7Hr2hx*1Ez zq;#er1U>i~6;$l@Scw8jIAIfxq7@9lnyFoh)k?Ab?sd^nA=BT}nWyQ-c?vZ|B6v{# za{GE+N&R|0HNYQp(m70CYINR-3esKR@l+^NcnpPk{#OgjXbfQKxNM?&i=WQ$8vC$ph25U%c zCy>M6NOTD|-fo$H-OWsksBZdS0%dbX57b+D*lkcmWWiF6Zp(Qe@OKpUIUR`Bd8fok z25$R3*J3R`iTA|~j5`x?Lnw|hd$?{X&~(so9+gZG*% z;Qghp0%6O)d<2Oj3`G9p+Fw8x7mZdvoM@EMU+Xz82ax=MmkwC_a$D=H-mHh<8mWa( z-Ga$g?h0qLlis0XW9Zh{(z%Qc=%BYrAreo?&PTPx)+{eyxPc%(pSK8!hb{f$k*o!% zz$Po4c_uPoAt)?Smtny-<=I|ZEXvN9^S+ZYF#iZ=!Z>dT=8Hd@mHcp~!Zu@sz=^LrowMG*IGt6pqqqM!(8z=H*$D$* z-#t^cyTe$3CEcB}Q6rL{kE$r#b@wMh#U5PaScGR3)#s(i2H8++a3m!8G*FtOOsglU z!X7`jAuW@uj=wU zhI3C1g+u?#vJEC(jY5h@%&OV|wkrSybPnW7AV*D&<(edwcRB<&dGP{5RH4;Z0AjBW zB#1M!+_atl-jXvM*Z#Nx!InRHG4ar?sudPI_k4M`D1fv~xk>=g83g3VN>-gMTNf8< z@x=w}wcF+wVp-G%I1hBOqx>)r{++L{$O+QJU0C^>KqIl-!+1ty@pEkqzmG;&j5pjF z54T77{*nG}*cw~-m5DSv;yCHksp1BUgB$;_;#4;QFo!D>8}unAgalqKSREi{P#Nn^ zHV&6<_xw<#QLaP>8;SS@BphpX7hsJmzLJb);}(_l@RvPj`Hiy@UUI%HgYE=a3>CvJM&IbbRiI4Yn zpPnx`&$z3UHI}tAodW?NIa15sO0-f;aV%~JRgn)0m6Fv@R&9K$t1b?=pU7h<;S6-% z^{sf2lK1#Lmr|`WlY2(n^spCLblyY+48zdr1kgrzALVyl^~^i!11M^>cYbdRVBORi z8RC7jb0Y()xMu-JpE$qyhf%b=J5e_xK`!0izk*!lBBJ9DNO6$41Q&#awP2k$FX!I$dx0u)&9iH6W=L@gwnP3s$rYS|5J@9+AtN)M z1iz%25ov;qdA3L;aCF=m%OrGglH6j>=Fc>(e`935uSdA#ISH6_Z;thT82}hC{acBv z9xG0jD=LL*D;5qXw)Ok24g-SN=CjBT#BbQyMD>scK^jwG~@t88?ks~`yC^^Ji{vm`a$aDi098VE$) z2vrwbTt`dt-lh*hbE#~d+hc3r?27}!4F!r$NNarESqc}9W^D!+FAjYHM)DOXC%kig zD8S*(k)?}jYjG+Ki27pHdK)7duXq3emE$GbSm|n4l^*PLVI;XP{?@604Og5LYnqGg z{%<=@KLVBD_NQGjYwJ%JP(|gf128wUuVJIzk$awOgWYt@TPg{509;r>jxXbV7d>md zP_`TbTes_+@#v05a~+CO_3!2yfLkpm)K^_NCfKP?;p1-zq=(DJ9fQ_ z59FG$r5>Qg_igWYoK~yK%K%z0;BSp)hddeX=G#^0n+8JBzB;DGI z*8diu2?2cm`&DawF_H~+1toN$h@_betbDL2GHaLY{nL9)b|g)O;<8_Bz zFHH5i*geX#Q!#SFDdi#i#a1YzDHJ__k9d~4P*j0z7f@*wvjCUA`vmeWyj%$A$4|KX?b<)2kDR?v0NQP5T|E^PZqgOl62E$zCXhnq5>~5kMRNGh53T%Eq?hnamySQ7Zc=!Kw z-nPHQ{inE86`+XL&Lr>deO~V8nwZ~mog3mkKM+Z{i+U2JCmol{=U5_kGkR_{m7+uh z=9vfZzUNR?0<@}%CP+n0p->MKewH%nD6q8sUFE9UP+A(<9us2Vv>5Oi_AdnLFsQh? zn@U!hTTjxUXc)+KLMEi+rC^o6}$1Zj9rv;r`s!&E=NV=D$x>~s<8q+t8O!l0H14rSqkig ziMFgL{zUO8O0k#s7qqrZ!8!^(vEZ~ZVk=&+)RX~f75 zd2lgS3?+Epo~&E$L`DQwY0|Lh1b`&?PA2%b6*LMcFCjjxk99{9$~YshanwPjTK=5e zF_Sk1^**xuQgxk}CfO;}y`hrrGGfP0D!Jg?TxLdMJTr}&4LRH2(a1@OndmnyO>GiW zuRBuvn{BF5A5XR3K$VrZMF0S{^xL~@G7PHYLrOQMY9(_1L1K@Ry<_mWL{DOmROk3y zoHcMpodNeXobz*)kfgu>zt_t+jR1;r2wdnZGO|SEN?^3=Br(~>V^WVcJz46?c9NEd zWgum~w`L-}0A54}1_maIGQ=cVY&+gTZhubgIg+xt20;X?HucEKFj5MbatB;j+_|(X zx3)Zjvj{4aP%^eW(6kMvE~kHMETyX1pO-byom`A;=D~*5QO&Pj-WFN$$+OSd`TzQ< z74RAtUai~aZ~L;pU3{iuYyUV>pg;Jd1>60iYtsS_&~8REy`{4a+r81Z`m;q_|Jxl^ z;FHgI3HJ6$@!m)}EX@`L#^`y^)g>Q?n9`X1dZNT(;H&#l+E=uhCrY;SL03U_{gb}d ztl|z*I|oP{g#Zl~Q61J!Q#LcU`fS}smyvXrNPsJwe)K=x$z-VfVjhE)UF`;&l}~kT=l)1cAJjRv_Qy@_;-_sS;(MW#!iv-d-^K-B%3BrBiT>IoLvnAvbONuni}O&P zY7LTIAA&kwx-`OT;d~tcto{HJY0tMNo-rBllLBY`WcwmmJVw>LSR*m-mg5g_&z};X zB!`ZusD}&Pdw-&z%{#ygUVFZ1KA!*bnK`@p?p=At4?dnKz!#tPq!vl?V4;Epkqb3~ zsLleAg-Gtlt5#1d)~X@N?nCwd-9(=opJVq^5{v*8a!By64UoW7jZSvn@MSAne+IyE zimHw-l1CBzQoP4$QQQov<{>z?KbqLW4@=V4XruZnAV{*J=DHlaG&JwxoRp?H^gWnR z-SDm>;bsot+3W;bg(Ja}No~l@2Ar7+0B3)5Y()TYdLLx2m_&{U42-_<3t0=kNmNNO z0cxZ-l7)yRH!_m^F5>l3qLr(G1r}>Z>HwUfjRA)8g3&lAO5Lo)vRGW-Rh7KPX7Ji!!{&pE+#n?`^1U zOUY!eHqJif#$l!7NH*u~9qcJMi=UFHuJYzTC*@23qVo=MYj2`UfekWWI1HBsb>cINeyC5dNk7ZI~g=PbY5 zv~j10_m`LELyRi5{%plUCUP9U<`eW{nd12Zq@X~#n_ROlci2hfJVHUY)b4%N-rH%% zYhE+8%(*$xerx152#`0Djw{|s)GYyA6ld_-5d4>4Y9iJrHh-jHw|>20v(J^SxSRv1 z#TlnOUya>lyiQcrrH4IjmC;RciuXJ2P;n1;{1_cOr|PP*DxV?|)BTY?D%>jJ`eps; zia<~EK;7ECt~v%~6k0nU4Q>DPRM+fkGpf|gyXVUpJsla_Ze*32uo)A{%qv=ThGmR1 z$sns$c0S8sGZOcN8wT3<-=zKxic(Z-{cE`24^(8dMK2h}!Te*MJQQ+IJ$Xh*N`%H- zK`wHx5_g1`X&ebja?Y@{G-nYGj8-8~$yRC&DabI(Jf8r#0O?OwR+^z3r z-Tt{C0NeJNJu%-s9{}mOo6`tL(mgUv#;G`wM<8whdr;D#bUXjtRLEzi5{=;=FJ&KR z&Q#3b2yBAGsSDpRH*by{5r#>hd#K(CPk$zXLk?Q$@JF|#AldU=%!mUZfeHi~?t5o| ztgF(=#zn!yA&ZwC1$hMkIm=C|><(b-V<-D6Ia@@4?SCHWBcM+Hs5?XzfJ*X&smpS2 z>lzRr02eC6Hs?01CLTdh0hq|0&d9EK{i_|RrEwO-q+LR_!QE!=ozqg62mjE2p0nMH z0|YeBCYNz|fI=$|<|J@IQV%U7qsdfCR_vZoZkK_On%=pmbLx!jd_`w9$APFI5zM~W zw5>}$+y6h$+RkSkwb^N=(fxcZ0%Y$>2LV5^;tB5T4=uRK&nXgFkOUjDnOPP4pW=ew zbD)a?O6|`m9+XJXqm0+cjf9r@+It@(LbKhSMTJv)uxMEz;A|3Eb*W%;56&WCwXBbF zlCLqjmpZ~=k7Sw{=P%Y+W99V~b#;o1Sv`lw3nfK>5iTot`jeeK$;@6N3tWQGB7z;| znmzoyg&#Mqf77?A$5otEOkA3CSQSZN8=n!92L)fqg9z@$a){)Pa>(_fgE`qImyYCA zmG{31)hed=3yDAsoFRYZJUmTKK>+9XQc1~{TcwYMgVHhMawekI!bpj!okB^2v=E zeT2k#9M63%W4%uTqkkBMx4OTZ$hUm zvAi8UcffAHv2L}K01*JLX+L9)r^*uGVeDzVg5>sYV0XXUw!*%Q#y#9lkO|8*x9!5D zLg2!C#86az!jrzeA%SKNc{Y2tB*#bk3s{VxTgUGPaAaXL8HOq@3Ws95`N=ILj9ByS zonH;z))O5gZv_?(ai@>cNM?h?G&tqr#wQ{kLb32xM^1DS5W@NH{MI=icCXfz#oISo$Me>+I13206F>NJ%3= zZV>S^VQ02@B5wm!`z)3T5J>PN*%-mk;vm*|jK7oe!%jZ|zaR0P`U$-F-dViY^KLD& zpBZUv!`I2K`1n&NW1nj_D`LJ*X#>WjBFqXkz|n9I4M`el7QFcb~6XbAF8jrhCqRP8sk|;{fUdq@oRE zGzQQjA^8DbRzZ%X&or1g5D{^&I-~WC*5#pGoyeY#NF;2TE=sn9Dn9G<**Ui+$xbVN zvev}!WgC}bMP%)3t&7AURBc7a0;LB$`3geys6-~*DgbJw)FbyvyO$=Y;PE{)IXPCa z4yqDXD2-Q3Hv9xt*bO{H1o;Bn&14xbvd!jXVpspRC8mECz+(JCq-s056x;5bj>(?M zQK8QiY!iT;aC>TtKP=ns+dZosDYmkG!0|nxQ|{-E=^BW?8@AQrqX7@kTsn;8v{yle!?~WeMUjx% zwj%On`#eRm4X?Ngs&q8A)yuvkU*UJq@0-<5a3>a`<{BY8IGJSx5$+cA(O!uZG?M4> znOuw|%25XRi~PLp$Lk#eY;>;;9Z?Pe&c8iYqS?JNPQ`G-aad;`DNe z>c40qS7%YB*1mYu5U12vFuDkml6t%d(x@LB$H7TYWTd{r4lAaHR=Ag@jM(`p@NbuB(q1+|~@L zm>S#vT;;@F+y2+Fh_C$Jd<_Mh(WQM&Tm7J|6^9CIDxG70fpf4H*bvXV`F!1m zjSVgNmCK~XtIA4icr|o}R}W@zpGK1Z<^0w*Li^_Y9d!d^RQth7s$?=6O*BI$DR%s6 zAhRWQF}N=;1xO^2nIBfwVQjrOwwV`8A|sCR-j@Dj-3E8Z;=6bcWCaR$-W&o1X4L8O zzjUv$ME=y>sarS0`xt)ZM0zCRLaT2UkodeZ$TSJ9XsOZBKw|5MC@%21JW>>J%=odB zFJ?^JiUex%Q-GpxCzj`S(0L(3y{_fK4aAoDGq4a3V^K?9Qr)xh_0WwTI5PRjmpBqX2P$rQ)T*HYTDd5W5XTDX zVn#d&8tG{}+2~^2RppX&W3$xq;!t2Qe z{=`b(Y`7x zj)-hh&Q%UOaTm3Wx2BGCSVa=)WmL(?1rV2)y7H9l3W2I)%jz4$MuKew7Yra@f8>q{R_lv3x#}mzZ z>)$H@jFoNcMpss?2|ENp>%0FvvbmRQ*1kHh`No_zkCkoB+mz%jpI>) zdXV5OhiaH3sZeV{JYKZ%B>2da$hJB)|6Zi3GoV`>E==`t&+jr>W5nv%}+c$>@-dX7|_y8XqLG62G&NhCxD<#31!%gd4M&OJit36f_xD7>O zj8H*E*1`7>fOd03t?uBe+I(%sw(A|6dmKr4Wh_wGC2fFwHq-O%@cHQvk>aW^A9@ zT+tf%T-ZY8?36+#GNQsWWL-d6-vK~l^lhXKg%Ut<`wxLSk+ZK>Wxq^NQT@JW`_9bS z%75S09Q@GF7j6BcUGamOk5#Pm*3fMbA;^;Bpw`fSXTY6#E25Uo+E6EbCJyW(3 zs{aCB3tQ~vxx8HL7N4t&4ZihZ&*qzq;ena z-WyA@Sz`czcB)+2~1Dpqz*VhJP_#D;LNSC@I3M0mQj!sEpX2r^TRXYsOjMzzc&0@`2)KZS#&-&{2RhMhF z^?qA(eh(m>Mq{~ip84NQ?aBw=SOIa0%88@j#!6_VR{<>FL-I!+V)7=)4EwJ5l#dNL zOP;AP$PpXkJymg!%lV27#Yp5OK&Dmtco&~5fKBVmz7=NkifbfqlkbsM_x`&W=rhj( z@CT#7?!U7I&^xtpd!jA@*HshbKEIW^RraitZl-m2@pR7WO%y7DZ*{tyQa=U&o`hKc z2JS>@DlT0_1zwI4CJs1>W5I2oZI{FwPnKkxUOtVxgu9zZL6D)MCkkVdw%I9aJXps2 zr)zJZPIje=V(^v^FkX_*LyH1)`2XCOpI)>lo_*S>_aSl5F3#B4A6mCdQWsmfO||mp zW}D_C>H7oxkYe6O!=cSB%}64+S_LAL4?z0=7yKII#Hy;bfR&*~`QCaTZvy=SILt}i zXdpo{qPD+yg8b0Jz>xt74m?K&)cTFhY^c>9u%+e5ewByu z0pFg=C5+}iBwo;Jl?qMT0LbA(r$Nx3AeK=wm(_&t2#RT)%hg$_S4CUyf)i!Q32>CS z`7f(Y{2i65=*(22xI=qtJq83kXjZ;EUn1eGIF3N!Kx&)sZmZpvdj_w!F|}JC-xWgd zu~o)a%~^iW(5Wv184gj!4TFg~5^R@}cr@k40szdR!~3jrduZdw`qsVNR<$~NxMn?6 zOfeE5<#n4+1HJA}EGYvxpgIU>FeJW$r`0mPmS8PamnL^Gt2SyK)n^$OlSV(3^X0rc zWgGOt#Ma*2pdXUj{J~vko5a-(H1Zo5-eFWrquU+h4 zj@^3R6F8r!dV2k#6$wDTc3#{=yxv$A(tA#2fSYX9Ru22yzCL4) zuV`MAKk1}dSru1KmTbD{+V6BWW0z<5&7Rtd1>Ph1h_0xb!-5@25@rd?6Te6*t9wlJp*Nit=cXE7r+xqUA++15v`FymJJLYz%*8C1TPmqc&x zE)r9vYTdiOjS$qicfa*b*Cwdg*<#=Qy6afsY(tlDCf3{*4bKc$;7BmzSZMzb8@BnI zp>!GMpPSMCyq^ojEm}C!u$`!{F45LIJ!wNM{qGIyzca+=1`z!m&L2RQzm;mkVt99G zK09Ri`sBz|>u14r#9`c5w)a`Y@bhn98LC~byR=3x0mn+G$4mHOs@ESPS#>|Vqfs6S zyj>J4-T~b6Ckp_cC?8zO`@l zE%X@4JIESavAS=uo~F*>qB*D5_)8O z9O`)&a8|Sq%i|ECNLX>IuLZ`4QKGw^2gsOzwg$kPv#n0oN=S-%OLPD6YaRRI|-K~B<{i^8QcA)FYsQVf5eT6^c=VqLC(b1$31mp7P!)S zV}OFgjhu6(M7){7)rm$y@i!AmI@4RCu!y({a6h^kY3pX=gO0_BK`e%O2Z#MYaUnp! zi@$ZXA}C6}H?;{8K**LqD|fu{ z9CtU;H5WgD;h3i?t5tUF>N_wB_Trk^u+TFaiZ1?B9IZOyu=rO0+5cZ;h_C2RfT$Y!5x zTIDDX**zru!9;+h@lwe#Y==l{Y+vJmhIm-)Sd?GNNriJ8zPBNnTrl%ByEKY6HBimxOO7#@s9nG z#B10Nq!AD+i1L}T1)UHFcq&U{nz1BX8Oi;|2s(>ro3`>As{9zw^C4V51O;8tZC3au zmU9)qFWn5Jo=1{l5tYa8?}nOTl@8=>xWS!VR3@liDh~nycZYhd<^6dZuOTqhW1D-j zV*L&0qs^qqsyo~;2@+A2t98GpB*{QSbDdoT>y%^|B=m%@tr%%{J^XH}GsihC&q2&N z-0g`)*T*tap+0{G?|FI)%4;QTc{#b}zLL2uJtyyWlwjHRw&OguM@@fQCcZ_?s!0Gs!thp@!?z;2u2#y+$ zRZ&@nHrb`4C6ewqb{i4vp%M$v(cFtQE0jw%TAXkv$0iBBen24?r&vazk>X-bYm57k zcs4%l+H?a|0)or6zrL?(uD=qg=e7%fPD0DN=waQQgG?FnVerVxWJxQHXRA%mxnr+tx2)HFX{>VPNx^q{mmlX4o zn<5Rc&I51*oDjUmH&U&5G9k2X_C?H+G9Ic+I$pA>z4qoUC2c-~%#obDF0K%;FjyPO;&ab=HN zB5`J5O`J!bOKwJ#pUgR+wK~;~``mMN+kT^G1r#ZZF9V2t)YHtIF)JNR#bIKnfgQQ< zi$tGG4;5%c<1i2B`w$AIukcyy$L}4LtbPsv>Ks+*VvQ=)*~tas4pAwQ5x-z|0LXBDoNW<%){8$PHvl0=bev9Oi1FEaycc02>6c7<)BetFRt3%e> zXxX?C*zCcEy1?l!@o41;tq&puRAhv*GSo>!Qog&)C`@+X6VV&sD7j z@J1KF#mDQ`!n3EIKm7=N+)ocbpX9P)rW6~fX|&b3)fk=V^V0BkizbPGfx7=IVy zu#TLhBPtwf43NOZ8B{jS>9A(l0-`8T&*@2LX_DcmLS9mKn>Fd92R9-KKJ<5x_#3$F zY{4IQD33{&v`4nKIF2B3CRlN%I*rPV1-Z6O=aHzN$Vs9&JI-3;NY#21oLV5dO@G58 zO1aKupz&yZgzCHBOgpaBqSH z=1Je4q9-IhmVcDfA*9CyRnTe=73Cd1xIVP}!>(%)Yz3A8g)Tlii>eq2>)4EKukLEM zo2~lcFhaFjmfpdLQlT8DASug-e8pAq@_dM#SHbYZNQ~m@9FX(^lHcZz&Hbcey-&s( z0}u?voX;ZJ?!MK*MM07Q5Xh8MYwrNCJd+3Li`5F{8RD4>m{6AR_umZh8co{fNhPr% zk~}M#T-h1d^e={12Dl_EI>uedZpC)w)j3p>B&3G+%zu2$&cAmF&mja*DO*01vwq39 z>eG3ts1;wyp)wl+{G=K!_%~DO6Id6&181#@3YBMbg5=W1GodKaJEac94uN#}Y>ApG8G^Czf0&)xs!tmFc6h_+HbtyFJ@?dfB>3R|L25t*OXJi~rk< zM1~mX7k@Tm?TbU}UK?nIapeyi@&zd#;XZN8!f#pe;`o2RVcT!_ZSrkw2mf->*8Z@g zE=BI4tVRlh0?yb$fT$BCG1b@FEp>{R;QA!~G3lc6WmhBeol#QqRciMy;ru=dP>c6; zZ++FgamOnsLc}%sda`Mv4Xec8UbLoUh7+Ff6fYz zBguUgN!zkv>ZEXZZeA z+~*0B+YdY&em1r0Nv{3U7XbI*0A5EU6ep|sRMEmoXg-}sDcr_3d^Mxw%5Abt*3$+@ z)<)`TRSwWG6VKwXsq@Ayw3)vw+U;MjDJk>&+ru8-=Tx9LcLsoCYij;4V-*Oj@TooG zAtB00j8%8+I)UyPn~12@N6X47{k4fTAFtWLGY71<)wj*_TQ>irnsu)a6k}yEe%&$e zlRW>4ZG-|~O6?1LKI@;i)iGx`j9nb2AcDM&-*mN+L`2vHfTsRM?PUN5+)>u6l?O^1 z4Kfc@4wh}^c-^{Jx)x9H33D@RD|D;reyXkmt>0z)4bU9#pK9A}gfq_WRZ0=GV>D39 zDrb3!@s$S=Pj(Y&pcGHzGi_xh##DJAC!ZB^=qS`jqd0aIbLz%N6MNz1hwaAob^E7( z{J>s)^^{urA?+Sx5)n$0U*w8av4|>#?Rl?Y)k@y2=TQ-M;;V&UEIZaerG@#65-0uV zB!$iC157>6X|t+Ebr@_93}pB{F3C#@XiqBG%et<_G;y7UKS=>+#2X;$%&Cn}Wj9JS zj<3gi6`+YcG6b@uNlb!6MYS~X4@tObICG)t*zKPRe+=B*PO^|>kOSSOHTOVio127L z{ajkR@*tA&Oy#L?#i1_}^{j~Cdnr)&fC)2Pjm044WhKF9NF9Q&-0#UCX>4EI)v8cB zN^JX`f%KoPlvwM^z|D9O^mgx$)WRX%bMkH~btfvomCoUSy@bSDEnB}g(9ZqjHUbr@ zeK!0iQ02wWMgi|Z&6QP0 z2I{PLBxNbG*5RCmyQoIK!^3!ht#WEx9u=TE8Lizcyn>5KyU?!l zjo=v}QHUPt0jfss+w-EcrAsC>pJg2t-sqE&M0^TIbGCNR31r#f)INFtEBp*M4jQda zkO=r$q&B+AYCn}O1RV}Jm#o(Sz-MDyK2)>8oR8`%W33PI{85#VsKkyzTE%Ap08&4K zDimkPN~!Jqb5C0%d_M%bG-KhIsC>MnyD`RRlRrVhkdZZ?DckxRnM;lRuFIjIM^ZJIgWQ+*Dc+3ft(v1d_TRbWApFD5_)Ri>1vZGZHH>XhL-`B z*05j)o<4hl zaE=8&^O2~08D;JMzULGJm$TM+XQKB{ZU;w!oVim@*2}vt4co-jBFJo+^zzxdZGSOV z=|q2nn}2=Z*}CGaMF1%6&&S#&kJnQbF>JT9vf|gMOS9!xO$!_o0@!iWX5nlVMF_rs zX)It^%9m~S(45`xui5O26-fkt{9n%NGvat)=Z&G${zs6e;8C6?GYCfGKAy#QZmhxt z$LCQz=;Xyoc4T7feD3QGa@%IOjc0K_aRkW8$ova?-U0Mwz<*O=F^4#|{To_k(;X47 zd7O7=YH4ECvqgMP0Bsi{kl$J!e;9~puO>TzGmZmzN7DAIYk;kS@^kWlc(o9vN2`Y`JA*> zg_uinGMVt|s_w&|!&zBTD!L&af z+t>fkRpkYK_FJ!aq)y3^+~At;I2?%U1ro~FoJIrxo1IC2H`YomyN8T%T&0u&6}MQz zz5m{FoH+klB86J&h4g8ky|LFRY>ln|`AC{8jAbka^8m`(Dht(A9fd+rvd*P}I;q@z z8;;$^QUZ{fa2xH8<8u)-khDy`g1BBa6ml1wL@-}Z|9W5%RdR7hL;9Qa9e0TID3LX) z^{UM-H0}8p97SCl$c104qtl2*$DNDu_ey?_&VR zABf74k~GF+N!@Aw+pKGgBqXb_l}o>7 z@)fvG@4E-dRW+LTNQtnuH^*M0x}Mwn%%Q3+9|9uw^0sxWYo${KTYh!MhBwEyb7?4H znVF|cw*5~7q3#;dAWc+I(HG{)oG-~DwtBjR*OFSZ^Jd`U)z?GkH|HIqM<7 zab-jjstaaof7jKH?idHni?hy1Vm}UbBX46QXjYZpD38PI*|Zo-NQC?B)w2~@lu~Xr zsZAxvDpvl>dGkxCDj%s?|K3=_NBwUH7IMRY4qL^kBd1W(l^WDQqov1kY9Z5gjef{R&GWcjxD_I#xR^s zCwH7k2e$yY?nM)+*>g21y{-N|D$g?o?e-=${LDhE?nTU*-WPpwJPKz$tpVl`^r{Ou ztD~%D>>Rhy7=*gq4D36f4#Ys`NThM9VuOvLwnb)7)vb+cmtdjtNI`2_BuKLA;E0D- z&or-&a*?Njol6&NhWe~**pFwAMA7FTw+WANz zBp9>J$=8ocW${&tegzgE2nC)>NDieSCvg%=+VYIm9?2u1R&DpouH;>*-CkX+*v7kU zD;@D1F>935?su`^Sv!y9S;N_5)k>h$#vNO5?uR0CB6;-j`A1(5W%*n^P}I5iDE@=0 zm0*^h=JiMO;8Yky`a!JtVNQda6Q$>XhuF<>r~#LTw%p&~W?5o8+|3RDG$2L>HJ(@0DXYE8{G- z-x*uwWKP0E)5*j#2eMLr^(s#2h?@=?gOxbr$cSq}Adzl49tn#EFU@HKt#&#m(jj9? z@dQ4bxvU*}cG*_n+flw~pi=f3;2$t4RL-2MTdOm)6aQh^ZvT1<$=Pj=bvJu9xey}B z;VlDTQ>S9^Y2Y|6pW$;SccJWnL`chAx;NsiFlJ3I;oRebaWt8q%h_OiV*7qNi`SLf z<}W+q80>%QfZciRK7}f*`AWsYo>TLrpjnKHAR7uSuEtVyPgbfg)Gl@NoAZShUGx zXyegDk+8iBWLhjrgSgusTD#R(!lymvH1Vxi8QU=2V+Aqi;=>|OfEhbO+9l6k?5cP- z8b~DU!!OY6mc;U@Q^1Rr^rvq1k8^*m6DCd~*c>@9)mk795-WFrfkK$qEQHlFD`%~m zlu~DPL;^5A$Q+fAs^(hRTn~DH3Tm`mbkf2Sl%Y?7EWKXjWVKjCZs9JWw5wCb) zl1}VE1ouJ78|d#Tcp=F2FN{QVq`8bOCARvnPVA>}E-NDzx~?&a9(Zy;Du0~DMj|bC zl2)Q>EW;TFU_L~uI(4`Es#No5O=Fh9A0xGSh%ToLLZ})!GT8r04HexG0m>7xiG&|= z8YodZZKf{Z-${g?BP#Rta1hHA0W+=+GPuv2YuaQ7Rl_%-L-{22+;aNt({9F9_M9)1 z`#~N58Iqt7IFUSA9ny`e@zcC4hDlr^cVXeVruDxYDIu3p5wKO(+ZfnnBbL?bG$7G0 zZ&3|N@WGnZ0D@YV`)+v-p7EAbwZrm< zJ*yuCfVnoZ@}pUu5AL0F#Vg*PY7CGR6SdkXqwG~Pa{FTv<2DROL(?KY-$rb^ANF-F zsEObDv?ox@6&jAasKd^#L+y0g8cTIs{hKYDdwvF$VBWUZS~mBChBzhd3qxx@S&+P^ zzZprSgIz1SFLE9_LNKC%Q2**sKqZc{0zuVB5P(xW?|oCN9R*16CKk0LYrTcUdeC$2 zWhA=P&)U+n4IA`_T50WE9;scOKZJ9CFQYbUwwjf3%FHD;vWzDyskPr2iwDB>yYS-)3XhRI>_^{)N`4ftVY@m(HNp@; zaT3l~W5k9Azyr;zcJ8!n(v8$LOE?oHc)`7nQ|GWF`SS)fXaWc_Phf^snHojN3 z%1qOCXIhp3U@U*XX&b-l%Acb8FhI_ovC~{YHO<$|ZU(tdqb~;l3i#|7GK%M&OOgB; ziv0OK&ngR5Ydl|&vCm*Y@v}VcGJx~eSOpe8`@;UB&HzDVuk2fSxnvUnQlodoqj>M< zeUa8mj{&TF9ozcX+mexImuc&(UEPN=4UX1h+gR-hT-^SA%Y1gGC{v7>H@O^8(eO?H z0RR6QF1hVyuxwUV|)*bYw*k8wv3 zSE%tq4S9Ul?e0LlB7!A97~8e~aYI66nSEK?dadh7s&iZ+3?&Ljwtj%~#CyQejG^kD zBzqfF)b>Zf$kUi6bzB0XL;joo$}wJCxm2`C=uA0iN|VWuSm?=9;7>aLeh}#I zIYLPB`ig~|cmx3+EEUiS2?>Lq)(&G2@8!5#FlEr4cy8~Tmz;@;b1umyl7vL|?AXn1 z43|biK*K-H^pQ+h8Ev|D3ad|cBRqB)=$yw{&D1IGu|vR$j`Lt97H$Ag+=uP|7${v; zU^|icO|p_mEQlt0JF7dAWtB#BIg%zgyCT$$pCak9YLzr9$z?s0m8KLG$B94-9lvH@ zzy635RNqT*vajD)z>#hz5Na-kE1nth%Cql`e=(=IKm1G4VR_-wgH z&JjZ4m7+QeTpOCh`zC2-*uwjzY0NI3C!RS+G2Cn!Qeh5fk`+Yn^Dz?P*fRKBI(NIM zOz|N!QYGa0iUUX8O|BHC@^vk}l-lU+SmO~UiTDn6$lac94zKy(tBbaMdslP*Y%#Rz z6FJ-dZQr~jtQ1Y;r^;hM_uN`MfaiQCu=b^a1$ZAkHyLX9BLMAtbl%qQwE;wG0y*t( zM%MjuBF1nTl_d#mw0bQ(oUzfxJuBx&ISNZO>dKa<_dz0H7+roo|EsTjzSv%hWrexWh5JJEyRkm1w4mHR>kCJH6AWo|JDGYBcm;u z=&ob;^BxB{lag~G!5V-oz@4Y2$pqDEbU(GyS=^^qXx;bmUh(hki0#kkECwiC z|8El!P{U}fwQG{zI=CYwR7(qCEyW|R@Lfmq1a%I zYMiXt4#2v{u@(M2WgJGKl4&3pv;({T)`rZ465P%8f9g0PxWjlax2F>G;ro#t`HJn0 zcTFm>QjDejb~=?96uVg*<#H{SfQYf4To?Uc>Kr!pXWTMiSCpF+xD#}VP4*Ni$H<{v z+hn1&@tckka^-pYGz=v<9kILBU?-I-g2;D>JwbQkI^#!E$;orQkOp47cO+VJyu$oO zFA&~csx}5|?$0^O;P!$#oE5j}Lvk|93mg*s5`3_Wi%lCL`G!C`WnBE9|NLqDjT z=VkX4{=BTy`OkTOQUXi?L;x%h&6&)|)+D-#xIaup#iWl+%I*M>ZafH`k{?@uNUYQA z=1hWsy0r@7yA?z#xv_mn``;W}!0g^LdH0=&2wButB9k|DYk@rUjhuFdBU(Dr=%UFt zQD>S$a~Dp|OiZBuS#jmN7tyy%O$-FUHRgO91-kHd5v!2p+<5}jP#VSAIJD3xDY#j@pDJ6pj;9a|TNi=&NnG*xT0xW8ZvuQY7y>$VNghiXR=5aj7+o^=w= z#p8MFe?F9>+7y5;y68kZA_R>JKm!G5*e>f`^6k)L%W8e@Uhd-0JZU3HfMX|P&F>ZE z?#I=#&J`fN7d=!J+j5!dDEAd0|jjSB#;;f74_;z zQO#029Vjdhq(8hmky052MdD#-8A@5-oT|c11|u81OM?R-5}4;ha|l!w5pru+%!P#&O)q#ye)U+QEIu0G26@ z+<7{bP4?uIMA{1r&$eyw&P>mfBgEo?TW|9a=$TAt9UPpY(pj!`z<_=d0KFN4Fq!np zok9SVR;U9cenjs@Q~aEJ&@3n9|$dw)H=ZZRzEXb(cGK@4v2F6V-L2he~|lJVyvh z=8s~VZQ!gl>gpEK*hggPobH%z=FLJYea8u?(N=ipt+7Bw^N8>CyhKFy!v zn|QX@OuUqWbML;1)T~+k9j7`-07waN76H=7N2WRSoqhHs-ErOzh~dfIOyp*cf#Sz8 z?f}(hyNa=!gMlr-+P1~LZM*;XeOvv(5{l5&2JcR7<%eBM0UpL5Mt0!eFIXJRZ1xoZ z)@j@!6g9JJkvb^^m$u7spI>#`3iV}Im?W7q*dlL@BSRAY2<)j6xWI1e+|xD>^>o^& z0p_WBgF8bnkQ9Hpb+~0kH^a|G48?hGtc9BYvjg2F_`i(8CPJ09|3KTuqtxc{*w*$Q z!l5fPtDhsOzVzZzyR$U1&%gY}nt)y3+}yHmE6|+m*x@}$s--G`JaY?&T;+?>+|B** zKzr?`f@P=IR^`ibzP4)+REpGB2_UelXS;_)LbX4r1rE5?XthNIWW`2sP$5_~oqn*^ z4f*#r>f1s=#0cDvR9p0z$`UyQa_^i8msKV!EE-TG$Zni@(bq*dtVA4A-L1MiaVc7hUwpC+Ag3kJrSrx zjMy4Nz$wC9v$Xkn*M=hVH6JHB=v0wnt3M*)hVs8aZjavDRzeqvCP8BAb!?7PrdFos z$VS9Xw6^4&J$ts$I^7(JjZR?wwbcusr%QxCy1 zv&mbrbRIbVkO34*fbqk`tt%Y?2;kO8x8`Q<&v`m~HGqcB>kXUoQsDwp7GveruGr{Y zH|(5k*=&6#&2n}t*iv8oaogfaVzW!Qtfv58{+LM%Kx&muR1PF3(XRJQOBHeTRAHN+ zjg;(%xYSHk47RlVrOoG{GgXzcO>-UmZv6|H0*L@Ff=IwNZ=-EVJ<~S9(hK-~dvoi( z&=PP8R($}AhHdmE1IU@(?dS@=+(7;)E@OL|?%<)mF>bQmHL%Cmk zW_G%+|CfgXUNmhQzJoiAV88lTEeUZY07_o&i!euL!JXR^y_|7A!BT*KS*zb#m+)3lvW2Ud|c zgfp52xkjc`=H%GMJDlPCv~75PuJ`L5!{gux1hHiC=lF9ZmT`>xgR{Q<)!6#cP~F7% z30uJ=HB{mY_w@e!Xh+KYSjY|UmLqwG&Q17 z7?u6zYd8a6WEvOD-Wb`&`#ZXCT5mkhE*t>E(}C_UJviF0c@j6`dGCG?cjN0+XSRO0 zE)E5^YPz^HqmKYSCb%at{vJN#?DuYsw~mB2{!^?3NnjLRa=aQ6@n-i-3ZX1o*n%e^ zjx{LbopegrsY`L*&-OI78E*rm91XO!5#ziTh=)tx>7dZdY;!YrSgt!f&rNGhb}dZ=5AAi zDwCc*AD%Vkfs;aAOUmBQwq{n`b!5$8)zdwdXkUTn`rcgKFrVTG>yL(F9%ti$WxW+7 zb5}j zodiP%alE=rekAj2j`gqAlU-kc5J^M&0ssLrPAMD^A?JYYXxe}JNZi~UASc0BSD+?V zFAu2ZZ8UzNUbOGph~x~A5mLx@82`LkxR2ug*mVck$K=-vUAc~i_Wij^xa4WKnJX8D zhe%ZJd=9uy=&2VZVqzT!z3r&KF^f+Jsgq<6>hCbE=8eWN`)}amS-~@4uH4ejiJyvS4bA0Ygi*=WjD~? z>f~1Je6NwHv@UH<=6Bz}>uE1~=jOzMeI){N+gi(Rubh`>5fw^~(r185q;<4q%V!pC zbO#mbK?Gp#@!}#ER~VE6$tI@*w^2c0G6!N!ZqRTE@2i7z@(rH%2~_?ES~lNFL`ZDy z53Cw^QlKPYO75lh;InOOQCIy-w;Q~2x~o~>&cmrSa9)PD63xzN&ceh)Fi0aFj;Z`Z zb?cs>5D(tny+rSkL^y4Om@|ShRP)lQ-@!Y3;0UkuJ7<8Z)iU`!JbruNz$lX9Abid=6*|Uck5@ISohfl^j$*d$w0W2>3IIw6v z(K_h{rJ5fXA{h}VQtgZ2w25k{v%et&D8EfD2YZ%cpxb3lhn<@*Kt6nS{HV`w<P9>C86xR@o z+opTUmCg7oB%VG&hA(+#R^bGky;r*GPz}ExOKFcCEsiM2Sm*nVuDV@rt~q6wcG=

-5s=px?b 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 %sHI;;YUBgnSf^=K2=^Sh$vq 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%0Al_fg`rYm<{G;`;lFr>5h-Yd_?px9AL&xk{VOGja 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

ouk}I0y)?rsws$J&X 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

( z6I2V#oSL?Ixofj)*r0nnOY7ntu+H8(*Yx=6cRTYlf?1othmy{6D=1W=C6GIs%>1m= zEdijRB55A$+g-NOf)H8xFg!~M|I91#yvPms2oN^`=HwGN1M*#o!`PG&YP7jIbTpUS zv0=wQh@-lh|3Lphi2rmw`t^pVZ8+##oE|DsbT@`}`-^K@<6{`W#sHBsP8KrrnNBJZ z2dU5 zFh2_(fPjGi{npl22(XB?@ICwh5nP&ntI>vDd>ojgw2*0|C-*$ibqjS3@En)VpC1-n zme74qJzz4UulIs?^5UCRtyAV;x!;$I4&A z?9D`#H!gOp%w%^mIsDZSdHE;}Ns(@WqgNR41Y^U3l$VESg9n$$U%Y))YVCy#1z2GX zB%fxQtz)Qi?63MZ{Wh_YgDBeGSfxr$?iA)Z{@J5ESV%J8M{L4TAYwSJ*aK|Sx2)oRKII}dHSsFu!H?c zVf~joej!V-p0kI3#$IoG@bbUM;mT2V#}jbg#H7bB#c_Xd`<@g}B+?sK`}0W;vL0l% zE^ON2wFAA6re8AH)NN0C>hMKeuaKFP? zg&o;i0ujh7Gjz}|! zF4so(tyeb#fQ=j#WU!aL(%AjKCJF}AUnOB4h@|p0KR=!T;kvLPQ~$#Xs-H17VqkT% zkl%cwA+f5-8>u=^8_%{K_@Pf;vlHLnw1>YN%O+eaRrme-oegVZJ+D9~k77Id$3tty zZF}(Ef$m{<+wbZq5#fvVJSyXV?Tq=KE5Yl$(e^&E1^RR}t z)lfHv&PUUee7n~2_%yI_V`i=IRRSHI(=Dy)HZOU?dXTD!z|^bjpM7|-XBlQ!H&few zttUPMHn$MwPF^}@cP9_6b8cXrz9-ZDBLS*vQj=g&`%2d`e|9l$qIB}MS~h~8#^EHWwTs)R1GeSYSs($UIgi3;Ev&~v?hLK}FvjFR z@{VU*f~iz5Y;35Dw-<_?#NQ797(TcSEZj$GUhH81A-L z)B5xDeLrU>On2$<+e1s=TS*D<=(h{M`zvcb?Eszi-lpl@xS!1hxg&{rt5bW+i=DlbQ-&l zdu6@!cev$+&G&mY{xBFkY5e@Kve9QFu`h@JrlmFM_?uF^jMC%$;XfxTs+f7_tV6HI zyv=8VC>R?3fZgIop>AyIF&^-W*jl;PJdqIDG!VLRtIWwWNGsQ>Q1Glm=K-=G91z|g z`Y}t-P28`;52r!>)W27Xit@ccE~h&U_W&N&^m?!&=j#!$lL(yOQN9lc0PF2^9mm+P zU`;jhchr2WAplZB$i>{Q&UQigUTw&q_ey`4HIbhs{N(4n<@s>t&zdfIG@T~a??Uj) z)CE|zI@a*WkLVJ%JGE?K%h-RAC!E=QYFiuIvi}`;rOteRcjvwxJ9RF|pRz&*fn^@5 zpoS;^QVG4Tq(QY$hk8BrRs=xqpo#?Tl7sFU)zCH-e~)40llAVkJf|F*vWuosFGxyk#I| zLA3IKEV59S1Q2w!(HW2xaEA?pBFvS`wQClm0tkZ$T|g>446Ie!)}<>qaQnDY*$T`$3!J{<8wWky2Yfv|VMsyOces_ zbBpWqFeg11Sn#xe0ZCO6+w7xdFgg0Ezq5CPD^~JlnBAeSINn)`)Pu%00Ac&D+IH~9 z!nSZ&`#jY?cXGXuI@b)-yQ*lCAjaq(~dvHU4zM*+P$-4i?4w(Hy zmWMUl{&xY(g@M~ag2enP8~bOe1*4}Gpzu+l^G%|cI2PA} z@e~#74gM;6E&MPMgn1cH@8B({4SA zg9pC~tj^x6R9A(KZWSVN?qgJSvZ?kt9X3RN`hV4uS3!TXWs^~=)xxdI8+P>J#Jb;S zCDp6p-(iDY*ElD=lL;`C8vCu!xu4TqqpP5%x4gHs**CGqe(9SlZOqtD{N1uuan&@w zK{8q16KrODFbRZI2UENMI2A}H@$tQNcHP(gJfTq6tW=o~Pj1+9 zb1D+x)`fu`9Zl`npAKxXzp!M5c8GuU8Qh1aoqF+PUQPQ8j{?%q4^ zfbB-C&eTe~gJ z5SzxWh%2GXpM=rtobR<0+Sf;4OoLk^20w$qqge619Aw0JgP@%N5PrNa4Tsgqki^@b z?Cc1G08j83JiX=>J|k2=XZ;^F)%}^jnfaNGq}PCr{o-z_jvV9BXg6$qao3h!3?dqJ z{E`Fsx2LMM2XJ{lDFn!|+htQ_@NCBu^GbRx(er^onO$>m{Y*pgosKVa`KHH<_))H| z6`etzBOQhe_rW66nep{R-CyD#ZiZSb?m+^rG)`zf?m**banOVCdzoGJ81BU~W0>W; zK}vb}Z~RQWo7#MmYK0K)2rkrKP2;W=*@Js~cJgGObmJ9#U6mGd`}t&QEl;vpy0Gck z4^5tUC0=RvfmVQrH~nCUE!&$tu^2JfDe^qXgO-(?vS@>;3SI*O5~0j|Y7OxMKvc}W zB=CcT@S48AlAU9u7Dt{HO8yuwbUIyqEtP5MaCzy$QcB|)<3}sPwS>9HL6SMUiO~r1OJ^JSO zy6cs1toC%aQ(Eu&P+|95J)7L0Yu^*ba^l~vk9m$K@y;n6uS}~b%)21{txm32@lxrF zXa$lD18ifXZV=cI{>FZk_i#MSg)cF|gEHo>{bPBEj&Th#dv%!mD zg><}^+36QIZ2#8GgLfcZ=3c#Pww{;&>b-zYJxXe@$rtw)0%yn$L)1YlA^p4%09msB zcoY%_?CBspwI;-&);L_rKOHU(>XnFru#VRsbZjwmpnbtBXRqua0pX}P+evKpv!K@x z0zx047X-bpKgtCtaeiFB!HC7d&!@S~;TN0;RVm+3 zGj3K=4=tEi`MW^qjS&xRMH$5K`%J^UTmn#VGwK0U3orvbv3PBT&dYKVMkeH!|}^eXbc7XY26x`qw^bEVE{O~lQD?jYx%Z6nXPc)SAuvKy6x zt12mC09ylSt24fQt{yz!Y4@M3g$)G!LKt&hav;86%Jx<(uH2b^SmA4-AUg2s^7WSP z!_@D22h}(MftARH#E2$Z{3yC{R~uB&O}X~5;lOC1^y(EKjRZ}1zbg#~2J>G84ufQe zJrR#?ABDZ`V|Cm6*^$*8q#jc=Puc)DegizMQ^{>jRagFq#}U~;N(i5cwxhK=M(GnYPuK6tkMQe zYpZQbKfKz0z)m|W^&H?o@O|nI;wc7_?+VpnjF|X6>f4o)6kSNhIMJXbu1V+lAjl?V z!yOY*m~kdR7=A+Q35?e~9&gw6th%QfS`TEzOb0Hx?-lu>$B#+f&i~+)?ZyXIGz#nZ z`@r06c`Fwo&m+050Mm!D0L0P1gu5s=)VETd14aR;PENkbf?1OT<;6R>x(PflHU#F^ zQ>n&MeBA#Jqo4usa#+VEH;~&#C`K-8%4O-dffLv0N0wa=I_qpu)QBCkx|Ar}TV8Z+ zF7z`yPx$jqQ``L0o+S?IVEs#Hgmv%-E9Dyqef4G`ZG#-y@w1`uuyYie=*$EuYsNEd zQIIWo_gKJ7Y;6!?EcZg{HHhxa=BXV!K2#+g^!j1_Ct6Esw^|MY_O0393JDq&KTq1I zXbt?&9z5EY5RHC0Ongw|qncmO_Yv#)!~ZfG04=Q61VLUTLO*Z?8W;$z)*}Qgs@N69 z6|fe#1I<=D=m=OK$}wUffzuv|{MMl;hYcn3TU5|s`(yIax!lrN0F=7$AS?kD+UoPL zPR6BWeP#L{A|5rhpZM8$$`=QTx(rk@RfhwS1%eB~kPV495=rI4Em$%q^25q*ED33O z+mV_FwWL3hKDq>ccEgN%ac!x#eKspJ2SfT&EOLo>!<(oRjU z=US=EhlSczfS7bI4d{Fjv;c6m!sQUO%0=bpXCIV7vzoRv7t;?+itq%9=|q?%^^UhK z>y>uq@pE=?>ycN?v90_tUH`k=s%*#KEMy)6Qk578TLqoEl&g3L8;?5({C#XEA0OIm z;uWhO?8Q-`3WeWcN^rP{u~SVMq3ndf9lxI`*npI2&7on95mK%0=K%%8+^aeNpe;W) zP4;T37J%*ukDT`k)RR4P-mWLm?7Nx`pX%7&+eb3%Nj~xC_`Tpyj=CU3Ay5>*8`TY! zFCky2BTJg2iP6fe^;l%bkDauKcXzDwQ~&zAetyQZ z=OR1#+Q4?-8;iMr{K8Px6=GG}&-88X=#F>XKi8AG;C#5$c!m}r-t6(ssWm+S?f$2^ z=Bp&lJO8w=Br44We=geZ_KIxlm9E8yg|-Ex{OCz$hRm&3n5lm+t;0PCw#Cu8m1VnO z#SPF40Sq&%?bq5izgDQNNEL(yDyv#2UOHj5L1B;HK6GHT<==O1tF!*RpYdvbk=w$9 z&45{D>|o-cv}3OhG)|gaUE0=9I^wRxN2x?**m=+HSDLHR7Or`fmmdU@9a7NJJ@9ur zc`%b|AM;{(Mrh_6pb$*!0H@AM*QK`jDNBEjVu9nRxeYtLX@k=}+xvXtz_sOo1hJ*L z#A{j|h+K2)z7j;cN?bf05W^clS{gy69HX7hV0wexdHT@f7K(`9=D~BltIoHjj1H)v z{9dGUe-2+}Dl`B_JZm|A;x}+VNE}17MRpec2(94NxCk5yRNMRxMsyMp5vb}xN*fh8 zQ)Ua%$pp0?LUI7ccYHiWfZ*#x)aUg4@BUDuu7o zSS!LV3O$cM7>bD_wi3qjvsVP-F<>?iU8hoJP%DXx5i$|J@IEpCNBoI4+`vz zpREO78zeOTylbPkV-+?CiZMr8dAwyyrRQLJdN{S{qMv!J%^eU7Uuj#iSIE&eYB>T# z`tk0)NA}#a7i>13*)q;-b7RA1%UCwP?CBGLj-BlJf$>0%UI4^1lD!22hxz3~2&n%xY2Z9tWPE|jA57hP8$>(yTLr<&!M7WU5_@q^jw)ENcjEI0T4xBQr@@-mh zB*dG1PzwzITctFfTYmYouLIc&_Aq_}%( z&F6!01@pJT%Uyj9PJg{~j;@<4wRt)GhySt=xGFp;&EAP^*=wkERZda~VQ>jjh&pp^ zKmd6XyJ<04)1W5tBS^hJKhbg?lDF=3X~39-&#r^fAKd< z?GMX;8icS&*_B+baHhVVq$oH&#KfAbS)%*#Ut0^}s%tz)mxWxm34IQlx+u zG!k~d_1la~7*?Q%Md;f2_ul*MM5~AR3OxFkaR@*!H*EiniQ0DPe9u3Ml~6EAD(*0k z5AxKkX%_pTvr*eXb**N-?=`eKil_*@4xAq=KL-c@7)xKh*a;1O; z>k@yC+dt?8E)6bWJ9W(xS(Q5aO&F(DIFEghS(X&meKxYmS97ln=N@ENcK@G`)bVLO z<*)bevv4=?L4V)R0e6QH)K?R&rvY-Ohnc0H=Q@YopEyYI-xa{N`O~I?7qjus_dGG5 zbYST3}Ka=yJh~AAgou9n&pDdUnIMPj%ZwqSAlLQUWS`b}V9(531m;k%Yi6{xbZ{WTh^T z2vxPv1yYXnB#u}N7V3a^05;@T+QzM|unCf&*?-xehXb3r7bR`9L{7IY_RozjM*aX2 za%tTc{YCooflJdl@9*r%AmGw$M@j_QJwNZBY#)2>l;4NMj;_z7beQZ0K1wv}DsEzw z)ad3iTk2iJJPf&Ij|tZj&HZ~%x3o^0AC^JDM&rzt^`4JHhvRryb5oZ9VBQQ4ing~B zxYPP;v0BT|`nh^oNB|1qF;;_F1*hh#mPzQqa>n|hyTmg?PX)~es`&cad67iRJHxiN zSj&RP_dE{V4$ogw;dzLstY+4|=qVu(X5pQ}Nm%(kq_|=0Y~Vm}Hccd))V<`O!tZ`> zJJ8`z{At(j{r#ae{C967YwvLhS>StL?5SgIncsuQ+II0zPTK8XJrWm*Vr%1a+ZKHX z-{0%YEp~D>wG)@O{M^P;hCKPl16xj0n;m$Jej;=R?Ze!<9yfDO&V_@<=Bv^A>;&Hf zI4&rQ;uu?fS%s0EpZW3;fVrk32*$g%0``IvEW941&RM6W>`krb)h(XG2=5$va^2eOSc;WyGZV|Q>km3hJfQkd+}!Bd zd=dOH^Q6#z0~I?#ukF=%lKr9AUem0f>K+Y$k^e~dRQr$vOYI+u2uNLDR%lrJ-(#du z!UtH{e3oj~zg#Y@+i9QX%itGL$%tNb%J^xw6G4soV)=NaW8*Tt`M0hqRtiCd?%}N=2-01}l05 z0I%e_5o(ZlAUM!?Gj;e8*)aFd@~~!iPUO4zTR9QZQls zqIopiCFi~Jxfb{%00wrj5jcT`D>>d(sBX*qp_1Th01d2KVGr79H?6iTyi#gO8(i|h zi1F;Keb5=Hwd-oHul(!Nud}cv!;BO5zn_W7fA)#jj%la87u z*q{kxdXP?jXS+$`v40;v3gIX6|C?x6w z5pymEUf z#TW;!K{3XIo^E7et0z}Fr`pu$l(vW+s61S$<8ZfpWXWftdx&Z>E3>SXm1pJx(TjsX zUd!3}N9oVcT!}Bj9VFTvFg!{%RuRCw*pPj!;wGB$V;uUL0tqSh$o?@0XM?$z43_+P z@pbjHO-b;!F$_`{ia6V{UcY0bcg9}5vn}C2f073hw%`94by82-Ev%E#zxO}8@nT!R zWaZDBHPH^{H@}D_&wlwQXYBUsk+sh?M5x=kGO#@d(%om90_KTV)!kS8Y$gtp@jy5+ zu=das|Cjz89xs$^sp4^Zz8&va&F@NlEe~_{G_h~~ z_q%GD!>`J}s%-SR=R$v82Y){e;KH}{Jn(=9(94* z{x!#1vOF#uLCRajL&tyjbCGgif~O80fEPSoIv^NqG%b4Tt#9n|_&&9KYQbkdJ^n_Exhov50?SHNZb#Bc@r7pj}q%Y z#mK8^Q{-A59G-s8KjY0Kt%0&LzUl^{DO$gX!5{`ga)-b}MNjaj9Vw$Fe^3A}d5KeTZu8o!22=%c@ijK)RxQEeB&n~7i zlj$@&cF>-ws_n1$panQ@l>*P`1gsVf@nt%mT+x6ak!^YPSgZ=|HK%B~TE}heb!tSbmg~B z_3XiWBN6A~Lw^s9zh0UjBI zb6>@WpP02)4)V?3~HK48Q%zoF@9pi z2chF3M@~ixbOvDApY`AOTM4X4hB~c>&KN~he0%9RWGNYJcqWh~@vCS|Yw|^bCnT+w z52`S#aDYGieWvki+Y{^j-PD%-P|4Cc`0j6a<>bmvdwRXF`6owVi{e`cLyT34nI|g= zVnH%&9?#VA%>m)x2ErT(9Nt5-R%x`Yb=HmWGvZsR2%d~FO9yw=o%P;6?es<1y5Vav zm1C<*C0oHZM9n&$TywvJjk`5F?YZcMZ8qyNyAdb0zswZdty9UR6q{sBu<9~SE5#$@ z=g;Z>QcrDGVN}2-$JM!r$bjhDhM&dHE5)eTfrDh52chc6OM*^9I!eO|Hf!yE_sI=y z2XBzn>9C@D%CVk*lghxZankeU#L??7fBC7MIlE~G`(N09|L$|a?9yHXZZ?_|n@pw?9ajY7)?}yj+rkdmP?~u`@D| zRS&B?s0r2tGC;(%VKEvCt>Btg>{`vp+TFG`05;JAVBQC#)xQRI>|#8%;jnMX2c`BZ zS6n;~(9F5VzgdK~eUDYXu(PT8_-068%u}?|!(K4+W~3=S_|l%09Iz)dsx}^&&@TI( zyFtg9ea{*ygLt2J!o}~r6iK>LbDyE~Dlo{ITRZ#NU z%Tzfhw=01sD${r`4&oWZKt!XXB5CF`7|4Xy{rvZQO^`{w{eSN|XlU5zgN0hg5bih+ z$Sorv#6ES+-vgu?WNGOX4E?f9u`FHIwend}O)&!53xKl32=h^RuT)OOdV3+yW>$U| zY>uh0c%K>VeA@7S-u96pb}%&7cqP;4V6b@*|tKg%rhxPQPG=#Ke8xa|OQ&#PJHuER@x zJ3N|M+pCr2+b~zh9yw=}#9uSB^xp%E+1x+p=x!QXu|;JEe_I3wzW+5E2y)PTFSB?a zM4504)IUcv{}QwQl{#ab$Jl{a>ubME7YM?2X}}Z6d&eN3)+C694D`n3o{c@*)W?wx z1|3`O#`aQYVB3Drwzivg>mao+ZZPYl!f8nQxOcy9N1nuEuO=VdIU{>zxfx!OK>#jF`MyWv*X4!>I3F@K+gxy>hgb~K-h z{Adn-e{yzUCmaZFZ6Yeg*mGsCHoErrUoUKYA=fG!-8MKB!0$Tbypxf57w9UqPc?(A z^PWGWx<4bjtO9^_5ug2^$OtMryAoHW<3EQp&#Erxa&gViy}!HOQ!SgH{{MJ-?`OO3 z>soA`-pf7b_KI83I|vYFvY-7NJ1?F|X3}Po%;XQq`z^^!X5!2|KRM4$ zmSlBVlEvOh5MTj707S<{y}jSld(B$=3tJvq6mai3-|wgFz1A*k*WljtAUHwByyZ;R zRGX+$Y8|UE?%4Tt>bScm9wn*XmKdz+DtN&I8mlZ#hlUq%GTp<$1m62%lV`VML)RqD zE^sT21d$7+bQEb68MW5MsG}jGbM4>5@79D?*=p%Na1FYWrPg@tnIxLQ8tUn$h5seL z+zqslU2m8jh|c&?e$XNi>6jpxA`Xp(mZbsJTIow_08+=a2ZDoq-%<-bI}#{8s-yQr z3>$a04M-+-L{VHQBQX|4NkfaIoT`i@-}mrP2%`j(=V+^6I+?_G`gY*M>+5)bej8o- zOigT6GfP?prF)fjs}byQug9a}G)9j43jsx0M zu)6BBM{6#fh@kdDomZw5=~hatVSlfzdSj_+zRtQUNJh85pshUdNg{jUI`p>SU$jE2;^ z2oB`@%lwX(w`0aiMI!3^mFR<7G*rOiqGJot$f++<#bZ`DcF1R13<=x~(4J zV%u`PHlRoOCNXmj<4HfR5LQmx@8o5|BO$1hw?=j(9d1)TQ>sDZM29@i{DF>B#Ve23 z+449ceW`~v9_t|}bDjHObHzEM#2xSZgxzIL*MnJ9|5W1H8DW zCjzK)PkZ8iJmT-2UD+R_EZ_bPDh=(Xnm}O~cc$mNGq`DvIq=t~eLbij!`2ZK`3I2B;$SNji=2 zVgCGuCX#e`8q3_F0}AoI`k#(skWTA?iTfntjb!^?p8VjsFawJPJC_VkhqAJpx zD7&n_#mzsHN}NElZ?D_hWL3E>)+^`MUTK=@S!^gVZQd89La$PgZw;cSsd;UVCO&(vx|8odC!Ll z0(KJMzrp>GZHn;TKTBht=J&jDCyzxp*N5*MJEp$$qS=PSdr#qF;bXpslQwfNbW9tf zcV`l7?`N!wKtf9$6{6CSM<=5;h^ZEA;^{v+g?nqW$Yt|Zd7X~;qMh2b83NtB%RqN{ zr=mq2WF9me5H4N*C8v%>47GmCIg<)Ii_LA{DUpp4(D!$Zxv6r$mo`-m%sc3ej|~TK zWwD8uKPS@caoX+n#Y6bk{s`_YSFu9iH&@`G+SFf?s}qRejt9y+7uW0?}` zoRY9pRB1N?(}yi9{M3LZ74cPkO;ZldpKVzl+7(o;!HxWx8a#*G*5<3hW{MDN6tyCc zDjbXsgsjE7qx+^i8Yl7b)A=U5NRNgsJy`s_VI3~h@c^PU1^apLrHS#U&!+f}b)3(Z z5Iy3vnE}!U2}Qk#33A03o!8KI+rD^~Pg$0@bf5q+hw<2;=xBc(+KDvR54- zn2Y97`=VnJ9N!)XkZhcFYI!vAv1gHBiaznHd(+sg6>)%{{rc4c?h|-^?_C0ItmDt0 z-+?dJTe!DXMNiRZxqRdC(CY4Vk>`3ObwI_|dZVPmmjN}s9G7_yft7&z67;#Ll2l9j-q7l}a3c((rnmwv%p0a{?UYKjn$* zwVD}g0RcA|OduGb^VjI&pcO4@vY>#2waV9H)qX7ZMGaaP@9`9_M>TE8guLFn9*v|yKbK^ zI(_^h=f8y1th~bGfQPPX3r1XzwFjJ{4mmE(hIc32mKSc(s3|usJw{L1DcVxlD|m>g z^`SG)(?;#?_FAEc9Fcm8%b)Ux_!;U;RgqGw>P+^tp+-G0r74B(LcM;}3k^fkZe(j7j3s#aqJCWB+BJF|af_a97xqZDR{3{6RHm`TSyP=xJx_;tvt*HwHo<0jsbRgs%F$Ln;Ud!j*wCg$<&~@=H`Oz;kCfD5Hv#FxL$!D|By{6+S5+o^8f-&X7B_$go2(GO)tj5Drb>pcy zbrdwQM&tSG3(n}*7sjHPqEVO#woLWy^+ye|udXRm258_5pSKX2^r7!?54Nvlt?r_C zrWXsZEhG6{2sI5m8RR|PpXN1yds@oZw8#y?j_iFP1< zDrh~>9iD-KZ--GJGiuOq#ruM&XBvnM29cr7iH`Vf;#`ExZEsJ&Y7a(6+sJnUxV%`# zjg0Pk&P28jM1pv#m+Q7oP;)(xpL6XZBo1Gl=Jzgnu))vWTH%^Z^DOh9g*ywTH6oC$ z=U3;23isAO%UEZS9HZrDRVRDMW1=k*2lA4wzyE`C2Ea8ETt@2AkCrUzCI(%`4jw*S2Hy$9=5?!iu}sc_qwdgU4x(8D&(6a(7a7H z{DjvuDSDVl33K;28yn#4b7^`*W7V9q{>ayLfKa9p43RU z8Wvs2-=O~>P;TGq7#I=>al6|VL6F+2MR1jR6)*_6tx!`y)oOf5m;$n&LrrUql%3XS zaFg35cuv;5IFtzE^s!#Ndwvbm>453MKUCeAq7wu}>AK&FY`^-{1X6ca(98Y0ceaMo z17}pZ07zTiAQ>kjU4K}&X@(wxVS(uSO52{f`MFcmruAsfKPHHhL%!`qn*GN;Xx!q@ zs(6s#bn~5@0qt_GgxtGjn^B;m^R;&}j-b&b3D)~Hhg3>?&3lGgfv!*w4ci{ zxC_8#^#fK3+G<;RL1I6uC-BhGOJJzkY4CaYyZoIHKRcVrSrL&639S&1&uX0nQo_^t zVZx{yQB^P)Fcl7$#Aq&7klUy@{gV=SATyLau;oXRPNk@Y+zU=VTYBIol+kqZJgOiU zOIOjDY6?6%oT*xtvk7&qZAZG{>OW8pU%4RPsK;suq^)oNi90!EwdJ`a0j#1u)kbbP z6UfA_)mz`lIh(ENYYIiHMN8k`ddLGVWjReGBTK6DJJbSZ0OPSxPRrQ66NZaS_?_P5 zwv$7*(3zB<${iHA9?I|ghkSIPj=ZsowN$a&wb{{H1Fz)@%b(DAUv65Cy`y3k3r!`OwL&x{&p>L?qGI7sk&o=J1lHy7856&NmRFp)Xdbf@9TTUKQ-H<^?RZ=?dD%g_&i9YYM_OsZu2BkDikH4)x zZr&X!TNc~SCGK1I;Y)*2^z%Iuyf<$VECqLXEu6SXR@578W0va_BtZPifCq0%!+t=c zmYdeCA!*U&4>zn5TmAA6f4pdsfWp0+O?b54@>m-Ur9Y%*N3<_#5=^-8Pzv;oo!wd>!^_tqM*()~1lE61{B7)MLstF@?UC!(Y zwF@d5D?1gK@}sg=vw}|-?@o#5+Z}f!LVG7?uJ&1-!H#U027A#_@9T?Vnrq%a5JNs& z!T4whKYXoZKksPNyV0OS8y!gEC6a^ebc4XHf^!eE#&q2R*F#Q>3^{l<~~rG-mG? zaAH>v&aG$Rsk?BfFJY>+_ZExz=QEp_9Q5ITcy0`Td1VFNP}@?cn&w)ezl-e*m`bGT zh{AksQ}gR_IRo+}Fh=qaoAG1wG9CI**FdeW>(mW(7dp0PFfE3Tqbxb(LXKy}+6zUt z)sew>_elY?<4j?6r9C3t9vk*z-$ZA!N!A|giJ3@;rtAenWu($^+`G4q%a`U64u>s- ztOA+)5AsBfc_dRwvvek#UatRZRd?G8{9x^VlFL#ru#GYly7PEUNT@@mpn8FNOF959 zbL>F@2DOhoZpCZz?^3Baf&ss&05(iBz-<*B+Ag1uOj!jdyB~+LRa#&QP~1uh0C}BM zgTipWVYeYb>jzJ|oOT;k%BFoNtW=%;_O7F9s1`&`1`GERNzYZ`syJL|R@)bi)+SQjUt7 zjo?(zEH~P#5tc+LezL9|het8{yLm)+(K+08`seHOWLwFOwTMW#Mrd6IT8dv1hlvix zu$|4KKI=FjJ&(jv^c|WG)gW`~xSesLa&8_?H`TC`qT5EKDsL`rYV{qLD=jqdaX)Do zhxYif#rIH%aGnNpPrQo<1UJWdz}E$;`0VYrGh5zABi0~@+w?R{Q0ek=2^0N;*gFzM zb+v;7=@2Ln#Q=PX?bNd$gZb+4#<`CG7+|N|6ZP7v1p}a)Yhia%3e8@ zyEI&fd{%TMrG>o&K&>as{&ceZQe zXuac3*uO=i6h7`X*3nga756E1GKQ_U@+Q)f+#NzOUqg#vZ18EGsZXocouU+!rr)VT zTuN+8$(iz+=4PxoodGU_l(n-3I@K=vw4aaKSXr-;;RcbO^y3cMYolAi@Yn!8{Dh2K zmCN2PDsxTj2}CiKYU47Q(t5>ZQJJA2Z3oZPH=m4SYqNn_I^SalVwfQlJ-bThdw^fM z+lAtVstu$P$gL`>R(2b(YVbLBj_$>SyAROrsi4Nak&w;c-ZYlaZ6dhSV}0^fg;Do< zOnO%p^IDVezJTfNw`p{xuGzfWwnAM!Z*99~#iL5Ci0UZ4q5I}i>%H^CTFHk2InBWKW=2t($2-dZB7_h@GhyQ~JpR7rO@ zEywOvf`k2DRNrq}>MVG`j}G@Jf3=C8V-YmU4eMIzKN3f2p^hMJMck)KcrU*H${Joc z79~Mz;;Scm@XLD{Q&Jr*1n|U;C@$a1A`ILw&qy zPF#w7t(z@GxMx)j@=`?-!9e4ILzFZSDE5>OrE6{6eP`ZuPZZ6nooQM-wdw#`{w~Vr z>W+TMw8uKXiX@z-L$OBEamTUWilUTB3>0S9w7=pV{ystf-u@uwKCh!opgj44AJ-m~ zktkICD9NXHWN<{O&9X&z~MpWB>sF|4BqaR5v;; zJe>&Rz2!U_nvWu%xJy#NHM38_lRj*}*Dz2TeJWs5A;0h}b^#=08`>|B6mn^?ttyw*RfJC8CB61581awVZ&|^vtE+-$( zzYm27R#Xx0b6d5y!X+}060Y#}@M%hJE5OsJ$F3@_^A7W_J?NUYc9{%P;V|{C%E{}n zTj5Z=h1`w0BjDk6{;A%t8_Ur_GL^w@$0RjPJVN1nzcW=(VPbnp6|AY^U1CCFqr)Ei z9=C>pBs`Xb)IKN&7oK*ed9~7A>&tC!bWBaqwC2@~5paOf!z;@s7*xDevmrXQmNU2| z6yv2ywQT5%+b>~`fXN2e$+Bv*S#E}4fci&A$^ck7sxAsNxPj?Ggd^u~(D!em@ zszQU3uN@B9{BZS1%gNVlv?YILmk(7M0}t0Tm?m>suOq_uS8sFgUOLvxbi%wCg@zC#BSOUCiZjZJ*9(lH}d zz1g+`9gWOY`;XqC9l*XwfU?+RK`X)hf? zakKD$ zzezH`+0_;j$|?J4=-YLSx&k=J_kHivf)xs?WAg2rMf}%K58&+897=TD?W+#>Xi0LF z_P_`b-+lXB$s$X4YS*pX#HAKp-igw^D(;?NGC>*Z`nm5!3frpKSHz&@NYqvzRcs=i zx>c&zI~I+ywyuOzsB6W?iqaA!#5z9gAC4pHYvTH51x2!eN$%T)1xI}6Awlu!t{;80 z2U7zcWEyk|+~WZ6d(=;x5b)uXt*X;LOFO-}<;<~ont=J_@h|~-13z9aqU3HMX;Ueq zv|D-WPH3z<6ZF*G;^o>)@lUlVp#eW_os>x%)}dgktZ57Ga&a8hRz)2e58I}PpeS0a z)v!u>Rl?^tig2xY5vdX^XpjyW^vqxY1JMpXx>hmG{GMb8S8rD_J?1yDqUJsDye6UH zu}gG_#|I;*2`B~vcxq1^=dKm4PNBO=n|7YhHQ}~m(#mH|i?Y@5+wDWB;qb3e!36RxCpN383GyMi}-h?Au=T#IV)iJOtJ1xx@KmV+^ zH-yx@7l#hVF+CJPGUmts{ewH`jRtU%;POAcyM)lV7mJw&`chnrkRO3KLH1f32?Dx- zwAuhOD0UkJ7GC`LlTFMdy+}9x`0~yO9;{bzeXfRf(s`cRgQlfiaZcY^}O!g#Dt5hwFZI$u_DmuGGjfo99A}XFVtOZF!bB`7BBr_^CI)&zIZHpjS{F|3m#dr#-DRQAhrOp9G zRfM{gvywuf4x?MWwtgoXpf({zBXo#OvX#%)C^;Gk%F)Szs>feC^i8sJn>npn)RKqf zntCQ4tA34mOvp!}t{~ZLz2HoQ>Jq`0X*7)OOT38AKQbO9_>l-t!wTi>1%1xY8Wncc z@^or^xAOZ9o8%>lS`CpOWeit1SxadO$-z578a}4&bCelx}Oa6 zLsiYk+7@Bi<{HlOW%myG@zFzPrh!^Kn_DGpJb2)UJr!%XbhCjEZ`3fD=)no1)_2w% zq+89^&1mW9wD^3cz7=*1w9*yox!2T3^+9X6M*g#^vDw18sxg`zK}V}jqf)G!n34M0 zgJkvf09h81v!+#ES}b5}M+}eVGGvi`_;h^@A)e16zMGH6;OB2H;kn~+jKsTmjZQ*> zCd$8eRy~ILMZYXuzTLF}G9FrgiKcY7I@YTz!>P2BU*mz3PQj5U=(2yb*OsC&=Ho?~ zh(2^Q;MCFBI9WccTKM?9<;xBD%Pwq2vgj>w4Ik$<7c{+&?`79kf+_?gV`R)lKFclD zYm^<;&nnOBu-AnJKFe~chOh66;cvg#Kr7_*m#Z3Fz2P!MVhpDBq5&ofCrXDwxyiIM z;7d-=*8YpO)oj?PQr=DF8ug#rYR%GMtFQ8z5AmG|?z~$b^iBqF?sf@lxguUV9N}5u zeQgqGd)$^LykBVGkB+ABDH*CO-gR=KyzdjH02!ju%-I;@<=ZtpKOMt^2KMmoUg=n2oreHN z1IZ9-8N*j~cX1Tof`uDovT={KyhRs1cy4dl z9Ljy#g+Kq=0Dk!LDuN_Cz0^NtN@l&}}2+YKW#vI8|TeAS$&!(%)fz|O%S z;z2sDaTngbRKU}_Qpn%l=3VIG_Ps0yd!tZhRJA~A6?Xf4v>h$$9?PgDm%#3)XV&rG zzc7M0&v%?4<@B+n>9xFaZyN<(qTndk^@!8$08J|OxvZn7Mo^}jF+~9lxhR*_TRZbZ@{Bf700rb&$W=w6@)V%ZJKr z!vUq5yr@Z<8VychY=x(D9TO1p7_oC1fz|p;hFXKK#~pV@N@B>-E!PF86QQaItWjz?-3E21 zWzkRYRbQx^o__Iu9phCW_7aWeh?Xz;JJv5ORkbpmX?5NijMUz8M0Nc8JjPtr&usm= z9Y@GQmo>VVtd8tCxSPn4ET*QGvuj->criYjt=gF9Z_N0xLC`YT!`~&K7$nmzl9dhh z`H(Esun}^`d=7D+#zxcVddhfae*kaY=wK-9$3)7H1+tk4*K4dlU@d~dpa-j44a;Ln z@h-WK24+_Tiyv%RVPAYOj;%|1Gzd(`zBz!ok5?SMh7BpDDUVHdevapX4x;gC8zDN@ zx^n1hQM%T)%IY*X>{%L;+4&CYn(jyzr2JX66TZ$IyWzsrv=4)QQ9PuBi6?#d zr!zVHuYWv&L&Z&8%T^H@;~CW4@0ALChrJHdlsX~bJ8?XK;#ICUjc;fl4d)|;DwSV! zTkS_=ANPACgu*p4(LrgX%P14AbB_cF>SVZ5{f;2x4n4&a1VGa@X75w zYTH%odGC+-5$)7a)=(gVlMTMp>$f^c^M2;Y5IqF1{bb~en>9Se|NaG8^H7f$PpWQ* z4#YZ2Mx1fC4Nd!0`e?#!9RVSU+w{bdqhk$Dd(H5vyJ35SYtYYo#6Q#6Sg|Kf4%~d# z5lWGMef4I`ByL@X|2sDNI~nXCCq~%Sy&S?+pus!p(^On+tHxowg*(9N*~rN*WI2Z;kenwUb5RkGRyD1wh0$Mzc0f3OoyPbz9e;3kecjx z30@j_c25LbUuXiTfO!{d1P1kl3yb{RPM*#`$TL$@mp~h%Y1++(8^1lXh24`;y!6x< zUZ17or6WVcsm@njk)?7(HX4rhh0T4?xOJ{$gAT^CPg1^33opyg%kXACYguKvr=XNs z!@}*F9Z@~^FGa=ybT4*r=tv5SOL@HdNdbR&I*w@AX9bBWu9Hr~90B>!T>J;a#5eU~N`miX$G}@)fRKVIhZ_iip!l49y_}VHC?F?ck@8G?~3R=87o3B?4 zz}E?4B{i+7o-Nv;CT(eepjpF&q+IEzO;#bfI?Dpn1PwN-p6{^3V-fv&QGNde`->#M zy)ILG49eN!uiEwfzCk=K4?AKHU+M!R<{yA zsEJTeDFY%`{`b&841sVQ54fptBC3|3krVe=`;rO#TDy(%RATcAcdPC|AfRJ&1DxSD z79~)JsmA20I@RS!nX35DAMom)mdGRI4BMD)wjIYP3e)Xyl(BHN2tWq>;NmOXeOCymy8Ak4GRVtXgY+aPD)*an!<)3060ztP8 zdx;TQu920mau1*2vrZ*wc=`GN-`&{8?5m%n#YEo(lL$yD!oVlsjX_t}Q+C``-uIJ_Rf2X`uXX2OS?8`AK0o728< zZ2`|8NML=l1Xrqy9M8~(r;aiW?P`{{I?=&9S2H-hFO4g&E|5a=yY6#u>6ATf0x3zU zdY$!EF{M&0%7+)Cs6+qB(11X8L%f8&cRzI`Z!b9^sFd^$?$(UQ%iT)0WrJ)QJo zDBi-gl`i_y%3tNtr-4trS3aUarA1sC1jV!3<+-rD|GPz-RS@|yjq=+$6YfeL^@20?IPZW_*6$t`He=CpA z=JNz&E}WbSW19|j=V*`^yN+oxezm<>izg57>Ust9JX0+?!?9=xTe&7KEI0AreXS2K zpU?4bwvpx?OcTUBT&iJ)Ea{C)CF~;@e10r~pD5SbZDNZo%nI`OEX%KEOqfMoFzp9j z=WY@#x0dLf$rv?tQ0z%Lb+;92k;o`NuBgsh+d7F<034%HmNd0$G<0t@xlifEEBwpd z{XvWFqxj`H?wwNMt_QnDyC~Fo z251`(Pw?(o4?Dptos4q1iV$S;6;noZn<$YRVY17*hH3S~x~+P~l6Z7))oO1#N?YdI z#-gR(SFg2fjJawI6g73HTc*ILdM>x7dWyp}RIJ@G8`<7+gxvzP>(}PW)(%)`N&t$4 zgFv*Oz<#w{#XwI4%bWBo{H4}r)4GSlKwt zwN0wst)g%V0Flfpohv)hANApDPbA@0jZG*NHi2b@mF1u-l_Dy|GMz#Tp>WV50YM_q zZp+UDQA1mk@Vq{2lZ){1Ni0X~T^0G#fEN4zm)?G7>R;CxTcDwVYQm0vTpKmKfoh)m;NCHmkSfvKixnxTK6)A}dlA~h(@BhW7iD7faG=s`#N z`6_Nvp0DkEo|jJA5U;yy#cie`KceU1nxqKACg~g=wGb_MFwRS^-1~2ceA{tH*7fw5 zA4gI?T%*yf9&w^Ms)eW$`78IY9H=kRu_8fLi>fa@`+3y_Gg=oKHcryT1E-nXHkxwL zWuP#|pG~=?Y0iaAwDHcJ7LJYw(HpMefBW$)wzxlUthn$r8Toxd?y}10SjQ{q9G-69-vThHywI_MY;h4^SNHVizKFO{Zc{*lQ z(27keeNdxg?B|At)}=#5u*zpr=Y-xdBb7=OYDol$$T+yii#pHOCK2AvP3|dwKkRKA zP_1a#5D{Kqu#MQHw9{Ld81&)ceHvGZFbPy}4TG9SM#KEV$N&yahA}(4j9*=;SlCr* zpEf_Qbr%*+m+*!aT?wT~3B>QTB!Pxs@~WOOgLk&b zhEhHnR&&aYhbv`_#Nt@YRw%cvM1dksyZ#!Y5`#N1O=x5csH!=h8{sS}S7X(-K+)#Ybo2 z2rjxY%ikGGdl4n0J=7b=Ql*KT1k@c*+eBzm@c<6;9&A(s)+%=Y`6tKG>Q7qrIq^|w&_XLrjF2Tp=@dZ8BO4@%s$}`n6mX_@} z^0kR6KeovB`nrB3VmzDt{3l09TF7=6i+P(yxtAoMKd6XB2Z^)?Klo(j;uC9lJ$MM|>T;iFiEH$hx(>>}zgTSXr z;Ha`IIr5C;n3GOewZStL40$mbjKD>3vr^^fm1y(S$x>`0`E=L@I{64x)RCeHfNGE2 zd}omu5i4m7RqJZeCJ@kw;=T~h-p}Lp^V@jpP!gZtAXwyjPmF|dcdd-w$uJ(|8#u}R z932cH$NM=w5Jqn(fE{G^uU}Y2jdnbqidpr&hMnDev`sq)BuNfp zms_U7=OH*(F>cR^5W2kkRbLYg-l5^a7`z)zk~~cj^&;1y)4e8TN`fPTC>82yk3AZv zOX5)bypCF+Y4%%l5-NA-FgM&-ig$5xcM5&}5}w%|#Q*idhKVn|bvcWD+|QwOko#1_ zD;HM~<@r28=O6R5EW-WHXNxv+{42ZT7#Z^8%*`T}Xvde=nJ=`Eza|M-^{LElv{viOA|iP-T9v-dPH8jZH2v53|Bf zl4&i^OH$UE9w%sJtWsk~{Vo%wuxkGzfsQl-lzRy~%u^#nZAcH9R#+kVYfTH0hLZtA z(-BjfONT)du}c?Ewvyvqd^}MeUWNZ zG_9PmW7JK}G3UaHr-5U`evA`g{)(5f9pW>PZMbNRqY{B?DJShlGUUM;pJB3(2bmXa zb*_S4WI}4vJv{EW;ZCJ)1kWE#<02V%5BKcK-71|-7_VO`VZFCwQJ+}Thl$}Jk~E+Z ze)kX!d@|t0_4zU`aG#1q)q8k$mlmryxj%+^jr1gVz0K!wMv!()tx>`k>H?^eP4vaQ z))FKc+u7v?o^CeL+ojXvGiQjH?~nnP-Azj;-6rcyd+DG$lG3K3i}}rma4_c$Mw%&s+vwk z_fZ>dI>fTZiAwKahUbd=p?VXw1NnK+mbbYvWPE;Uxd&WylwCaC8zMVw;k6re8Wj&F zdB(l%hRyKE5wJg6t6)FR$eTACR+;wxjXVx>Z)dn~-@BMKCaeK9!&N`h{QaQ>4Ms2T z7$0}fU>vW`Rgn!gQ6wAj)5s0d=ueG?jUne$vqIx=Fd0FfMmf&!iG=v~!%-T@fNAs3 z6nt1ODJ)vYIziYH+5PT5Kc)yy|L)_AsWqm#wpZ?E$bkJceqFqMJ!d)0y|e>i-n+d+ zUHphnjF=cZ(gAbmnavsoyfwUhvuck2$x%N>dwtj-z^U>gR%nwFaUW7Lcq9rgU(t+V z>{!4QJN>$>G3TtSh&eXsw{}R=U*LHQ)8HnygIL`rn#j`D>|cnYjE$@B<4gGzfowQP;TJR4&IpoKYnqsU_)|Z+?zF}82L*_c82i}FRz$! z{`^4+|IO25h@*%fUnmkZcj-L+xJ5AWjVF5WTkTg+V6}_SW;47CBqPNFnRU$+9rf(v zivh%Vcqevwkl<(b@VuY7n!~kKf(bI^v2cXW*^dlK>E0tSweB)gQ44&!wddGOw3y zSd+#GrAWFo?zzc5=_raR5KHgxoABWlf!DF|sCAPRDmBz-59hNj94gi@K@hlqGKwF) zw{Ast$EHH4)qI#7_Tf$L?J~jXA3W8ILZ*Qpp0|4oJfk}kC}zr-915W?MQ}j-)tx1| zzg#!4tcj-ix9ix^lcxQy8Cj{(Ke*`hinMc8GW)RvSvhUt@)t$Cw7Uv z#7Pi`hhlWzLH^Ug58vD(;b_~SpZ@)Q*wO33-@LMj9G`8V?Z(_{1!E&!5}t|`seM5o za&$U@y?dhg>vxv9uKcc>U5obkHEEJ(BF%fYcCTV74eyM`d%KbD_%Xa0L8wb&J|4n_ z%tM0Rt^rm>kvuCdlP&j0X>)Ejjf|MwdAWm)avfI*(tEf%L$qO!_>Nu;>lqnITbR96 zq62DZ@J`v#dLj`;H3tr!9MCUwfw&Fcy6Z{N26h_y3^7H9ZNejn@uyc?T#}_P{6`R zZ#ApwlPJc_dYjI!&xjQ?r}x{>!2w7YI^KmCj_Vnf|T%d2~GV+OjQMHiql98#N+(F zis$S<3gvFLom^AEkr$TMwL(1V$!+k#R?lo%m8}K|eejdFt&+8>mTx)))dOg*z0wJq zAXxpRZ30CSr|2&P@FgP4x9-(&f*0%1fESB%PQ~s%Z-9>1k4sl_bYe}_JmU9G_2IS8 zw}}|}nTn2Vc2T)dw>k$YE~>g$(H>QjO6symqZX9Pme_QvV`^htbiPBqZ4??lBJUO^ z3C5xX2`3IF@cnaHytprb3kz)(tmDW`5c6wwT)SPtH)z;r>C8?|`!PVr^P`Wpu}$MW zMmGLT%8Qk)4xZi_!R7f12I3xUbDgImVR+PC!t;2TEa&R23|^ZpSPtgsOaw2`;J^2% zfx5qrZ|w17aa{we=)}liB=D>AoA@_R2JvWxpny*1 zHbKE*UdGL>1|7T?ch!cP>0r-T0Aq21y|QWHzqOSdx1N_P-^L7C zz+g0Ojgvf5E2pZ#L(xqk_SLSPYcHD~?9o_ay69w++S9gWcrQ<(OhBKp) z!`+sp-VPGjt=8n&eFSh_ymG04I@v_B$BS_S`sr8@4Hp3v&-2C43Km7t(3n4YDvleA z1knRgT)kJqmk&m;p5s2z(9CX>`5ZiNDsS9 zMUr6;c8&RPo##|tF!4YD!bYbK1n{dfS+v4*kaLO*MCo`#_<#=3YIJy>%4+F-)HT2w zCjd|}=X#)ny3479kx0*AgwI4LdSZ;AGUTw&83<|s}J*37*1|k^XclY-tktwwJ?q0ld zHH)L85iBonV2<`ML9`POk>C)>e3=aQC+D^h;Q9Q<{x}}+^UH-AzI-T!fB0Y>MV`}0 z){E&RohO~(L{e!2msOc3!fk$k6npYfynQuqH8%mCorvC_1gK5G*yIZ?k%SBnB(Rhz z5J-iQq`i-a-P|j+Kb24{kz^1o+?5Uiomh^JU&@VB<6->t?M-vUV)d&eJYU+M#HTlS zKiZA}Qif;h8>f45f38S}Si_E~FmB#2`CL& z9p2fAIQ?1xYxxSMCn9JPnC%=0V8=)V8>?-zf$2mCC#Ir!L<2OpR7Eo7!HzM47vB9j z+WkI$$Kmk=?^1y0x`DSYl;~r;h!FgK{e@i!($HUjP{ri9>`4`av~A!1$^^dm>wEa- zQz^X4bLi!Bjq&{7dO(0@wHh#+HO4cX8h7!td502F-i;vs^5xs8-fLr5LdA%*2TM(a zd;FLjOv97Q;_hYvo4$?>(Mq}qNMa#Vdd(Eth*sS^OEvSW721^X-97~AOJ^oS_|+#{ z@W$xi>qO;kez2VU$Y?+Eo9jr%#!;$P(bWO3JKEsGLn(7?!$Uf-1!UK@P*pz5-N1v{ z4GYC;lAtsJj4disph)*)rY~j)OABO_@*Y_xWrTu@VH8 zTEl=yG#qk(1TTCr7P2BjRjLaNeD8;s(G&II+s`WQ>cyWAj-a{6Wns@tE9)p|1S*Y5 zL3w(Dm;*Gp7ZGk5^ju0M@bya;d^bKbes@k5qJPdWBbpK;n&G=LRWwPx0dUXpXWX!cTKCZ3uOWl|wOraqZ`c6sU zsz$&Wb&k?aoE}}%1JxmAW@-=a5xsGS{hPai@P8!_fO3j0pjwW$)rHXfN6fi`lv^(xKiPYmq`!P#)|Gi($p}|e~3JqzG zyG19S#SEYC#Bk6m$2WQ4Qv^IbIr!*G2FE7@$kVW<2-J4+of9n4?J{0Ar-3eEa&r09OB+BhP@_j z+T!!HXiQXJQq*L?lnbRR9jk0sXU$qIg#ANp^at8j+#?mT8ozUlhS%4r*cjzx%!8|B zuct=B*rJ0!vO9=hep<1L<(J50e7t)Pw;NU*yN_!Yk18BoLx%V7JPnD|=T7Ymf_97` zM}O`a*47cu+^aL~un`zC@7y2gk$M&bRRU&!%wWei1)?cY}tL&WMg< z&yFNszOaeXJlPqI=K#;+UOJ2)f3!gd<;IH#<7OX@jJWZ|oo&3wy?$X=oDR5+i;r43 zIO@ffxdwKR`mpw}gA~caXv&SZFKuHlS!jayW*@&J-f7_3BN1F8KshiS#wD^YG1Y`- z+4GF8w;Lu!+mA5%X&(}k(np98j8qWlda+3$|K|NIi(*7$KJ*hL!I3YFp+pmXJj-V;F3^@LlIX%P0o9jjY)b@<%Nr^j^JA3+Bgng= zV)*_V_bu9{9c!96)*R3Mi+f^tfq=O~km~Uhobvb5{H`GP>HvZ0JGX0is5D@vj?rX- z4&IBChx+k1FVEx2y>ZMD2ZYxR|VBUmFtJbG{hTU!PE(|b#Jg24Sc?L&W(Ky5ON zOqGtY--lh3NzBewRVIZ46FunhwfKAe_|dN);mKWIOpOM)o_?F8x}K?8?ZUC$F_KF^ z!A^`ovVk=^;^&@7<1)`qF<-}*co(lfq+{j#xcR~91OclrN@5V8<7?uhGnbK5kGr>v z{{9fY^Yjp(p-6{ILeU#Ujt>4w5`+=5;oP7Pn*?SF0+|O7@;J4x58;LjQ++Ya9E;-J zOKbSyD_0Q=c<`rB_u?#{K`Gt+BLx2hO3x9LUYgC}?5zTN5?+ju@E)bVD3BOET<_w1 zy@rz{D&IIfh+>uJoTjy0ZDMLDia~<0lZX1a_V=(+Y~sz!IsDs`V<^*a`U71ouF+|$ zHIrv%FG1VUsThC1VR{htdImEDCf}wV|75m;IQOzf7Jq0YNKj0mpUq-{zGQ%4CrA=m zCkWUd>%rcs2$GU!u2->5MEue1E;hJUF-?)>S$g7N4F7a?!+zfD_WF!*tCd0{dli~j z0gveBKyMt)t_OX+3FNaOEG}+vBZD?6OX^&MeQ}hEMCFB&kI^USyWvW zp(Zw<6o8bRTu!<`&R>Oo?T+L09aS2qXi$#nUz~rV*?e3O=(1XXvQh(`fJGHl`R}vJ zQkUrlq|+fDxVT&j4O3rFlfa_KOxhp(b%>n9+P)k_FUFGN+$ST#Tjub-G*gnvYZs&l(MgEyKK&~WHY5; zr@s1*+vyG0&><`CveA=1)Py$qInhQEQ8LQqi<<~f`MF6EY(30c54caA2OXCQT1ZOS zyR+*|<6G(?<@KV#cPR0)wbG7oQh^7gL<1o~2z5EEZ&onT@3FQQ)oLVY{BD!IJT;ld zt&*ee<;i+%C`hVCW5o-vhK*qw*LK5$Azq%%LIaOBUD&7<$gsS;6hV-X;O{>w^ARgZ z@q(my*#7#%RkZpDWC}EX3I18sXI%y#&o)tNHK0@T%w9S;8e#PSYYc*%_6`CV4Y}bX z;nnLI4EKd-ltbw8yKrbnjI1$+U!PgQ5)DL}i26Aitm`W!oL%8X*waI2(#8&gg&U8m z1b-TN>Bc=;@~00bQO?)s7{Unipnw1M4SS!hd={g0j(ipDBmlgz=t5sBKx6GelA!AC z8#O#d27YI=gFdcX50Ul{$b|c7Y_Bayp)Q0o*UFaGdFy%^|MLqyc;^Zo4xjDQT-gfT zT4gW7eL?H=s61$3G@3xsRYz%o2KtB(?OR;$vIliK`om;32nPv{>o~=8{W2Y(Du(-g zK_dPj1~rnIsC$bH^%haX78!7}(!}0EWDZp+llN#KE?g+$+XPsfJmU|@G?QG9 z5{>6q_XP3N^959bT^t;aq0BQfF&x4_UoLUo>7e`RsQEqbUiaWuzKT85Q9Mn|*KONwgzO)~|{BRciy?!%DTiFKw-3vo_<$MljR;xCg=bamQoE{C}x#<8_ z$&OOoi>*=>ooWOb@8NnY;2XTFYpfMdmkxbzU8q);9z>7~L zaCf=+Q|g&tXHvCXkuflVZq0jPNnhT#SAXaW)b3A{qf!;F3|2>dQ`;mJxL4=`r)H( zT*-1j{B5MUc5i%~!|5m8X4fZ&;$}alr_wleasuD~*=4LPSCH=WqFV3RaJK6Y8#qXY z+!qgHW=8-&dwmIcl9jKW7{>f^21lP7#6P}1hpfMe_iuIZXD=pkn}E?xfcx2{B6gD9 zPVhMxek7G ze%XYr)Nzt6wrSUz7~p++`_m$cLx(^zkMF)X%=cU1@7FBaJ(Wt(#s;v1p!UZEZbc%E z9kfR~r+NvLJotwI>*&9QM?{{Q6jn_YUgwL+!aCmnZ_XyagHF&B>0-p+T|3i51vkl(+Abi_(c$K?z zdo+%hhMkVL+N@elK$DKP*7aG@KrF20^D?5*05V%egt?I}74InLLDsGzKk1$*inQ!4 zwE=SB8;zDz_$M7uJT8C|?{|AlgWJjjyN-r{SdvKwJAncxEudNer2_c-tyaYVK)uzm z4g}ZZ)9!$}J-6o3-(K_tsqAl~n=ZUl$X-#u#J7IiPl?pFtWF`-R zTWNz@*IGDKSf?DkdXCit-pV&@fP;XHRNy*UM~YEVfUVgrWY--j(&D8m@*7pt#8#z$ z?LI%7j8b#kl~z#EP-Mk}=Xpsc28rTx+qkpTvTAQd3~rP>IK~S(*dN9JcXQ$`hY6Cxj#QYU&Nn%b&$@egaNY4Rj$Q3 zI>ZrP{ugJ+c#CdB!7m<(;WrmDc)%~ew?QVbJBnQ+K2&&y{_)*K?BAEdsb~;a?qu=q zm2En75BB#(aBL=ODqL@WvW6NjQilg)e1xA%2Hz%if9XgP{!R;jbtaF)WI!V`#KP%O*e!mlo=n%RXNQh($c#UgGRcMpd7_X@#2Pk>mb1KdMU5!|Tb zYNbvV-JvlI;l_FiIfW{35FD$JiEA=IFfz7d5C>1}M`3FVpKLGUB8_vL;7wumL;V4` z>UG>F(;K3J-OAN*fB;Z!q2pxr5gOvf?HWN#6BT;Xy}e|l86x;eAO7~uO*%84X?`bv z4NHM0MhNn56Ud(4%ZtC-!r~^4;?4lPp#YSsP6fRj zo<9=DlU$pNw~9EQZDB8g!0}-(h6yr$M#IyI$a(oqL}Z9e=$%h;I7lP)3?0impKoJw zBx=r4QJp_~F@a20vnhh;<6i9~gPmWm;moZp{_@Ig8=L&y{k_;&so`7C4dXxmZV|^O z0ydRRdG(#cezG^G8|W?#x~j`n&v2T1aBsPZ1@!{9yH-QN?ZUU8jN#LJHC$P$+OQLm zjd40_cQA~<`Q4hOOP-!ckW{$v;>jVrNxN|Q`W*M!h2J|hh+8zKXD(;(#IA@_)VbO~ zhRk%7j=;_@*M5Gpg^;&{qh$VN+MQDe6Zpv+O9TRSeEEsEk&QP#&s#C+;pq_e??`}{ zf@gRGyQX6pOto-`q$5tyoaH;-&vdY;%6`7U1lLobB9@Bc!Ey=lh#xC-_%9xh;e#7R zI)ng5`Sa;e5WL>_(ykExpWkhw#`8KxKoH@*KDswbKJsPVHuyjnzr_WJNd z2*(fi@T|0O?QY!!9em*ywktloa5`z7SGkH>z}*Bw0nE*B@T_Gp|EP>!-q+p35t^`; z-Mt}x*0Jd@e?N?$zOhI`;6sXlP?|E&JT-(Lyu5&dpQMt+XRzj{{SM>Uo-jWBB7-Aj z_R%;nK(~fD2$Kb$JQ_D8*#G0_OK8$@9N@F{^FHsNjA3@EVv4D|dO~SE#Jv z>B9ro0vqFJ{O{ktid4#vQ~Wts$C>o1!27dD`26WO?ktpXd%2FI^rHdZQ*X!m^2vB> z6*N>`Ne8DwqX3zHsZzCxTGdj)nj<5@5TfI~Xw_=i*vRpob-77C3*(QEr*Q4+f}?21 zmDCWQDu3U`0I0A3aaVw~Y)C0kGXc78n~UDEAweB$73`{F!Ru6a%fq)G3jj&`yB5I&DTV^ej1(I%SEfUZ$;bKmcUBKO8}vPLMn=(L5ztd zH{c)fI_+-d4p#Cm^n^*^^Sn?z_>(E%Bn_xU=w6>|V#V7r#Wb~kSZ-plVJ$?OSubp$ z1y`S~`t*GRu&GlP{#O;h&zZKSHbRs96}wlspO;SRab2S8z)_ElYE)aEzu!;C-nFg? zcau!M>cK8LhS5PU{_cZiJU^v8t0Pj>C94qPjSvz4^21FlU>Q#P5ex({O{S$@+Y>YZ z%VZ2S8$iLouY2*#KJGIw(-$jkB#6R$XpBlU1aV%_N~eZBbd-xUXz_l$9j2IyJ)m( zWS336_Th$8G0dZ|b0}#8lGG)xKRRb5y^0bC!VS&_vWCRj+@ZK~)NWyL<%3K{CKHE)$F_WLo&W zCzDv1FJXiZ^gq0kMLE*NaKMWJ+7bx@{li;pngmN5Lnj%D;8{8wZ>?c%WfEALTWR2< zo0}wfZJgLQM21?&LcaX}6ZM`!a;4Xm;7R||SC;n{Rq)=UEt^dvHk;ItqDIt6tC=;L z3H{iJ{kgHbfA-f*#O}uI%xI*UrL>etb~niu+0FLoMjN0T-U9`oPzL3*D!tEq`7&Sb zxi=v#83=-fs;u|E@0xSI@7{CKB#9Uu4&xXUyiwC-p%~lX){F`0Os3Pt0L%paH38XTn zcF6VjgyCi^niw)L#Xx=eZUzO$mXVPNf3}LLu@2lO;Oy)0;1KsZL%(_Bd=1ab~3r8hdwk^1Ung=t=^JIPk)B2l^{c zEk4~n(SiT{*GmeJ9uiP|=lMRod2R!@HVT;OZ(@J13l+?Ya}g zgGs(rw)jFWUFeofY;amy{8Hw^2IOeBGm4GX68s?_1CR?_WNZZ@krE9=n1R>t$I5C_ z6T1EMc;BI;@!EfvE&%t_alwwK)BNO*fC$wABuJ2iFV+6ip&);k>VKcdtFps_>_xaJ z-yj-ia!cjPPqhYG`0$()-+v{Fy)xK=;N)g5+isI9*qCC*Uy;C+sbhp}VAr4%vs*T5 zl7#tw-V70Bw-cRYq_afYj0KqH=3iU3ac`}PfBE$o_A+^FuGY!UB`I^g2pU|&?c^`o z^7;azR3{)tL{3yzP`>Wcs|3$3d4Gb3UYRo3(wWpwvVq7hXCig)BgEls3aD7n^nO6j_ zF*xOI3y!Koy8{mKXU^VF>14n!$Qq9g`|-n55&W0W^Y9LnIhr0alcvsmSfHbP>i`W| zgoU^$6$bp{ySo|eoJiP3g!uh^WDAoKLA(}|BZI@F4|kk(K6?uf28skTPW(eTg~kS# zG}?l%Nq{-l(oTzgH4omKub_*-@EC*C`FnLl2OL-;Bi+TM_ln3s6T!yK#NZ#5UQM)l2qYmsS}kMt218!G(nC>i269AW*rGh286f$-NpQh&;2?K!djV zb27XGOcLY#IVot9=!{!WZ!32!rb)QSCW`gqMzW;P8|K?_7}j1rKK9P41H) zAKolsKiAK$nm9P^XHs0l$^8th1WvaYbY}Xb6^Y5I(8BG4h35zyCdm#yyI;c2ekQmL z2B`56E-%!$XG~Nh0Zb5xJs@i(&cpl~_kNdw-+o1)&$SyTlRPyYX0R=3($6xXx%Y96 z=%*{ob;JwndW|I4S8lRTPm4as&%N955JVYBYd3%g_n1^>2)u@vgp$<;5}`VLU+4G7 z+_;=9=~PE4YLvPdKLfG5$EB@mlE4p+0uQ!pc=2EqZ`@0vEVU;Y7fw!yq%;HV1C!uN zj=xRXmJ44#uHxpVslN3beWpyC-ZvJ&)rUoVxlu*0;nlj03co(iWP9mx0UIO?&kqF| zpku1E_vVKydfa?wTx5Bjc(9PcvwJ!bVuBwev)?Q@G0%j%cQOXsZ6XkK<5MzM8RYW9 zUO)0=*Z1N~dO;7C){A(&74Z-RxbX385ueU-B?XvEZI9`|1e3%>x2QuJ zxb`p$SEPlxq{VpS#X$lAmtDbfmf((=?Fj96G)6$UR^->6T9e#QTTO^`E`#@fem_lH zb;9R$;%g%zcp2-q5>*AG!5t(IT@IvM1fKUc;b6>?+M@cp-PZn~zc};IcHCIZU`)so z^n0+W=DJx}jJt7+fG^EGf9uOMJY1_E9E~E# zICglN{&qEm2L!W0l9fcxRA6^=u7tZ!GVpce;USROKNZuukG}pcl0`Sca|<^fB=Pp; z6l|{9(V=dY%oP>ZOAA>o_>6_54x4sKHoAMPhhWG;&F?^*VDVRvlh|R%eSSmR97~L4 z^Gj9j3==tFDcBw$vFVjM0)mFo0Y6-Pz9t#R3dz(l`rd&FH+?;>)%Mc^22Sq_>fdVn=Y6}xvv`7YfsNKQW`P>`;Y0G2>$zxSUc;pO|h%EW(g zI)s`?f!ikDzM9tY^HUMYtyM(386Vf->hoDl(<(% zb`Rhq0*)&WGa%~LE2V>D5eyQz{`GrX1OP-Le!h6EW}?VsIyBM)XS0l4uGX%WEmX7u zyjCRu=x~q`*m|ZQ*8l;RA0hs`6d*ONvL5GmbjDy-y=`568jp+yPG$;pq<|JINkDZo znY(0WKvZRpb*+^)!So+CGk&2BRVEnXAHT@ zm>eBLPfr^E@Yy2FjPy^FZ83=Dn>CgEythPlJL%RUopTTCZIF_4V!F@G#3a?+z~hw7 zP4MaOy1!+q4)%QplT!zR_jeS_R4fLnFLk1QPu$wB0wZs05ab`AwyVf zgTcmu4v~=!0C(lo!cRucME$#kB6?hP2F$SPyj!(AWORny%S!i#yOP%vnGv$u1d*L+ zWHU$M@^XVoLlnwd$k6!jtu>jPTX=a!P9ePbIUTIrZ!62`h!e?1ow%{gL^S5YuaYhd zN?*LYiR&vSp4%V8g@-we(rLQ=WROb@6WWsl|F(95lC{Au}cyt5=_V-9qICz<9Y|(QX|sD zwfQoct$|0|7Aj13XR~E=GDsaEE4)Z{(HQ5m?j|@X8M^O(doz#k9`D5kf~G(>1O4$~ zJ%W4gxe25bB~0|xv5{%dCl8le7$RsmzSEBqgZbKKUezw^jMBq0_PE1|pS-(?+}&%V)vO|qAB*CZei&6CwdB}qhO_z-P%yh;BHVri>^Z=a57g}lMw zeQ3yoo#P&SHfLekV_}SeEM2q}%q$Qr=1dp%^%<%={^@*CkD#T~CV{U9$ zXn$VxhrN7um#XZgYy3ToAVu}DV z!DRc+(Sp_UDQW?|m>B)oCkc{^E2%rMEBLIfLXf9RPBI7JU&|$KlC)=Vm z(9$YrDQGMd$=G)U@dj-u9`(3&f2A5<21lKW*=UL?YIg;zOq~Du{sz1Rc;2NNYF!Na z1nll^887RF;Ub`XQbmNnHd`k9Pm~EJTzEt~+Ch-kV50SPx-h#TiK3>bY6pi1#9ThS zzFNc|(#}r1J^bCYUjpXyHr#=S7GbYg zyAi3?$MAGWl>O)e}FMod(MUu6VVFMim zQ^lOj2=HnQ5*gfe`qjamA-sD&gOy@?lZak;t zN!0R;UINZ%Nv4=PZV^Zx+c8X5FUQ}e7HL0@TO=_7?A&2si@-I-bxU+w@N~N2AN68m zi?Cg4aZD#Xp_UH%vbaAwb&#>HN9xN6Uf=mDjaN>)v8%_AI1%j(LG#Rb0Iy#tXyV_X zZH{-6tjurYlY0&P_dg^hV4fNvsq2$b&29m0rk?)p;<^Q#75whYEKcs}!1?VgB8Lr? z(tmR<2|r^@(=Au=jCUtwL@@W1KH;U`&Tizi^K5z|iUVP* z&1$P4MY$hh(k`zL1O46F8{3uy zaA?o4Fj%P$sG6o$(FYjmq&QH8K^kqj;F_F3$PdY>fxE4gr1L3UZmpn~Aw06rGOIv> zUbMr_09DX7kt5?dEHAF>J!*&yT#jr541^;A2OH;J zoMnhbg0?7ks(wZTxj*b9V0g&nVG|X4801BPPc;6$K_8rzMtgp?%zBw`X~Jimg@?(+ zcDC#0O?Ypgwn(nFTo8g%Kwfv~SSlY!_vdpjMd$`GI@ zHT?d80R-GW?AqUl_c!jMLoBxqf zcYlz8wn=7fLZ&dM83Y<42E^z8MC5cQ$btxZ+?JC8$c?Ly3)-FW$=xJ=|L7RPOb~zl z(PK@nXC?=6^w=2w`lr{_CjR7wzV`GlGQ&lJi*s{XIQ#2&<7drpIn2&+rs9Ym>KhvJ=Ymj2%L5^QS$E}KgeK)K{lYG zTVz#vCw~0muu73iTi9>E+=iEnR^<9sk~OtqkrTB=-A-Cq{SMU{*Xfk}x|;x}mTJhD zcqC~@LBqpOpn`fx)(qDXjws3 zW;2Ir2Hj!)ssV!P*KQWcf`eM4;19`Nxrtj0(rXn9rv^PJ)t#6e^lCBb!_^W2TOS$^ zljtD>-#a3Axn2VP7K|Pd#^RreFzyme?Ix>w;IAOCSLRmKu~am%8nrZL)um#QU^XC1 za$7d;S|yB=I!_FTP^LZ3uGg?{G>S0SJ;V68;cZ}}Vj}p8SBK@uDCbIVOWWO=E*lNG z@(a{anX6&kG*m|V?2UF6`43J+uzjU~j*3)td+>{!Re~26{@`!~$$Uk-aQ@~(QYYY@ zyJz9|j|B)UJ@|LOT+yq@|A~ye^JYuyaGX9L!D&s2(+fKWaQY;s9Qx?oe zj5lQBo;_|@6*2DiR6wb_YTRU^J=2B9D+LAV7H#k7qz|_k=l2lYzj-N#`D{(EBffjQ zhoG^ANB77gqb32vI{x{oUX)0#FWfC^vHU@Tz7&aPY`TTNKf9&%WXE=OqVYI|e2%ed zfibF!B#|JqK@w*7$cTBD`p*^tl86rVx~0>Jl#?LK!$6TPm9WY<(du_#wVxy*ozRJt z(t0fWEkb7<13sK`MO1v~A;F`Z9?kSK289@KrHI2%G%Tk8RWb&-uatHpEwGcSwwA~~ zk`WS}`-p}_ZAk7)<*q;25z!!c^7syX`0<=7o~fRHtF7E81%b|1Tdq-C_MWQ#2^tc0 zBQFhDzE|!#C7DbAzYZaipO@mYcIn{L<>cU+HclW_jEzDGZU){`htYX9yhPTRGH- z!hHl>@~p!|L&IGbay5^(din&45Gz!sqQ~pp6;aYdUabf=R5?uN?%QK`CA#iwRnKL@gp1qG|5jLjy`0xOcy# zb|#`uwIKnIQt|K9f*8lNLt92=9{9^fH(8;DI&I+9$tZ5EGePW~#N+uT1{xE)r($GO zL8RgtwZQQa}!m#M8zrCGOb-QTGiI<=6#NU0gg^f}M z$H@{q3@-*px>09zU|dG89NQB-oAm3*DS~1rldGqulP<@``>?#6U@~Q}5Jj^B(LnBWFHs$g{4)pRvC32 zbg63QF4~Vw63w@ycE*isYZd(IDFPs;2NxHUa11i9Z9``sjF0;9+U1nCqQ0=d6X&kS z@zU;~7G%Eh1sT=B9-J5pVwIp~bbkPEp4)_japlD$odltIR3BP8ut~-?iw-|zuEwBK z+L!q z8J%4hwMyyl9`K`6){d6cPa@&*`WX^(=wX7o$^CT<$V>$bkCGO?aj+Nv{vAGVpIfC} zM@gzYw5i#ZGU@|@mKsP_JoqLPXpJ_tzMfZ7B&w9P8o?=jwVIPQW~bH~>7-YJ;DC&) zW_B!-Vfy;zij~RCu#wy*u;lNR8tqcSLW3aHmXXCBEgk;UibGFsh4_f5jGO{E_>M9K z(cW@m@MshtfBr-SWsoHi5izOWt`I5&H+R;ywTtTq-yTDOHt-hFszuUM%Ga=?+ly~L z+mG{ivbgs!k3anOs20G=Nlk<(>?40G67HqLy4G^sQ$?_$#n)q z7FRCu`OHNJ6MV~sB>f>IMdnP}6~1p>JXuaeylo;W1Kq@*M4eIyowe3zYqOBxNxN)= z>{~$aW~Pi-m|!$eK_yqi78Cw(w}B)P=;LaY`zM8}BsX21c;`|QaRC=mCpz3N1#`C_ z74W#k99n7NhtCb+A&F4HA4H+Fg`ucl<_F^>=He2ypSI7*QQ;_-^Md%MEi_Z1z(i>ATY#5EI%^ZaHR518k^Pa<`FzJ%Tm zFTQ>tM4OHH^@wa>pi4w($xO>y)n6wVaPxl!5qX^LBQQo$Z37VopU)T3)n_0aj?$RP zT9|ls9y$?#AZqDf_vmn+rz(I_ncG$cCNOB|A3gHd?{#V}7b###m~tY}QlQY%_C&e& zZ(1@2Sb7JXXbTLs6$s^&q5U7545)%D^P3vvr4TTawDIiegV;IY#-)#LV|vJspI$7Z zOfVwHqFqcN%VgNkPP?#?Flj8nV6PhqnNz^ucb{xKLSs2PZQ!eA6HoFD)zNp-u}1uS zcW$tM*o6oY^AH30>0Kc$sI=>3gWQl0$vAUFZGvipqO?Yoli`Lgl07w;^on$tVy&Uo z^W8M|yi{IuV*-3nIR<=pR{C)bE#fKinQpDpk^N2_A_KU2`=QDM_mSE46TRNdv~Y(E z>(r7Y zVqgL?O_bN`8Z=VZOR9*cOmLV9y6Hl!CxFo*8!-Zi;+lnV+QQp&X_e75iZ1QX?~Fun z=Zh^JaVw`HJw)PbTUA5|R$iEpalh@Otc`F5)BQdyN)a3x#t%=$bTZ`}zt^CH=ee)R zMbW5+{og?^IQ1t$!;viOQ>Z^NXW6c40559 zO2LmD&00+n36ctVz8}Mks4OPyW&GmuC9-vw0$iOqDHZMmOma`0NZJ;TjEAt9wJ^I} z)ro3dJ{xhj4~0%sC*6tSriTDobno@bi^0)aY$3WoiYFUu*gFt`r%qeh;lnR(<+bi$ zX4s4S%LGbXHy_z_m&|Qo5=}Z~oU%EyYhzw(f0I@XDEP`fmgmbx#T0`=ukMi89%a z%qeKP__^0@y%z^<`~0mUUYVk8GY}dCSMS_O;V-`1gTH*AafDzlK!a)0HZ8eNBghR% zjR>=BHxtKt+EPKRGUL(hm7?l{_~DRXW1fTuTJ9Rly zZ<1B|CwvIW^toFlc!r(aHy1oRJbb1K=CXP0@9#u^msd|*8vO4dNtCf`GKMd1CQ;#B;EuP!uBrQ3QiNgz3wiyo zUnfv)w=H`+y=0jL(6R>v-|Lri^e30jvKQsbbg_iV(Xh5{i%5-3PxO15ID3gKiEQ}s zS{A1d#IT+;iL>gejj~a(u!rWBVQhS`UQngGN2>+w9yhSCV#4Vm@GfM~C;n2+wFeQA z*tq>Nft8>j+7@4H(ndQbgF3iw{gZ9(fd`>+KdKvb5*3Ri(2qXm4>|D_Y3fY5j{8i+ zO9XIUS^qsfC>RB#FIF)4d=zOibHnbTFc#xgyl}J^H$UIdqWNsTjFJAJ z4xHLI6(R{Eh-}mlB*`%b0&w+^5UrNbITb)|xkw=4fEjOb4;g0&RLy0ASK52ST|qLcw zA@O*Ejzq(V7;?>`V^?L8pA2L%0)7Su5$BOgeg?sC7)}POD8IK_u4ucSbPP1>3@(oL z5xw-p>8xfv9UvixPc4ToWP2cRJCVy6V3{Q#dI1g{2g(F)wTlD=F*rPenmtcv1+;;I z9GgqQ;6K&|NCC$ZQF5#1#5Z1?M9A=~T=4YHF1-HMHL^(qUIzXV8o!56kS#Fj(opG_ zxJ@K`E#6?Vci@>F2H(Sh2P+nWOza29nC`4p$^Q7>`6ePU4|edKLwrU#t_;yoRru$zt- zWujUuxsc}fd-?aDT`VC;ggwC|appib(uD@zz7fZZ(*g9m$@cEmkse|aCMtBc#0V_h zyi&rA>eAy{58oxX zW@0~sN5$X6}=vzPaxS;^r4`D$4;)t3p1jxcF`%Fo*-EBM!^Bk(gi z?k3Bh9&q8OUzRb(Jv}l+R35jO0NmPhTqHvG@V&bRV@N#A!ABq}$GQcFiDj~cP42@q zZKM%82CVM3s-OhDri`za`S2m1_Vx;RHpuX*^jY6d1NB8R zgNOr%c7$m=MKn!e0ixSq#*x7eT-nONq)!i$k^apm3wUvV2OejdSe4PSQt4ZFt44wR zoJ?1&OY01uX{0NF9wv{M5BFec-9&|qtlmT1I<>v$@#RngC&Nwaq#zSsSl2`3A!#a$GY3m8C)z`5eFRAuvgRnUdnU!61Uk zy2>{{Hyl&;H9io6i!92&kHNR-#lDUZyj;iUkCyS|a(hseoKWo@ap-lWupKY=KbOyA zcwY?FK#f7(gBHPjk!vI4nB{sf&*amhP07T`{G}42EdnMc?a`PIqy2uJZ7)RVw_nAv z(N7R+OM+r@?Q9|x^P{p*LsMo9FhQ0-A(Ose*K5=&!Q{=g8oqY413ogzpS`<|o)}{t zgLRWhusT;qKa}(XRzilxcHoOu~EGzA9ueeAt68F2!+rM;}i1g|SRO z&lM~11-Qmh`WeYdxGCLIJ{;WJgDcBf1P2Wa<-91+*`;-FfPhr)&V?Y_^pzAzjLh8F zHR{JC*F0GzL+^Cy6^p|p5F?nW($3|`UrxA+TO?5n8EyTvnY7}+&LPNHaCm~irk4q@ z*udS@8oYr90iXx}{P<2>S<2(V;}tx4ya{D}%xUp2^01RkraB)+rl8i4-EQizp(l(f z|MBb!W@wA&nPVK%azrrh=QAZ3M`}{fb~231*K;_ryFqY7w%>Ha?rW+DRI8PPz0Ek) zCD+#>^b$P&-MJ_D&f%~MVg2TAl3)`kk~F$@yHz}H>wF%rQ9c`!zbDB@lp$#Z(}RH^ zrpWS7kkDC-xzfJ>>#Ip@OYI8dv|RrP9`JB&8kRVawbS`)h5In%4<|H~hz%%YcZ znsOc9wDmsU`SfH)t`zL%(<1=Q`|ac|x&cxIXj%>ZCuh^C58aj!w%bZ-A_ydC{T~5= zqdhM`oJ)>&rM^J!jAzEk{;DCQ;|0u|>LPoL;s5yAdE^LOd_+BmJKFuK%iP!(4-lz_ z+<50^9jR)2e5YJsZW00Z27zxM_M^m&{15M@wV-Q)jP?bxh#U>-z@V27@54O;gX?5K zQoStWEMH`Bad1;EFITWi!;}i%@oqW{lj$`A;@wOlD~T31a}Au_AHn_EA|`sh*gxpQ z8d>eGArBH+2j044Vw~(+T8UmeUsNg8?uZn{(E#tHk!x7!r!i+3oTE%0HPT{dygh56 zH{4QTlNV3)Vq`FYpMJQ4XU7A4e{L2LbdTYM$sp$Gap@>l#}I=c1G2PKZloG0SxyEN z59a;c>i}(FlzU1vZEZ1_%YY!$RwMhxqcj4R3t6&c(Y>!SF^V{c*OJM6wn{r^GGt`j zHy=Mkj@S%Zxwl@^;XuA{5C?|%`xs54bR_8u7v$RUTo515=k?Aje4{e=%E(d~3MBZ=XRt);MMB!t@=RTRq&WcfS=CT4^0x0-NbADNVS zIis@BSPq}Ab+x>3b3MbyJWOJuS;4?PEoY() z7}tP(enVTS&R#Fz_)Jh$_~fee=3pJxrip>k08T{$1gT_KsTwM@fj>Gmh2K0Npz0#* zAFH=DG03pJB6lq9y?frI=6cjGDRRU#HbMaCc{JHOaDAR@_3?(zUyvHY|Mr{1c=!4?*0xPO zezsgqr1J$5CK4Qx(7tPFr;3b5c6B*a9rRYFhSeT|ZHDgkInm$c!)9GhG@RH> zRr!2wZM74CEA!qv`yuAxg0&de zWW0KbzSDOij4J&%d#8%MkxuL%9m3$?5d4uK<75h#Z_d+(OUf7)hlUxzccz8_ z+U%XjNi1C`svNn^xYc+>``&IJnKwF2^{aEUo0>n&BEe-ez&(q=rk~m(7WK+reDKi{ zUjEv+&P6COVah4Wa^5DOG6;GY`x3QwQMi}{f#_>rZQ`ZRM!L5=I2aCL^oRqGn54xAu0Aeu?+Sc3`caNC@<|H|O#a_H-GlvS z`gD!%uT`+feO#9s7&2&M%t4!OkXX=G`y4vgqq#sk?Qh_wO(HYt(DwDmDHGq=*MoPi zC6N;w^^w85OUUIiD!e9hWI~Jw>3j~yc6MNfF{{Zvu8?rQelZT;m>X|A%EKcv?}N~j zD9;d+Hgk^5rI1$3<`d?&j0*a(N$(SF-|Ml*MUJ-e@=YMe2j}LC!S{Q4S z;XAo+<~n0nTuueJPKhd-Nv-L{oA;VHG8)Euv5FfH@^JSLbs^Rj(IG&FKY&a!gDzhb z3y$!v>kCgd^k(&;V+p&I_`C!aGp*L50G2Ms=7tZO05t0(>NF@auQG?5aiUau3w zLFuP;qQ_@r&zKLhG=gm!^obcm`={lYG3;#N*ufB)yaR8ZOJS?pRGow4WSf&@0PkH* zs}kQd!NQ2>m3P=W)lYz?K~}PvlaZ269Go;zt+$X&n+g!z1Paqk{-%R#=xNfBq}R2f zM>U7ZhC2gI?NR>KoC91(Xn=$%H38T{Qwv_iX#7OOr#cN)!U~Yd+$Mu*Y}#mso9&Uc!*YDe z#5>90B{I7YO?YU?atD8PnMv_LA3h`lTXQgw6M;r(1M}I6j#GB5+4wA#!}sZb!c`Spv>; z*BQl`1gk-Z)`PeP$%Y#(WIwLz-Kq$KH9w>OF+rc~3X)Cv@$Y}Pq4V5N@ARky?=gbi zPsw=XP;Pomq=^mu=3)}32|R!HK^%dOJYL=9$4~C3&`e2%wgfo7*IoJmZM`OnmHlMf z1Zfol@O!QXR+F^9aRS>%b^0fLnZQ8w{%f?$0D(b?s6&oT%kW{}Oayb83Vnvjo0owARaTGeYufAJ>DMtB)!BQ`kbA%b!3OT+oc11Wbn^`*M~6U!4d99 zvA2m1+VJN$lb9p0^zL=3unty%-+dLwksTt26X6~;@Gx1&Rbjqlj{foXRls&RhsXr? zRfagN)nQM!U<75{E&;-WI<|66+?J#JNiQy~wRA8|lxryOd^s*QPU6I*cVjk*S5AjW z5b9XTSSVE+^mhYq-``SlBva%ZeJxbC2u^7a>9R0?`e)gt?agv3QCe$Yplo30aHmdj z{XhTeEdJz=pW*&y;PnvXam{6~2O}{=MqM~C(?!x%$2(-bKl=V8x|xV?mYLWXN27;) zv@rs@hfVnTzAXYCbE!cvB4c%dE&BVHvuk)}-!LxR-@wC%2|dv*)v8J!tBldUDFYMT z9Y}3gacE~ZZapkvknwClMriYUU56Zu2W@ul-{IDo4RWV$j4}!GrE1=Wc3=4oANO90 zGf5`;{d&@Rd$FVwD`ggpOXT9GJbKFMYmt1=2EMy9#Jp73E8pLKk;9K(iebE`1HZhS zLbx}CVzLY;*P!~q;@--nJ^F4p<2-#_rmIS!dXsVX^JI>>rW@Zn-izN|PM}(<;pCA% zd^VfV@#J!1_tjhur*?(0v0g!ucGVGeBQLXe2w0OrOYi-?(_YPg^>{;1Q^O-(ROah? z8slPe?TH2Q z2D>M8vTwa`1gooYj0ZCqU;y1LIq=oh5;w<--#chvd7DYUn~amFRtkQ8cBvxdOj|Ja z_IR*ICg1hCwaWTGy`MvahAc{7FZcS1^em)kdE5L9QOuM3eoTKmHX3zdEhj~dbbhi(AA!$rZI|47uPQaBE+*n|nOKy^-qqO$Mja1Q)L!3TjpFJcDm7Ya%uohDpHC>La_@ zGQ})lFo6}`Dd7genH(>DePC) zaWUUrE+P|b!adX$L9&Nks_rP`F}>6LxeX={vKp+~a7$2o(!hArkG@`)N+i#1TNvJH z;NfarCnm}@Qcu969U5Yb@88bj>wKRN5@cZ0PUOBIK-C%V4wU=&KB*?IJl;lU(u+)k zpfD+O?;8Xvv|Tcza5RFYHCam!&NHJI2^ws!|H4KM1N|=i(es@M5TUDnw`h+LVt$)5L0_f%1q= z=WS@wQ=RrDqp9WeLfCPAj}unDiFH1Ex#7hC-?dz^G0EWfC|)75rw?(x=hv#39G2Ex z3(xF{A!nKNkCK8AJKP>}G_DoZqbOSJ*T&q+a zhiTtSTLq0T`@~;|L-?C3Nko_|t3;@7CPWu)wY$@Y8iA+jBkSDF%_uQ3)1M+f7bf{a z1ARVRo2z5jkPi{VgIGj_Jsdiy$+HfO#=JN+<7eXZtFFT)pXrazbSXoJvS5&HJ}x`8s8=83~SSV|91CmEwsM4KvQj(K&I56a>o2o=kLgdNmzF4#9u-IF9K^1pne| z<46=u%+9B9e~mH5$92j-GEFVl+y&KNY&a7y+Uv~+p@r_C%`~nDVDWeuq8r5d{(Iokb*%xy<&)P zGk)>$Lu}K*qf9bC{@wx1uViRwb^Oo&q< zY2hpbQc8B8Py5WYA5|tpkpY#$xcBbxJsE#mG=_U_8d8ufcdn@#{KK8L)()f!O`QxU zf-NbjqNSnK_zvSSHx~F=(*5Uh)G*%bgWF^RB6tv)T`7{wG6=5l*;inapfMR0iL96N zaELOX_<~5(B*{b>+-KKpouQ4zItEAFNZM|kBy(LNdyqi-lj~XTpA+91GO#w!q)wLG z!$dF4Lt5+Hoi22oi6A#yU{a@3r5FgM<%+?&B~uCcyH>Xw*iC2C!W^zq{?iio!J!Us zuQgR*q_$CptFxv5{$)K2?+SGM?KByfD1WWXu|I8MlK{aPA$Z|BcN6WuaWliDPgK0m zK&ei)M;2Po+i1}TMPI>|BT(9yC1Z4>4)hfa0x1K%BE!u6SxMIzZ>rcPyA)PjtUDC^ z$e^PQuA_(`-B`$zUHg&t*_voUCT--nihRC?+~fJ%gQ(=rDqbj)T}E;4aupGRVy!WK zlt*T@hSlB*;tWpvMm(^&zYQicIn`?M{m*SO*gvdjg**ttWX`&1`Dd5PI{!hA@=B>1 ztQ$>C(UGGJb|Te#cdm#TCaPc zi*id(2y_O#s4c|FK2#=Aa7waO=NfO&; zl`baPEN%WT?tn(i zjMgyJAJFTGpWPrUqYc;Q8LSBQ8Y|rEH4~m41ei>i!mhM(pCB(!pE-K#}a zeEjyYD89InBe00z=4=_oL>;?33>=}aUAR$Uyf9Ukw!*mg#cUogAB^JRmt|z7qltF7 zZzPJ@D|x;4bTQUgPukZSp8j@Mlf*Qa<-)2_$Ili_xYId|bc_052=?t3Oxm*y77EkH zCkOrbVzG?t+XcOb@a*7cGPd|~La3N1_ZZ0V8C?Wb{XhU$=n2k2K%m^-=~N=!W5kH)aACjr{(NibjxI0*H4Y(?3c?pN@DZc=Wzsl4*c13UAVbYNB(n}INQ`B z+QfpiA2$gG=?0Gz?Uvt))NuKH$mdHs3R2tin1E{p1b$z8=D9_NTP_xLG+`hR(e^x1 z^YhcNf>DEPsmMg_Q8`v+?nG`5 z@Oyg#1ZYjYNBqmP>-d+)JFzA865MoA@>?dOFEWtKk|n*$Ahnfca$-`I!6>P-S*?BW z6zYg87LlXyJ~xUC>~d}3l{U40 zB6b?;t@$dtYX%Mx#haxP0t`SNX9MvP@WosfRdFnuQ(~#4MJs|UWP$x*+{t)IO} z_8%-ewC!#?BDN=jLhZVVil594QpN7ZU;KBc8I&5hzm$R1*Me7yF>^&N@@w+5qBP(E zCcn>@>!=d+Nng9HwcK|)gssRCzX+Ge)sorPasXRja1tzp2uxfX zExnVsnHXHLmYym}TbV_W5pj4h%(&6ZXF9)1ki|6;A)GH3^Lk{QO_tCVkU1nwxB~8^ zppF4qrZ>vas6ob;q%BFWfo=MR42y9YWMB-`hlU~;AZrgNow&j0F?PGOc0|Ut2B*Aa z`#~fZm>`b%P+F*}Mn;24Z-a^8&3grWeSasM&4$XNi+u6V@22QCH7!Rfyc~RuV z=_I4H1sU0xvJoT`-oC)k&C4-<1iyN36~_+_;F*1Wm|M-Dlun>2_tjjt*_8^uf2ack z4EtLTEFE>5WOk4amz_I$arWXes`O6}>2HdOyF{Y0pP=FZ<7tqw=*veY7JOAWyIa~3 zBQ;np5jW!=%F(@Ob6n5RhZ4ZS9o_iGGh?`VC$8(zVE)*%X8`kS34C&s_CmnrnPgnw z$A2RrlL9~AGxY7X7Thb0f%SHwo``In915VDdzLA=kx0~#+{$C7s{?5!UH_ODcXB06 zZhJA%a+~qk*Ta^1^WU% z+%L8e5qaG?f^?FNzzbfKWMI{l1J!&R?1|c=sA#@%ZyUp8MJIN4Vb5>?W#+IqF0G*) zYS4FS17!UYBa2lNjxyr{$$&wk=jm-S&NF(@m!BJOBVDp^ayRqWUHS~)_wq^+fgN5f zW#lA{>m=in$@Hs@I@i3e>aC0G6;))FHsc%#yU5(n)C!{AULyB;0wQxG6M{^`7huFfDzO#XZKjgmp;$*|-u(T{`s2ebfhYb$}i zz8F@?vLzV_@~e_<3tN}TfbOUL`f~g!?RIN{p-^3 z=QF(e{~da50962#WB(do#_td4X@US0i-5uB_Mlj-=?rP9Di3gTMc-S3U@n`--b0=E z#oY}YB?|oE8JRh6;Sd>JfQ;!szMa50(f79x8t^Y!xV})s^so=R24q5A8Ox~#xTXxO zT`)FGl;`W(qNSDSJu+IjJ?>Y=_DXP;!dG-T(5_i@=@m<&Dbu3d=;8)?8CXV{5Xv+J z5jIQmXL2;mMqUc$Xb?=s6@r4$eh+FZP1uaG9)4a;#5wrk(GICJE5W3b$@FC(EqLB+ zOIdCZpnUYG3h$ItMTSQBd*n`Po-A~ON#TB?g{)IfK%~EypG!d1OSFGvB!qSTyOD0` z40d@QIZpFP-NS$%_n3f=Pq}fU;J_Abrq0A7;xNTa1S-R1>Z=3^kI2eL&}Q-s;;ER4 zj}n`(lMZDTGO(yoDrvK00|Y zfYxC>Nv^%Sg>@#SUM7V;zVp9&A%g$(6&IcC zA$ZcKGaO=jBG|g0M~DHaFl*|RN2xf@3IZUqb2E_d8SEq*@ZimBMRZDAQcnkdes&wX z_V-{lyNM>j>p_Cej|sR=AEDEeDPO*q*XyyW+_6faxnb(niDS&E>m^6Y1{sG{X7Xvc zQGQs%U4r7wMoA|n9vKMWufABrR*ebY-y{LG(7f4Zk0Ki1+-Miz)gCgrXPp=#h!Pt% zr7V(rZ|1Y`1-+Qr8Nu_80`4tWNCF5}_#Kz8CCFM_+D|{+@-S#|JwjFP6oVQAQXk*H zx7Q8ls)b{RdhqXFUqO++@F!m%hV$dawh+q%L0_(g8o^AtT88fk?S0l#)jOoVb`vJj(z^(Rtm2^->at}rB%icf&|w%zlQ)Yb)Ggy zAY89Wp;l96z}@|NzK2YUAYd+`75kzTDs7Weuq&;LQuNhw5R7fLbqAczx)yg?0-728 zOY^i7epYWxwBMsB*-tRHnO4c@TMsfg!uJb^0vr>%6qU z*>pb3Ojl4lnEv*ythP1pr$5cF3ze;@UcZR0$so4bK(flwKGS^NnwTWBc*VeP5Po+@*i}@i@8;UL;P_a zIABO;ofGHi$ItQyPwp|8%N)9xGArZ(LF04N%zF+K`P)^!BASb5RM5;9^5Uvo$LFBM zfKs489SvYN0mO-*2b)_ZyuOiE5}CbO)tVepuXTr*(1n4Faxs$0v=-Tk!d}gk3fmeC zzrh7F5sQXtIHq0?l*%P#-9tlt$mPfoJA%k$N?IS#*B4>Z$|%8aNlV|;<9R6nw6z+$ zT_2!#_fE$@bpuF?n1BN97Le-!RbpxvaqU{Wvx0MT!fx%phMU`kE_{rE?SZ_qMRG z+MWZz=#Q05UDc)fnBa5Uc}$VT-{np&(b|-vu@=l^Tk&p7RK9Wr zj7A9}$V7GWB9qYFWJA^M0!;Y-`gsq2vQ$>bTBWLxa}>&zt0O-iJw=o zRVq_z{C;^Mg`;CWj56>G((DL2QK;0Akz>-Pg+Y&7@6%3xZ8!em+#T3M(7RD_PL)Px z(su?NS^!mk)X?K=&!`KPd!oce=ENY@Vsa^M$kAK7h0itBt_T;seCZsq0ww|@Od1Tb zmQ~feMk%*-txhfXW=Fv;Izq6ZMoYD?Z}*M!n{@2(;Mrq4e7|)AN8y7(O0*#==i3AY?b@$ zlsj-ImdIR^OgbmSKGbdtyQS^(-9jR$lBe-7kiwTtPH89OJ%`i~PY4B=Oo zGWg!{0B$YY*gqA*l?OR25_mmZYw8K1fbuo6GMo0`qV4#4+&Yt@+-t!YkQP=3Z~?AT zc>1t?wda)UAiisXAVzc)v=gN&OjAU6>bR4vE6{dHZ#a`-gKpj^H?>Gju4Nh?+RlZR z0(CidEp4=q-aDgRcKihL!HGTEM7#d-)>>-`NF z+|!|b-N+K`<_VZ4Lfv|jD@UrKprO}7MUtk?Vx1r$r1d2|Bu26BkQ@iV6;8oUJJ8b` zK$!WUn_zWnq=rW;d349Z^ff1L-`m2sUmnG;-;NVNwe&R1Rbmp6t{d7|cD1O*l;&ON z$y$psxXMKAAhU1fMB%c5+oB#cZ?|RQyU`v1C&gCWyYw7=Yt7cAS|u26iHv$rTUD{1 zYiOHjaG;5Wq_mbwaV?35$B+BVdF&>m9i=VZ+Nh$RtX=G;*(_mk)2~;S(%s?7i++ht z-X>#qlMFaj6QS4d!w`e}-=9t3*pw6%R&?rYu4e0~>Ux-aBaQZa$KXz{#`hxqE4JUq zxYt6FpeMLb+K-tt+CMD zG!|#1)5s+Xx-Q(DFVQEeI67tEg9k-K2uLfXIv#9WaB)wa1h!X5JbwJGN&ND?CGD0Q zV#1deQ-d+wux?I`N=l``I55rC>c^m zID*cu5H?r0Xh$AyuC4d`4#T7U^+kdLN!GGp1sax!16kS~P|*Y~|0dOqjb^)8Po#Xc zuU}OA?51k}YsJ5e^pt6COc)JmL*(-a7;wwD-&R{XSlbCjEQr5DKtfd&3TbQ-6r@

C&zMIW1UQD`SG}{qbTRx9{ha{XR~Ybqb)2{q5(^3tE)=f&N}EzPOlE`BQ1b5vP}_ zSugH)U_EVPgpPfLh;T6}V?kxmkp~NF{GHtrP^ALbrL5hS`$Z}IKs)h!_&m6J@@s zt(5kn&79}b>1;9{Sc-Ah_|P>5^q}EGgihuq^Q;gE^^kF1d(wb6xq|0Mqqx*lX$ytW zNjs;abOa)5ANQa_=MpgzX%o7|eay7bPj++)AzYoWVjq*rTDFd@$2C;vT6%qAJ1mqg zl5vBGlsRd~aEPY-{d;37tb`PEzn z-Q1rORX@JmE@*#!>*=g1xs#M&<&~3|1?~X@Krey3nGzXm501n-@tf7So`%WX3YVXW zW5d>a@Y2V1y`l>p_aggA9cS;Pu)E)fy<^?@&6PMDQ90?W>rkIppXtQ4`6BMDm2f*z z!SC;nU~QWq=0F7h^`nGV-fxq!4av0@eeTM9L8qprl4ZQGGoX45TZ<_jb?uuZh{@2O zvjnkCshzM?BS9o_9Xs7xpy}`8dNIL>e;ybK;C8B{17tQG6$GZaXPewJzLS@Bp06`- z5}5f3G@DGyKC)N=I?bdF-;7JUI2;7E&4`T&qM>Tp)WJtm=`V_TGFEVHaRuMo6T@e- zS&VQE>t+cx(mK&dk!!^vf}L?D1evk$%uXNPx-AUZOB)H|Iun>d030yn+R4HANg&HW z&YjYP-r(m2CY;KDtVITB+Jj9SZqm*~`4G}J*y}@=%OW6aU~jAg$M$f&7t%Q0)q#(% zY@*8dt}HM<5~TWfcu=@z>W~=sgd4e~sxoj%43}=lvFnvC%nWqk1nu|o{ej^xl`rEbj~D;2l8{^pdER)U8&V*_qy$n2n9zjo1(S zH|)g3#_WFUndw%v+Gtv0OCl*!qIUp61Mh(X%AveB>AlZA_a@Y%2#N$y$o&1@yXKtt z?mhQm3_GcPym+(+>pMBv2$(}{0sQn|9^%#KI^g%YRo`PTRU%l;qacc#^uO+~uiC?T!k-18!|J|bw^OJMe+G+9)I~vJ6y9l zhGfPrV+P9y1ZKXfinA^5m(UmT;AoE>m-)^cyH%WET-*|EeA+2^Hp>U}Dr30S2^UFkS;m|b$cXx* z%GsCb2y(wJY`V2I$OX3$tnvlP_C*s}M(>(!R&BkL&6jpPVL_6V=Xy7b3-wQo z+Rcq}r9_0|Xd=C8UGLuIf6_+P8;}_gW+HJ0ej4>&zT5;F@>zVP0RNhe3pBl6#OX;N zj*<1=SkI}rk(B@;OIF^&#rpIy(X^pVLm|3kQZAZocy~69yp%rYpURyUn}t0hyrKV- zE(#(%xzm!YU@wt%Mv3Y&HF(5v7$91mQp-?Rt#2uwX%I!98+2mAN(8%7KqesPOa_&$ zl+Ff&gY4%)LEE&944NV|GQ+?*Pev}SlJZ_vGg17`t`5cVx*ND6a-E*0sL}`nfR#){ znk=_UqU~N*fd~`XhnK;sM}&&xd1*Kq2GIbYVGv zK+U+bTE=FA$%bsHuRVl|qS25|Vzk8y7oTH~4m}ZJl4L;N%@Bah&>+ZkGncu(1TxWU zS-lTTC3CGLlo;5VI-%Pv=yX}B1ak8tnvw2y_&0$EA}*&7wrNeD0(wYG_9g9&@NqSo7pFP$7{R%A<0 zw$*1w$ZN$W+1MCC$7c^x$d_ekmqq6vJi|5FjaT9IF-e&#xW7$1qAgDjdhyHKQDsx5 zl*pUbRc%eKOZsKRH-p`s)f`G~(w)#iYubVqvkg%OaI{s?L9lAf)-crS!;K}fa)N=A zZ5||Od*>q_+}w&G7A3S2l|O>vN}{fWK~(P2O9aS#=Axs5t*r{m!7BVtCOih=H?Akp z;5r`MA($L;pr6m}qc7bg@U%M%_~B#_7w;u-^pI5(&>Oc1vgx0_tuoS@>rOk^+%2hw z!+xxYW8E&DA273BMr^CBvBl5^WIwMUT(coY;NHqLv@+<=5FneqHSP9r%3v;%t1$gJ z8!sd3%QLx_@Hm>sD`(nqbvcGK*VWCv8QLXS=nTN=v|@fgg>b=zs{}}L6rGeoQ=2gy zA@lRvOSmO7D>}^RmC2*Rh#9~huF71BjMlxQL>v%^k|aIHJq9N1=Do95oj z9lFd^@XC7dbN}hHUDa$@Au!3&2ZlSXhy)F|w3udW<^E(EdJ6F>f!Lm*#5l)2vRJWU zN?@9CC?^A*3P$a!X|vj~AJ5QkpvsbdCiSra53X#dlmwQ!&y!^4ll?82BapxIbyO;c zV4}UvR-3wc*KiaGdWE4*zou7Jk;8N z!(BcMkNENB?L91#2rjSetNu;9yV+u%dz@_6#0>KN?=_Xzgo>!VZ=@%T0#T$S*sU!F z3}#A6+)^ZzPB4L3O)B={aM+b~hlr>)*P}%JWgRRaW+giQg*JVdZM0RA#6~RZJ zw3*T2R*0a-87P+b8t7?rVPz)^ho26&m(y9@Z{Lxw1S7f`K-+1HliVxmw8+vCOTMN$ zq(9h-t=t0a9=R6b9#B}e%GLA^SFSu#*9jgHf9OPDz`kF{6DGUU1FdA+S@;7+40qe{=2x5e z_dn@E@rsOzHEJuX9LGtm$$ZGz^8t*X85GR5(?d|s#5f|D(7gq>w9kg9b*r*wi%0gW zS$q}oIS2wW*rW}6o$?)3^z!dVXz%xyE9htuJ1D|S*0o$ZwM64$$_K>r%yE zQCZpnhv;q8klH9|O-lYT6E^{iw9@5Cas~rdOm(?&m%&m_-}w6aXG0+q?lZ8jh;Fw` z=cH}Ee#D~%avx9U(O{w<3OkrIjRZhWT)35_{nb^ma+-{OAjr?{HRF8(z=ltSAYe@y z337Ze<;r|sD~?aJ=&buwF&n-hxZ4wzP{yXgc0V>~@2?(j!|X~*^&6tG0tsskZg2@2_&OwJf1y(d#VFR z4oC3bR~uT;)u0X9i$;XqHa%MJ8xLw{*v$u6W1)Zz1y1j=^g2kbh65e>{3Zm(P z$~ZsQ=_TBl;VGnMt6mf3S zgKu{-hIlNOo1Vnv%=-!}L;^%l(?ccTg7V%qjj z0*42Ec&Xg9!TVC^I0Fiy|=YA_KCZole%n#J0Sa#z?mVwu-P}sX(p4Wh&#n`I1)i z%SFBU5EIL8xp{J7Z65j74~nQQ@Xw?t)Gh591{4;^7Q75Du^PG!HUt?+M_Y{eV7h_; z6XKx|!3E#*c)tagGgTr^KJUJP!PSgYy*4_oRCoe!U(ex3r@i=-Q7?YCpF!8j7VQb% zTT7s2%!}1b#HmRos|+a*AYT68#0 z$nQm_kjI9IH_->iI$g+6A_sTnyxj)-3qj4Jyf(E>KJ;`y^J_?vfkabm)SuBsGC znUzKV{!vnEAKqnh|98)ablO-tXGSzp&^;>Vfj@h#6(8R?zykfl;FA7&D~4%TZG6WI z(@7odBh%38+;HToib%c`wj6Cp2ZrJ}OCpoi?Q--oT;1fq-w>%|(D3AQ>3 zMl#}`T?TyofKJ498sOhQZW*uPvvqFb?GCzd=_K_WB`weRUd_9>wv@*$#O9P6BNDLU#Zw3=Gk7QDb+6WXww--qYbk zq%*8jVV72-c;&f4^e%dE|Ir?f9}OXvETMbc&A3%YD8P8P9mmd00^PLzQ-^{CwNA7X zI2bJ@f|s-krFDe;v z3HNBXXw4A#ku)D3_Hgg^@XOD3Fxu;-e^!}G4S0RP#~5nG(LrEyx1tSlt>ov2-3GLf z7`=Zpi;;*0Jx;5(LRR@bD?2%Y2qRo1>3zLUym+=1SMSDfc)*TJi$x{0{VjFO@09ga zRR;BS+2r)Xg4PcHoBnT3rZN~9=~p!8ZE3@kM~`8bdn`GakP)^Plb+P)(k0|+08YYm zsbH+vs|?yj?HI@riQ9>kWSXFy9Egs8<7qP2!9MsOy8Su@u-cpipo4s*6QJJww-)^v znqzURPp1bO^^~CA6ljp5zSt60v+1Dd@|J zW`Xf3NZ~sXNa(oCqzNU)l6n#hs11beGJdkIR|X*hr3gRo>p3PfDgKe7J~B5epZ3`a zsW>lUjho(g#I7wRpWIF3-=FlrnKNMhH~R>Rm=fdv>Wy8PrIOfQR;_QjCodoHxfoz$ zj0CJ391#N4Q1@9Pu{?xwhj3;;@7Ybu@UD;sfjF0O@W zBLNx|8O}0;up9xmkTsa-kbV4&+06nhQU1As3@d2TAwD-2^EyRPM&$18m+<;Y7lNW^ zKA*>7Ci5b}U5IDM#ygu#(0≠%b0e^S1q^A!e7kcZ? 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

    R~~>{J3A((KZFtnBc9i0*S8bt4{XBOon@Vs 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

    CP(-k>#)+_Twhe%+-V-NubEOCzBHcbR8hwaZ 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

    Vy%Kp7dh&_GRM3j~+os^$qlJz)Q*p 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 AT>t<8 diff --git a/src/Tests/Render/Desktop/References/Fractal.png b/src/Tests/Render/Desktop/References/Fractal.png index 9268213d084138845cd764647559b92d81a18588..96fc832884ada4f71a49963785f53aa965d6384e 100644 GIT binary patch delta 83624 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

    mM+7vr^Or?q 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�(vu&V;{Zy3@!T3a-+5lYg81TjoiLgLQb>c&F}} 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 ItjYKP1*3=Uc>n+a delta 227034 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 K0ApjTZQtX!hrjSye^@=xM}N1Zp_xsE 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%0D!Wu!w(oxBy}AFKde5Di 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℞ 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�r= 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 literal 13026 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 diff --git a/src/Tests/Render/Desktop/References/PointCloudPotree2.png b/src/Tests/Render/Desktop/References/PointCloudPotree2.png index cc9ca8863f7a0791da83b6ea9590d9dbb6b3aa05..0f66b66bd436e2d5a32b1c1037093851dcf060ad 100644 GIT binary patch literal 2616 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4Yzkn2Hfk$L90|Va?5N4dJ%_j{M zd*$ik7*a9k?Op3WG6~b1oahgd$^x1{p>M21lR+nphYZSU4FN zID{A&1XLIp6g(Ih940U@G$@QJ7!8Nf^f2u5g~NsA;o*<>{{8dYGXBmzz6Guf4kyBk zAJ!M!*-4$h_viijpLu!pd$w^io)BOV`1m&FiTt_a*Prb%cz*BRzw`ILy}I|@S&)H~ ziGgX~JnrASA8)y^-{$#F%kbY@x87N=uw#Gv+usv88V(CH6co%q&-%Cd=Z{}=!k_K2 z@XPW2DzgH*A|6aP9p&){Zf$z;hi@9Ip`!6@uTc($9 zkA0sk$MfI>q&&0OpkDlM#^;hbdSCC)-F*73|93-wwt^I(rkvWg|7WjWt;n|fUp&X| z`Tl$B@BZaG@axn4`0Dwl_HUCL9}5B9Zhx+_`p@^<|DWgo>;I_3a4qux`{KIW#~F^R zF+6y1?f>Wb`7B$`y}aI3U#YyH^xFT|bLKz)v!S1{&zHgBz^={rcb$E|>&*UVpTZ8j zK2xvzumAhL10OjVIwHRRu>UV{Vb}ZrKhN&h`~TmlO`f?)f{{V-IqSXijLb|7O*%gs mM$6vOI%u>m9*J#|7x6#!E3&!r>zjd1K?YA(KbLh*2~7aSBGw`R literal 2151 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4Yzkn2Hfk$L90|Va?5N4dJ%_q&k zz#-}B;uumf=j}B|Mg{{821WUA>-XGztibyF90P-7sR+)qVd%Ynhu)z4*}Q$iB}v^%@# diff --git a/src/Tests/Render/Desktop/References/Simple.png b/src/Tests/Render/Desktop/References/Simple.png index 099d90202dacb39b479c3cffc21b1cb4ecc332d1..aac80f4497380e173ec5b2bcb796ef3c005a23ff 100644 GIT binary patch literal 24308 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 I)9K~E0UkvHzW@LL diff --git a/src/Tests/Render/Desktop/References/ThreeDFont.png b/src/Tests/Render/Desktop/References/ThreeDFont.png index 32b874ad53f28b8d1b1a5ea3ca06d1c1553341cf..ea0091caf8e8c3dfeddbaccfa89a95e14ea6194d 100644 GIT binary patch literal 23568 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 literal 23558 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 From 989360978268aee5b33d754b925571cc4a912c89 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 13:20:31 +0100 Subject: [PATCH 127/294] Update ThreeDFont References --- .../Render/Desktop/References/ThreeDFont.png | Bin 23568 -> 23459 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/Tests/Render/Desktop/References/ThreeDFont.png b/src/Tests/Render/Desktop/References/ThreeDFont.png index ea0091caf8e8c3dfeddbaccfa89a95e14ea6194d..bcb975e7e6c33c022f5dfe87fc7bed8d0bc63aaf 100644 GIT binary patch delta 22633 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 delta 22755 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 From a1a44d19c4be0eb45d0c219c6bb0a168e31e1664 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 14:40:51 +0100 Subject: [PATCH 128/294] Refactor IWriter, refactor LAS writing (via PotreeData) --- src/PointCloud/Common/IPointWriter.cs | 47 +++--- src/PointCloud/Core/VisualizationPoint.cs | 19 +++ src/PointCloud/Potree/Potree2LAS.cs | 147 ++++++------------ .../Potree/V2/Data/PotreeMetadata.cs | 14 +- 4 files changed, 95 insertions(+), 132 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index 07a8f8d0e..801e33251 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -28,95 +28,86 @@ public interface IPointWriterHierarchy } ///

    - /// Metadata about the point cloud - /// the "attributes" which are needed for e. g. the Potree export can be reconstructed via the + /// Metadata about the point cloud the "attributes" which are needed for e. g. the LAS export. /// public interface IPointWriterMetadata { /// /// Version flag /// - public string Version { get; set; } + public string Version { get; } /// /// Point cloud name /// - public string Name { get; set; } + public string Name { get; } /// /// Description of point cloud /// - public string Description { get; set; } + public string Description { get; } /// /// How many points does this point cloud contain /// - public int PointCount { get; set; } + public int PointCount { get; } /// /// A possible projection method /// - public string Projection { get; set; } + public string Projection { get; } /// /// The point cloud hierarchy information /// - public IPointWriterHierarchy Hierarchy { get; set; } + public IPointWriterHierarchy Hierarchy { get; } /// /// Global offset of each point /// - public double3 Offset { get; set; } + public double3 Offset { get; } /// /// Global scale value. Points are being converted to int /// During load this scale factor is being applied to convert int to double /// - public double3 Scale { get; set; } + public double3 Scale { get; } /// /// The spacing between points (set during the sampling process) /// - public double Spacing { get; set; } + public double Spacing { get; } /// /// Global of the point cloud /// - public AABBd BoundingBox { get; set; } + public AABBd AABB { get; } /// /// The encoding of every point, as we save the point cloud as elements /// Default is /// - public string Encoding { get; set; } + public string Encoding { get; } /// - /// The size of one point in bytes (for sanity checks later on, compare with data) + /// The size of one point in bytes (for sanity checks later on, compare with e. g. LASPoint type data) /// - public int PointSize { get; set; } + public int PointSize { get; } } /// /// Every point writer (e. g. Potree, LAS, etc.) implements this interface /// - public interface IPointWriter + public interface IPointWriter { /// - /// This methods takes a list s and converts it to the desired output format and writes the file - /// to disk at given + /// The necessary metadata /// - /// Path to save to - /// The point data as - /// Necessary metadata, e. g. global scale, etc. - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata); + IPointWriterMetadata Metadata { get; } /// - /// Necessary metadata, e. g. global scale, etc. + /// The file to write to /// - /// Path to save to - /// The point data as , no as ref types are not allowed - /// during async operations - /// Necessary metadata, e. g. global scale, etc. - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata); + FileInfo SavePath { get; } } } \ No newline at end of file diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index 700fd3c5f..1b5fa42eb 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -23,4 +23,23 @@ public struct VisualizationPoint ///

    %DDqg^~QPmu?Z)ayM+@eR$hq#BL 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 diff --git a/src/Tests/Render/Desktop/References/Labyrinth.png b/src/Tests/Render/Desktop/References/Labyrinth.png index 48b5664041fc776dbcb20c6c51fe0e1d2170af04..3c627c7c456f0ae625391bc96d0010041687d062 100644 GIT binary patch delta 227152 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 sCV#F+>Q8|u?Am>I*e`f)gOYh~cAJhz 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 z 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≫Sgwzg7ZG&*RrgTTp92h`c%&DBeE6txtc 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

public uint Flags; } + + /// + /// This point is used for data handling purposes. + /// It's read from a file and converted to either LAS or changed and updated in the original file. + /// + public struct PotreePoint + { + /// + /// The position of a point. + /// + public float3 Position; + + /// + /// The color (r,g,b,a) of a point. + /// + public float4 Color; + + + } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index f22bf5d4e..2fcf69847 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -11,6 +11,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Fusee.PointCloud.Potree.V2.Data; namespace Fusee.PointCloud.Potree { @@ -103,75 +104,70 @@ public LASPoint() { } internal ushort B = 0; } - public class LASPointWriterMetadata : IPointWriterMetadata - { - public string Version { get; set; } - public string Name { get; set; } - public string Description { get; set; } - public int PointCount { get; set; } - public string Projection { get; set; } - public IPointWriterHierarchy Hierarchy { get; set; } - public double3 Offset { get; set; } - public double3 Scale { get; set; } - public double Spacing { get; set; } - public AABBd BoundingBox { get; set; } - public string Encoding { get; set; } - public int PointSize { get; set; } - } - /// - /// This class provides methods to convert and saves clouds to LAS 1.4 + /// This class provides methods to convert and saves clouds to LAS 1.4 /// - public class Potree2LAS : IPointWriter, IDisposable + public class Potree2LAS : IPointWriter, IDisposable { - public void WritePointcloudPoints(FileInfo savePath, ReadOnlySpan points, IPointWriterMetadata metadata) + public FileInfo SavePath { get; private set; } + public IPointWriterMetadata Metadata { get; private set; } + + private readonly Stream _fileStream; + private bool disposedValue; + private LASHeader _header; + + public Potree2LAS(FileInfo savePath, PotreeData potreeData) { - Guard.IsNotNull(savePath); - Guard.IsNotNull(metadata); + Guard.IsNotNull(potreeData); Guard.IsTrue(savePath.Extension == ".las"); if (savePath.Exists) { Diagnostics.Warn($"{savePath.FullName} does already exists. Overwriting ..."); } - _savePath = savePath; - _fileStream = _savePath.OpenWrite(); - _metadata = metadata; + SavePath = savePath; + Metadata = potreeData.Metadata; + _fileStream = SavePath.OpenWrite(); ParseAndFillHeader(); } - public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory points, IPointWriterMetadata metadata) + private void ParseAndFillHeader() { var doy = DateTime.Now.DayOfYear; var year = DateTime.Now.Year; - var size = Marshal.SizeOf(); + var size = Marshal.SizeOf(); // make sure we can cast to and do not lose information Guard.IsLessThan(doy, ushort.MaxValue); Guard.IsLessThan(year, ushort.MaxValue); Guard.IsLessThan(size, ushort.MaxValue); + _fileStream.Seek(0, SeekOrigin.Begin); + + // TODO: Parse point type and extra bytes, etc... + _header = new LASHeader { PointDataRecordLength = (ushort)size, FileCreationDayOfYear = (ushort)doy, FileCreationYear = (ushort)year, - MaxX = _metadata.BoundingBox.max.x, - MaxY = _metadata.BoundingBox.max.y, - MaxZ = _metadata.BoundingBox.max.z, - MinX = _metadata.BoundingBox.min.x, - MinY = _metadata.BoundingBox.min.y, - MinZ = _metadata.BoundingBox.min.z, - OffsetX = _metadata.Offset.x, - OffsetY = _metadata.Offset.y, - OffsetZ = _metadata.Offset.z, - ScaleFactorX = _metadata.Scale.x, - ScaleFactorY = _metadata.Scale.y, - ScaleFactorZ = _metadata.Scale.z + MaxX = Metadata.AABB.max.x, + MaxY = Metadata.AABB.max.y, + MaxZ = Metadata.AABB.max.z, + MinX = Metadata.AABB.min.x, + MinY = Metadata.AABB.min.y, + MinZ = Metadata.AABB.min.z, + OffsetX = Metadata.Offset.x, + OffsetY = Metadata.Offset.y, + OffsetZ = Metadata.Offset.z, + ScaleFactorX = Metadata.Scale.x, + ScaleFactorY = Metadata.Scale.y, + ScaleFactorZ = Metadata.Scale.z, + NumberOfPtRecords = (ulong)Metadata.PointCount }; - var generatingSoftware = Encoding.UTF8.GetBytes($"POLAR v.{Assembly.GetExecutingAssembly().GetName().Version}"); + var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}"); Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); @@ -194,73 +190,23 @@ public Task WritePointcloudPointsAsync(FileInfo savePath, ReadOnlyMemory } /// - /// This methods takes a list s and converts it to the desired output format and appends it to the file - /// to disk at given + /// This methods starts the LASfile write progress. /// - /// The point data as - public void WritePointcloudPoints(Memory points) + /// This methods returns the current progress [0-100] (per-cent) + public void Write(Action? progressCallback = null) { - Guard.IsNotEmpty(points); - Parallel.For(0, points.Length, (i) => - { - // restore int, TODO: provide the possibility to skip the offset application by user (always use scalefactor) - points.Span[i].Position.x = (points.Span[i].Position.x - _header.OffsetX) / _header.ScaleFactorX; - points.Span[i].Position.y = (points.Span[i].Position.y - _header.OffsetY) / _header.ScaleFactorY; - points.Span[i].Position.z = (points.Span[i].Position.z - _header.OffsetZ) / _header.ScaleFactorZ; - }); + // advance to end of stream + _fileStream.Seek(0, SeekOrigin.End); + + Guard.IsNotNull(_header); - for(var i = 0; i < points.Length; i++) - { - var pt = points.Span[i]; - // re-interpret the first bytes as int - // we need to shorten the first 8 bytes to 4 bytes - // internal int X = 0; - // internal int Y = 0; - // internal int Z = 0; - var intArr = new int[] { (int)pt.Position.x, (int)pt.Position.y, (int)pt.Position.z }; - var posInt = MemoryMarshal.Cast(intArr); - _fileStream.Write(posInt); - - - //internal ushort Intensity = 0; - _fileStream.Write((ushort)pt.Intensity); - //internal byte ReturnNbrOfScanDirAndEdgeByte = 0; - _fileStream.Write(pt.ReturnNumber); - //internal byte Classification = 0; - _fileStream.Write(pt.Classification); - //internal byte ScanAngleRank = 0; - _fileStream.Write(pt.ScanAngleRank); - //internal byte UserData = 0; - _fileStream.Write(pt.UserData); - - //internal ushort PtSrcID = 0 - _fileStream.Write(pt.PointSourceId); - - // internal ushort R = 0; - // internal ushort G = 0; - // internal ushort B = 0; - // scale from float range to ushort range - // reinterpret as ushort - pt.Color.x *= ushort.MaxValue; - pt.Color.y *= ushort.MaxValue; - pt.Color.z *= ushort.MaxValue; - var colorArr = new ushort[] { (ushort)pt.Color.x, (ushort)pt.Color.y, (ushort)pt.Color.z }; - var colorBytes = MemoryMarshal.Cast(colorArr); - _fileStream.Write(colorBytes); - } - } + } /// - /// This methods takes a list s and converts it to the desired output format and appends it to the file - /// to disk at given in an manner. + /// Dispose the of this class. /// - /// The point data as - public Task WritePointcloudPointsAsync(Memory points) - { - throw new NotImplementedException(); - } - + /// protected virtual void Dispose(bool disposing) { if (!disposedValue) @@ -284,6 +230,9 @@ protected virtual void Dispose(bool disposing) // Dispose(disposing: false); // } + /// + /// Dispose + /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index 6f3f5b738..f0abff2f2 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -7,7 +7,7 @@ namespace Fusee.PointCloud.Potree.V2.Data { - public class PotreeSettingsHierarchy + public class PotreeSettingsHierarchy : IPointWriterHierarchy { public int FirstChunkSize { get; set; } public int StepSize { get; set; } @@ -53,15 +53,16 @@ public class PotreeSettingsAttribute public bool IsExtraByte { get; set; } = false; } - public class PotreeMetadata + public class PotreeMetadata : IPointWriterMetadata { public string Version { get; set; } public string Name { get; set; } public string Description { get; set; } - public int Points { get; set; } + public int PointCount { get; set; } public int OffsetToExtraBytes { get; set; } = -1; public string Projection { get; set; } - public PotreeSettingsHierarchy Hierarchy { get; set; } + + public IPointWriterHierarchy Hierarchy { get; set; } [JsonProperty(PropertyName = "offset")] public List OffsetList { get; set; } @@ -74,7 +75,10 @@ public class PotreeMetadata public double3 Scale => new(ScaleList[0], ScaleList[1], ScaleList[2]); public double Spacing { get; set; } - public PotreeSettingsBoundingBox BoundingBox { get; set; } + public PotreeSettingsBoundingBox BoundingBox { get; } + + public AABBd AABB => new(BoundingBox.Min, BoundingBox.Max); + public string Encoding { get; set; } [JsonProperty(PropertyName = "attributes")] From 5f7540e288cbfe52785de629190cc3f60c1f444d Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 14:48:40 +0100 Subject: [PATCH 129/294] Ongoing changes with Potree2LAS --- src/PointCloud/Potree/Potree2LAS.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 2fcf69847..52d12a38b 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -115,6 +115,7 @@ public class Potree2LAS : IPointWriter, IDisposable private readonly Stream _fileStream; private bool disposedValue; private LASHeader _header; + private readonly PotreeData _potreeData; public Potree2LAS(FileInfo savePath, PotreeData potreeData) { @@ -129,6 +130,7 @@ public Potree2LAS(FileInfo savePath, PotreeData potreeData) SavePath = savePath; Metadata = potreeData.Metadata; _fileStream = SavePath.OpenWrite(); + _potreeData = potreeData; ParseAndFillHeader(); } @@ -145,7 +147,7 @@ private void ParseAndFillHeader() _fileStream.Seek(0, SeekOrigin.Begin); - // TODO: Parse point type and extra bytes, etc... + // TODO: Parse / generate fitting point type and extra bytes, etc... _header = new LASHeader { @@ -200,9 +202,12 @@ public void Write(Action? progressCallback = null) Guard.IsNotNull(_header); + using var stream = _potreeData.OctreeMappedFile.CreateViewStream(); + // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors - + stream.CopyTo(_fileStream); } + /// /// Dispose the of this class. /// From 8d34cf0bd38e821ce001a7924d31ce9e89596d65 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 14:56:25 +0100 Subject: [PATCH 130/294] wip --- src/PointCloud/Potree/Potree2LAS.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 52d12a38b..56826e888 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -203,7 +203,13 @@ public void Write(Action? progressCallback = null) Guard.IsNotNull(_header); using var stream = _potreeData.OctreeMappedFile.CreateViewStream(); - // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors + var fileLength = Metadata.PointCount * Metadata.PointSize; + // DO NOT USE stream.Length as the MemoryMappedStream aligns with the page size + for (var i = 0; i < fileLength; i += Metadata.PointSize) + { + // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors + + } stream.CopyTo(_fileStream); } From ca486fa4dcee9ae0dbe408b7ef20b6fef2e4feb7 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 15:21:28 +0100 Subject: [PATCH 131/294] Fixed JSON conversion, first (broken) las file) --- .../Core/PointCloudPotree2Core.cs | 6 +++++ src/PointCloud/Potree/Potree2LAS.cs | 1 + .../Potree/V2/Data/PotreeMetadata.cs | 4 +++- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 23 ++++++++++++++++++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index a2a5dfe62..c12291217 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -4,6 +4,7 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core.Scene; +using Fusee.PointCloud.Potree; using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; @@ -75,6 +76,11 @@ public PointCloudPotree2Core(RenderContext rc) { _potreeReader = new Potree2Reader(); var _potreedata = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + + + var laswriter = new Potree2LAS(new FileInfo("test.las"), _potreedata); + laswriter.Write(); + _rc = rc; } diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 56826e888..523158af4 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -207,6 +207,7 @@ public void Write(Action? progressCallback = null) // DO NOT USE stream.Length as the MemoryMappedStream aligns with the page size for (var i = 0; i < fileLength; i += Metadata.PointSize) { + progressCallback?.Invoke((int)(fileLength / Metadata.PointSize / 100f * i)); // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors } diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index f0abff2f2..8eee4e271 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -58,6 +58,7 @@ public class PotreeMetadata : IPointWriterMetadata public string Version { get; set; } public string Name { get; set; } public string Description { get; set; } + [JsonProperty(PropertyName = "points")] public int PointCount { get; set; } public int OffsetToExtraBytes { get; set; } = -1; public string Projection { get; set; } @@ -75,7 +76,7 @@ public class PotreeMetadata : IPointWriterMetadata public double3 Scale => new(ScaleList[0], ScaleList[1], ScaleList[2]); public double Spacing { get; set; } - public PotreeSettingsBoundingBox BoundingBox { get; } + public PotreeSettingsBoundingBox BoundingBox { get; set; } public AABBd AABB => new(BoundingBox.Min, BoundingBox.Max); @@ -91,6 +92,7 @@ public class PotreeMetadata : IPointWriterMetadata [JsonIgnore] public string FolderPath { get; set; } + } } diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index a804c0ee4..2258e4dac 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -262,12 +262,33 @@ protected void CacheMetadata(bool force = false) private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) { - var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new ConvertIPointWriterHierarchy()); + var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath), settings); Guard.IsNotNull(potreeData, nameof(potreeData)); return potreeData; } + internal class ConvertIPointWriterHierarchy : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IPointWriterHierarchy); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return serializer.Deserialize(reader, typeof(PotreeSettingsHierarchy)); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + } + + private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) { int bytesPerNode = 22; From dc937ac7151d57a436b6dfa9f56cd3cb828ba4db Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 14 Mar 2023 15:28:50 +0100 Subject: [PATCH 132/294] wip --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index c12291217..47d1e8144 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -78,7 +78,7 @@ public PointCloudPotree2Core(RenderContext rc) var _potreedata = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); - var laswriter = new Potree2LAS(new FileInfo("test.las"), _potreedata); + using var laswriter = new Potree2LAS(new FileInfo("test.las"), _potreedata); laswriter.Write(); _rc = rc; From bc2eb9d314c4a4eb0731ed5255bd698b1cf60dd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:12:04 +0000 Subject: [PATCH 133/294] Bump Newtonsoft.Json from 13.0.2 to 13.0.3 Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.2 to 13.0.3. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.2...13.0.3) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Wpf/Fusee.Examples.PointCloudPotree2.Wpf.csproj | 2 +- src/Math/Core/Fusee.Math.Core.csproj | 2 +- src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Wpf/Fusee.Examples.PointCloudPotree2.Wpf.csproj b/Examples/Complete/PointCloudPotree2/Wpf/Fusee.Examples.PointCloudPotree2.Wpf.csproj index 7cd743182..bded40a0f 100644 --- a/Examples/Complete/PointCloudPotree2/Wpf/Fusee.Examples.PointCloudPotree2.Wpf.csproj +++ b/Examples/Complete/PointCloudPotree2/Wpf/Fusee.Examples.PointCloudPotree2.Wpf.csproj @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index 2f5b45251..ef857d4ba 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index cd9dddb14..7300ceb4d 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -8,7 +8,7 @@ - + From 81d9a4680ca734ef322032f1e245804601b11e1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:17:09 +0000 Subject: [PATCH 134/294] Bump NUnit.Analyzers from 3.6.0 to 3.6.1 Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 3.6.0 to 3.6.1. - [Release notes](https://github.com/nunit/nunit.analyzers/releases) - [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.txt) - [Commits](https://github.com/nunit/nunit.analyzers/compare/3.6.0...3.6.1) --- updated-dependencies: - dependency-name: NUnit.Analyzers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj index d359c7267..78e2db8c8 100644 --- a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj +++ b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 39c299e1addf55f629c8d6037b2a14f01ab6aea7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 07:19:21 +0000 Subject: [PATCH 135/294] Bump Microsoft.AspNetCore.Components.WebAssembly from 7.0.2 to 7.0.4 Bumps [Microsoft.AspNetCore.Components.WebAssembly](https://github.com/dotnet/aspnetcore) from 7.0.2 to 7.0.4. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.2...v7.0.4) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 2 +- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 2 +- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 2 +- .../Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 2 +- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index b003635c1..b4058bf9b 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index a823f638a..dc2d673c3 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index 4836f2310..b3d140d97 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 75e7492d5..3c1c9f3ce 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index 3ed55c421..fdadc4400 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 0731235cc..0cebb888f 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 1486f7cca..a2231d566 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -23,7 +23,7 @@ analyzers - + From b4938478fe1221952875cfe85fa2a03325c27774 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 08:16:12 +0000 Subject: [PATCH 136/294] Bump protobuf-net from 3.2.8 to 3.2.12 Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) from 3.2.8 to 3.2.12. - [Release notes](https://github.com/protobuf-net/protobuf-net/releases) - [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md) - [Commits](https://github.com/protobuf-net/protobuf-net/compare/3.2.8...3.2.12) --- updated-dependencies: - dependency-name: protobuf-net dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj | 2 +- .../Camera/Android/Fusee.Examples.Camera.Android.csproj | 2 +- .../Deferred/Android/Fusee.Examples.Deferred.Android.csproj | 2 +- .../Android/Fusee.Examples.GeometryEditing.Android.csproj | 2 +- .../Materials/Android/Fusee.Examples.Materials.Android.csproj | 2 +- .../Android/Fusee.Examples.MeshingAround.Android.csproj | 2 +- .../Picking/Android/Fusee.Examples.Picking.Android.csproj | 2 +- .../Android/Fusee.Examples.PickingRayCast.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderContextOnly.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderLayer.Android.csproj | 2 +- .../Simple/Android/Fusee.Examples.Simple.Android.csproj | 2 +- .../ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj | 2 +- Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj | 2 +- src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj | 2 +- src/Math/Core/Fusee.Math.Core.csproj | 2 +- src/Serialization/Fusee.Serialization.csproj | 2 +- src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj index d69915948..ebb95559b 100644 --- a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj +++ b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj @@ -57,7 +57,7 @@ - + diff --git a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj index 0ab87abd6..3d93b788d 100644 --- a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj +++ b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj @@ -59,7 +59,7 @@ - + diff --git a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj index 420e8a68f..e524d7818 100644 --- a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj +++ b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj @@ -65,7 +65,7 @@ - + diff --git a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj index 9e902a6c0..32b4f85b4 100644 --- a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj +++ b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj index 7a0a736e2..5d7adf056 100644 --- a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj +++ b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj @@ -64,7 +64,7 @@ - + diff --git a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj index 786f6ecf7..86444bd35 100644 --- a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj +++ b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj index aa567235f..6df2ddab9 100644 --- a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj +++ b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj index 38166cf1d..3e8db0b5a 100644 --- a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj +++ b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj index 61fbe59b9..33a4c552e 100644 --- a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj +++ b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj index 4ecd88b37..b1807d4e5 100644 --- a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj +++ b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj index 244ff3c30..0f80f0d3b 100644 --- a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj +++ b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj index 3e4791540..c2e1f4c17 100644 --- a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj +++ b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj index 66af1fb34..bac7ed62e 100644 --- a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj +++ b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj index 2c28efe9d..9da2c80d0 100644 --- a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj +++ b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index ef857d4ba..895e2bdaa 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/src/Serialization/Fusee.Serialization.csproj b/src/Serialization/Fusee.Serialization.csproj index 541a3c1f1..9c751e344 100644 --- a/src/Serialization/Fusee.Serialization.csproj +++ b/src/Serialization/Fusee.Serialization.csproj @@ -19,7 +19,7 @@ analyzers - + \ No newline at end of file diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index c7b25158c..014dd1697 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -14,6 +14,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 67865af9d185ee1b0688f8f1154bde2b88a7c418 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 15 Mar 2023 09:36:46 +0100 Subject: [PATCH 137/294] RayD/F updated --- src/Math/Core/Rayd.cs | 14 +++++++------- src/Math/Core/Rayf.cs | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Math/Core/Rayd.cs b/src/Math/Core/Rayd.cs index 54ba044bb..7c4eb2503 100644 --- a/src/Math/Core/Rayd.cs +++ b/src/Math/Core/Rayd.cs @@ -75,17 +75,17 @@ public RayD(double3 origin_, double3 direction_) /// The Projection Matrix of the rendered scene. public RayD(double2 pickPosClip, double4x4 view, double4x4 projection) { - Origin = double4x4.Invert(view).Translation(); + double4x4 invViewProjection = double4x4.Invert(projection * view); - _inverse = default; - _inverseDirty = true; + var pickPosFarWorld = double4x4.TransformPerspective(invViewProjection, new double3(pickPosClip.x, pickPosClip.y, 1)); + var pickPosNearWorld = double4x4.TransformPerspective(invViewProjection, new double3(pickPosClip.x, pickPosClip.y, -1)); - double4x4 invViewProjection = double4x4.Invert(projection * view); + Origin = pickPosNearWorld; - var pickPosWorld4 = double4x4.Transform(invViewProjection, new double4(pickPosClip.x, pickPosClip.y, 1, 1)); - var pickPosWorld = (pickPosWorld4 / pickPosWorld4.w).xyz; + _direction = (pickPosFarWorld - pickPosNearWorld).Normalize(); - _direction = (pickPosWorld - Origin).Normalize(); + _inverse = new double3(1 / _direction.x, 1 / _direction.y, 1 / _direction.z); + _inverseDirty = false; } } } \ No newline at end of file diff --git a/src/Math/Core/Rayf.cs b/src/Math/Core/Rayf.cs index 29bedc521..b3e335c9c 100644 --- a/src/Math/Core/Rayf.cs +++ b/src/Math/Core/Rayf.cs @@ -75,17 +75,17 @@ public RayF(float3 origin_, float3 direction_) /// The Projection Matrix of the rendered scene. public RayF(float2 pickPosClip, float4x4 view, float4x4 projection) { - Origin = float4x4.Invert(view).Translation(); - - _inverse = default; - _inverseDirty = true; - float4x4 invViewProjection = float4x4.Invert(projection * view); var pickPosFarWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, 1)); var pickPosNearWorld = float4x4.TransformPerspective(invViewProjection, new float3(pickPosClip.x, pickPosClip.y, -1)); - _direction = (pickPosWorld - Origin).Normalize(); + Origin = pickPosNearWorld; + + _direction = (pickPosFarWorld - pickPosNearWorld).Normalize(); + + _inverse = new float3(1 / _direction.x, 1 / _direction.y, 1 / _direction.z); + _inverseDirty = false; } } } \ No newline at end of file From c4d5dcc68bf8d7ec7a2ac9dc94940312e258dbc2 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 15 Mar 2023 09:52:32 +0100 Subject: [PATCH 138/294] Updated packages JSInterop.WebAssembly and AspNetCore.Components.WebAssembly.DevServer to v7.0.4 --- .../Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 4 ++-- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 4 ++-- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 4 ++-- .../Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 4 ++-- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index b4058bf9b..da8637bef 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index dc2d673c3..5774bd5ee 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index b3d140d97..9bba96e27 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 3c1c9f3ce..5cc6521a1 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index fdadc4400..d074a1fb1 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 0cebb888f..8454b9f3d 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -15,8 +15,8 @@ - - + + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index a2231d566..04217852e 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -24,8 +24,8 @@ - - + + From 2b10a9101c18c0f71ab8cfddf401de49c53d44aa Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 15 Mar 2023 10:10:50 +0100 Subject: [PATCH 139/294] Updated JSInterop.WebAssembly to v7.0.4 --- src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj | 2 +- .../Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj index a0a26f55d..44ab3c16f 100644 --- a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj +++ b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj @@ -16,6 +16,6 @@ analyzers - + diff --git a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj index 8bd75d6df..fff90079c 100644 --- a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj +++ b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj @@ -23,7 +23,7 @@ analyzers - + From 30691a0c082073e400d96ff7ae86ad8b38f7a098 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 15 Mar 2023 11:05:08 +0100 Subject: [PATCH 140/294] ImGuiFilePicker: double click to open a file works again --- .../Templates/ImGuiFilePicker.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 06487a2b1..568232462 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -327,10 +327,13 @@ public virtual unsafe void Draw(ref bool filePickerOpen) { SelectedFile = ""; CurrentlySelectedFolder = fse; - if (ImGui.IsMouseDoubleClicked(0) && (SelectedFile == null || SelectedFile == "") && ImGui.GetIO().WantCaptureMouse) + if (ImGui.IsMouseDoubleClicked(0)) { - LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = fse; + if ((SelectedFile == null || SelectedFile == "") && ImGui.GetIO().WantCaptureMouse) + { + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = fse; + } } } @@ -340,23 +343,29 @@ public virtual unsafe void Draw(ref bool filePickerOpen) { var name = Path.GetFileName(fse); + ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); if (ImGui.Selectable(name, SelectedFile == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { - if (SelectedFile == name) - SelectedFile = ""; - else - SelectedFile = name; - - if (ImGui.IsMouseDoubleClicked(0) && (SelectedFile != null && SelectedFile != "") && ImGui.GetIO().WantCaptureMouse) + if (ImGui.IsMouseDoubleClicked(0)) { - if (HandlePickedFile(SelectedFile)) + if ((SelectedFile != null && SelectedFile != "") && ImGui.GetIO().WantCaptureMouse) { - filePickerOpen = false; - OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, SelectedFile)); + if (HandlePickedFile(SelectedFile)) + { + filePickerOpen = false; + OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, SelectedFile)); + } } } + else + { + if (SelectedFile == name) + SelectedFile = ""; + else + SelectedFile = name; + } } ImGui.PopStyleColor(); From 34643b0694ee7115f021a70d6458b70284a5d1c4 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Thu, 16 Mar 2023 11:55:33 +0100 Subject: [PATCH 141/294] Reset PickResult --- src/PointCloud/Core/Scene/PointCloudPickerModule.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 2ce3da824..452715d01 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -66,6 +66,7 @@ internal struct MinPickValue [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { + PickResult = null; if (!pointCloud.Active) return; Guard.IsNotNull(_pcImp); From 166cef30883758c09ebacf4293df6194dbf8593a Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 17 Mar 2023 11:24:10 +0100 Subject: [PATCH 142/294] ScenePicker does not recalculate the bounding box if the mesh is Primitives.Plane --- src/Engine/Core/ScenePicker.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 6e05524f6..4d968e213 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -772,18 +772,16 @@ private void PickTriangleGeometry(Mesh mesh) mesh.BoundingBox = new(mesh.Vertices.AsReadOnlySpan); } - if (mesh.BoundingBox.Size.x <= 0 || mesh.BoundingBox.Size.y <= 0 || mesh.BoundingBox.Size.z <= 0) + if (mesh.GetType() != typeof(Primitives.Plane) && (mesh.BoundingBox.Size.x <= 0f || mesh.BoundingBox.Size.y <= 0f || mesh.BoundingBox.Size.z <= 0f)) { - //Diagnostics.Warn($"Current bounding box of {mesh} is smaller or equal to zero. Forcing a thickness in zero direction of >= float.Epsilon"); - var maxX = mesh.BoundingBox.Size.x <= 0 ? 0.1f : mesh.BoundingBox.max.x; - var maxY = mesh.BoundingBox.Size.y <= 0 ? 0.1f : mesh.BoundingBox.max.y; - var maxZ = mesh.BoundingBox.Size.z <= 0 ? 0.1f : mesh.BoundingBox.max.z; - - var minX = mesh.BoundingBox.Size.x <= 0 ? 0 : mesh.BoundingBox.min.x; - var minY = mesh.BoundingBox.Size.y <= 0 ? 0 : mesh.BoundingBox.min.y; - var minZ = mesh.BoundingBox.Size.z <= 0 ? 0 : mesh.BoundingBox.min.z; - - mesh.BoundingBox = new AABBf(new float3(minX, minY, minZ), new float3(maxX, maxY, maxZ)); + var sizeX = mesh.BoundingBox.Size.x <= 0 ? 0.001f : mesh.BoundingBox.Size.x; + var sizeY = mesh.BoundingBox.Size.y <= 0 ? 0.001f : mesh.BoundingBox.Size.y; + var sizeZ = mesh.BoundingBox.Size.z <= 0 ? 0.001f : mesh.BoundingBox.Size.z; + var size = new float3(sizeX, sizeY, sizeZ); + var min = mesh.BoundingBox.Center - size / 2; + var max = mesh.BoundingBox.Center + size / 2; + + mesh.BoundingBox = new AABBf(min, max); } var ray = new RayF(PickPosClip, _view, _projection); From bf6038368b05635759c545baef5d75e2b18802d4 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 17 Mar 2023 12:30:16 +0100 Subject: [PATCH 143/294] ScenePicker: deleted bounding box recalculation --- src/Engine/Core/ScenePicker.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 4d968e213..8fc88a964 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -774,14 +774,8 @@ private void PickTriangleGeometry(Mesh mesh) if (mesh.GetType() != typeof(Primitives.Plane) && (mesh.BoundingBox.Size.x <= 0f || mesh.BoundingBox.Size.y <= 0f || mesh.BoundingBox.Size.z <= 0f)) { - var sizeX = mesh.BoundingBox.Size.x <= 0 ? 0.001f : mesh.BoundingBox.Size.x; - var sizeY = mesh.BoundingBox.Size.y <= 0 ? 0.001f : mesh.BoundingBox.Size.y; - var sizeZ = mesh.BoundingBox.Size.z <= 0 ? 0.001f : mesh.BoundingBox.Size.z; - var size = new float3(sizeX, sizeY, sizeZ); - var min = mesh.BoundingBox.Center - size / 2; - var max = mesh.BoundingBox.Center + size / 2; - - mesh.BoundingBox = new AABBf(min, max); + Diagnostics.Warn($"Size of current bounding box is 0 for one or more dimensions. Picking not possible."); + return; } var ray = new RayF(PickPosClip, _view, _projection); From f7536c088b60f7acad0d30a1d501c9252e71ebba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 02:58:43 +0000 Subject: [PATCH 144/294] Bump protobuf-net from 3.2.12 to 3.2.16 Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) from 3.2.12 to 3.2.16. - [Release notes](https://github.com/protobuf-net/protobuf-net/releases) - [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md) - [Commits](https://github.com/protobuf-net/protobuf-net/compare/3.2.12...3.2.16) --- updated-dependencies: - dependency-name: protobuf-net dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj | 2 +- .../Camera/Android/Fusee.Examples.Camera.Android.csproj | 2 +- .../Deferred/Android/Fusee.Examples.Deferred.Android.csproj | 2 +- .../Android/Fusee.Examples.GeometryEditing.Android.csproj | 2 +- .../Materials/Android/Fusee.Examples.Materials.Android.csproj | 2 +- .../Android/Fusee.Examples.MeshingAround.Android.csproj | 2 +- .../Picking/Android/Fusee.Examples.Picking.Android.csproj | 2 +- .../Android/Fusee.Examples.PickingRayCast.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderContextOnly.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderLayer.Android.csproj | 2 +- .../Simple/Android/Fusee.Examples.Simple.Android.csproj | 2 +- .../ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj | 2 +- Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj | 2 +- src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj | 2 +- src/Math/Core/Fusee.Math.Core.csproj | 2 +- src/Serialization/Fusee.Serialization.csproj | 2 +- src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj index ebb95559b..4ad6dc1dd 100644 --- a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj +++ b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj @@ -57,7 +57,7 @@ - + diff --git a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj index 3d93b788d..cad802c13 100644 --- a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj +++ b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj @@ -59,7 +59,7 @@ - + diff --git a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj index e524d7818..de55dd0fc 100644 --- a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj +++ b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj @@ -65,7 +65,7 @@ - + diff --git a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj index 32b4f85b4..7bcb8c717 100644 --- a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj +++ b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj index 5d7adf056..b97bda64b 100644 --- a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj +++ b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj @@ -64,7 +64,7 @@ - + diff --git a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj index 86444bd35..948920ac7 100644 --- a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj +++ b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj index 6df2ddab9..c6209f590 100644 --- a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj +++ b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj index 3e8db0b5a..fbb2d6125 100644 --- a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj +++ b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj index 33a4c552e..04e85d918 100644 --- a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj +++ b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj index b1807d4e5..4edfa13bd 100644 --- a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj +++ b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj index 0f80f0d3b..4234dc47b 100644 --- a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj +++ b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj index c2e1f4c17..770f87987 100644 --- a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj +++ b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj index bac7ed62e..0a2aef980 100644 --- a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj +++ b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj @@ -55,7 +55,7 @@ - + diff --git a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj index 9da2c80d0..a3291f7b2 100644 --- a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj +++ b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj @@ -56,7 +56,7 @@ - + diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index 895e2bdaa..d77c28b12 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/src/Serialization/Fusee.Serialization.csproj b/src/Serialization/Fusee.Serialization.csproj index 9c751e344..1c3e6fe49 100644 --- a/src/Serialization/Fusee.Serialization.csproj +++ b/src/Serialization/Fusee.Serialization.csproj @@ -19,7 +19,7 @@ analyzers - + \ No newline at end of file diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index 014dd1697..e00d0cf4d 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -14,6 +14,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 16a6453df1394c09259b0fb9c55d4de2c5aacc19 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 20 Mar 2023 08:44:12 +0100 Subject: [PATCH 145/294] wip --- .../Core/PointCloudPotree2Core.cs | 6 +- .../Core/PointRenderParams.cs | 2 +- src/PointCloud/Potree/Potree2LAS.cs | 70 +++++++++++++++++-- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 47d1e8144..20c642732 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -9,6 +9,7 @@ using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; namespace Fusee.Examples.PointCloudPotree2.Core @@ -77,10 +78,11 @@ public PointCloudPotree2Core(RenderContext rc) _potreeReader = new Potree2Reader(); var _potreedata = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); - + var sw = new Stopwatch(); + sw.Start(); using var laswriter = new Potree2LAS(new FileInfo("test.las"), _potreedata); laswriter.Write(); - + Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; } diff --git a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs index c69eaa1fc..9664e0180 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs @@ -15,7 +15,7 @@ public sealed class PointRenderingParams public PointThresholdHandler PointThresholdHandler; public ProjectedSizeModifierHandler ProjectedSizeModifierHandler; - public string PathToOocFile = Path.Combine("Assets", "Cube1030301", "Potree"); + public string PathToOocFile = Path.Combine("Assets", "test"/*"Cube1030301", "Potree"*/); public ShaderEffect DepthPassEf; public SurfaceEffectPointCloud ColorPassEf; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 523158af4..6df9acd3f 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -104,6 +104,13 @@ public LASPoint() { } internal ushort B = 0; } + public enum LASPointType : byte + { + Zero = 0x0, + Two = 0x2, + Seven = 0x7 + } + /// /// This class provides methods to convert and saves clouds to LAS 1.4 /// @@ -116,15 +123,26 @@ public class Potree2LAS : IPointWriter, IDisposable private bool disposedValue; private LASHeader _header; private readonly PotreeData _potreeData; + private readonly LASPointType _type; - public Potree2LAS(FileInfo savePath, PotreeData potreeData) + /// + /// Generate a writer instance, pass save path, Potree data and optional the point type to write + /// + /// + /// + /// + public Potree2LAS(FileInfo savePath, PotreeData potreeData, LASPointType ptType = LASPointType.Two) { Guard.IsNotNull(savePath); Guard.IsNotNull(potreeData); Guard.IsTrue(savePath.Extension == ".las"); + + _type = ptType; + if (savePath.Exists) { Diagnostics.Warn($"{savePath.FullName} does already exists. Overwriting ..."); + savePath.Delete(); } SavePath = savePath; @@ -145,7 +163,7 @@ private void ParseAndFillHeader() Guard.IsLessThan(year, ushort.MaxValue); Guard.IsLessThan(size, ushort.MaxValue); - _fileStream.Seek(0, SeekOrigin.Begin); + _fileStream.Seek(0, SeekOrigin.Begin); // TODO: Parse / generate fitting point type and extra bytes, etc... @@ -166,7 +184,9 @@ private void ParseAndFillHeader() ScaleFactorX = Metadata.Scale.x, ScaleFactorY = Metadata.Scale.y, ScaleFactorZ = Metadata.Scale.z, - NumberOfPtRecords = (ulong)Metadata.PointCount + NumberOfPtRecords = (ulong)Metadata.PointCount, + PointDataRecordFormat = (byte)_type + }; var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}"); @@ -191,6 +211,8 @@ private void ParseAndFillHeader() } } + delegate void ConvertPointMethod(Span data, Stream s); + /// /// This methods starts the LASfile write progress. /// @@ -204,15 +226,49 @@ public void Write(Action? progressCallback = null) using var stream = _potreeData.OctreeMappedFile.CreateViewStream(); var fileLength = Metadata.PointCount * Metadata.PointSize; + + Span tmpArry = (int)_type switch + { + 0 => stackalloc byte[666], // TODO + 2 => stackalloc byte[26 + 1], // + 1 due to wrong potree bytes + 7 => stackalloc byte[36 + 1], + _ => throw new NotImplementedException(), + }; ; + + ConvertPointMethod convertPtMethod = (int)_type switch + { + 0 => static (Span pt, Stream s) => + { + } + , + 2 => static (Span pt, Stream s) => + { + s.Write(pt[..12]); // position + s.Write(pt.Slice(12, 3)); // intensity, and mixed returns according to las 1.4 + // skip number of returns! [15,16] + s.Write(pt.Slice(16, 11)); // rest + } + , + 7 => static (Span pt, Stream s) => + { + s.Write(pt[..12]); // position + s.Write(pt.Slice(12, 3)); // intensity, and mixed returns according to las 1.4 + // skip number of returns! [15,16] + s.Write(pt.Slice(16, 21)); // rest + } + , + _ => throw new NotImplementedException(), + }; + // DO NOT USE stream.Length as the MemoryMappedStream aligns with the page size for (var i = 0; i < fileLength; i += Metadata.PointSize) { - progressCallback?.Invoke((int)(fileLength / Metadata.PointSize / 100f * i)); + float progress = (100f / Metadata.PointCount) * (i / Metadata.PointSize); + progressCallback?.Invoke((int)progress); // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors - + stream.Read(tmpArry); + convertPtMethod(tmpArry, _fileStream); } - - stream.CopyTo(_fileStream); } /// From 4d8f9f5a868a876c42fde1bacea0a8c6ae9b759d Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 20 Mar 2023 13:42:52 +0100 Subject: [PATCH 146/294] Ongoing conversion --- src/PointCloud/Potree/Potree2LAS.cs | 26 +++++++++---------- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 26 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6df9acd3f..fea6e4a66 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,17 +1,13 @@ using CommunityToolkit.Diagnostics; -using CommunityToolkit.HighPerformance; using Fusee.Base.Core; -using Fusee.Math.Core; using Fusee.PointCloud.Common; using System; using System.IO; using System.Reflection; -using System.Runtime.CompilerServices; -using CommunityToolkit.HighPerformance; using System.Runtime.InteropServices; using System.Text; -using System.Threading.Tasks; using Fusee.PointCloud.Potree.V2.Data; +using System.IO.MemoryMappedFiles; namespace Fusee.PointCloud.Potree { @@ -166,18 +162,17 @@ private void ParseAndFillHeader() _fileStream.Seek(0, SeekOrigin.Begin); // TODO: Parse / generate fitting point type and extra bytes, etc... - _header = new LASHeader { PointDataRecordLength = (ushort)size, FileCreationDayOfYear = (ushort)doy, FileCreationYear = (ushort)year, MaxX = Metadata.AABB.max.x, - MaxY = Metadata.AABB.max.y, - MaxZ = Metadata.AABB.max.z, + MaxY = Metadata.AABB.max.z, + MaxZ = Metadata.AABB.max.y, MinX = Metadata.AABB.min.x, - MinY = Metadata.AABB.min.y, - MinZ = Metadata.AABB.min.z, + MinY = Metadata.AABB.min.z, + MinZ = Metadata.AABB.min.y, OffsetX = Metadata.Offset.x, OffsetY = Metadata.Offset.y, OffsetZ = Metadata.Offset.z, @@ -185,10 +180,13 @@ private void ParseAndFillHeader() ScaleFactorY = Metadata.Scale.y, ScaleFactorZ = Metadata.Scale.z, NumberOfPtRecords = (ulong)Metadata.PointCount, + LegacyNbrOfPoints = (uint)Metadata.PointCount, PointDataRecordFormat = (byte)_type - }; + //_header.LeacyNbrOfPointsByRtn[0] = (uint)Metadata.PointCount; + //_header.NbrOfPointsByReturn[0] = (ulong)Metadata.PointCount; + var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}"); Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); @@ -212,6 +210,7 @@ private void ParseAndFillHeader() } delegate void ConvertPointMethod(Span data, Stream s); + delegate void WriteStreamChunk(MemoryMappedFile file, long start, long end); /// /// This methods starts the LASfile write progress. @@ -229,7 +228,7 @@ public void Write(Action? progressCallback = null) Span tmpArry = (int)_type switch { - 0 => stackalloc byte[666], // TODO + //0 => stackalloc byte[666], // TODO 2 => stackalloc byte[26 + 1], // + 1 due to wrong potree bytes 7 => stackalloc byte[36 + 1], _ => throw new NotImplementedException(), @@ -239,11 +238,12 @@ public void Write(Action? progressCallback = null) { 0 => static (Span pt, Stream s) => { + throw new NotImplementedException(); } , 2 => static (Span pt, Stream s) => { - s.Write(pt[..12]); // position + s.Write(pt[..15]); // position s.Write(pt.Slice(12, 3)); // intensity, and mixed returns according to las 1.4 // skip number of returns! [15,16] s.Write(pt.Slice(16, 11)); // rest diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs index 2258e4dac..29dae67da 100644 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs @@ -226,8 +226,8 @@ protected void CacheMetadata(bool force = false) Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); - var Metadata = LoadPotreeMetadata(metadataFilePath); - var Hierarchy = new PotreeHierarchy() + var metadata = LoadPotreeMetadata(metadataFilePath); + var hierarchy = new PotreeHierarchy() { Root = new() { @@ -235,29 +235,29 @@ protected void CacheMetadata(bool force = false) } }; - Metadata.Attributes = GetAttributesDict(Metadata.AttributesList); + metadata.Attributes = GetAttributesDict(metadata.AttributesList); - Metadata.FolderPath = folderPath; + metadata.FolderPath = folderPath; - CalculateAttributeOffsets(ref Metadata); + CalculateAttributeOffsets(ref metadata); - Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); + hierarchy.Root.Aabb = new AABBd(metadata.BoundingBox.Min, metadata.BoundingBox.Max); var data = File.ReadAllBytes(hierarchyFilePath); Guard.IsNotNull(data, nameof(data)); - LoadHierarchyRecursive(ref Hierarchy.Root, ref data, 0, Metadata.Hierarchy.FirstChunkSize); + LoadHierarchyRecursive(ref hierarchy.Root, ref data, 0, metadata.Hierarchy.FirstChunkSize); - Hierarchy.Nodes = new(); - Hierarchy.Root.Traverse(n => Hierarchy.Nodes.Add(n)); + hierarchy.Nodes = new(); + hierarchy.Root.Traverse(n => hierarchy.Nodes.Add(n)); - FlipYZAxis(Metadata, Hierarchy); + FlipYZAxis(metadata, hierarchy); - Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; - Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; + //metadata.BoundingBox.MinList = new List(3) { hierarchy.Root.Aabb.min.x, hierarchy.Root.Aabb.min.y, hierarchy.Root.Aabb.min.z }; + //metadata.BoundingBox.MaxList = new List(3) { hierarchy.Root.Aabb.max.x, hierarchy.Root.Aabb.max.y, hierarchy.Root.Aabb.max.z }; - return (Metadata, Hierarchy); + return (metadata, hierarchy); } private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) From 5966471d2eedd8ca1b563dcc59144cc8c5e83237 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 29 Mar 2023 09:42:00 +0200 Subject: [PATCH 147/294] Potree Reader/Writer back to a common base class. PotreeData holds view accessors. --- src/PointCloud/Potree/V2/Data/PotreeData.cs | 36 +- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 153 +++++++ src/PointCloud/Potree/V2/Potree2Reader.cs | 324 +++++++++++++- src/PointCloud/Potree/V2/Potree2ReaderBase.cs | 422 ------------------ src/PointCloud/Potree/V2/Potree2Writer.cs | 2 +- src/PointCloud/Potree/V2/Potree2WriterBase.cs | 197 -------- 6 files changed, 488 insertions(+), 646 deletions(-) create mode 100644 src/PointCloud/Potree/V2/Potree2AccessBase.cs delete mode 100644 src/PointCloud/Potree/V2/Potree2ReaderBase.cs delete mode 100644 src/PointCloud/Potree/V2/Potree2WriterBase.cs diff --git a/src/PointCloud/Potree/V2/Data/PotreeData.cs b/src/PointCloud/Potree/V2/Data/PotreeData.cs index ab063804d..d07c05ffb 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeData.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeData.cs @@ -1,4 +1,5 @@ -using System; +using Fusee.PointCloud.Common; +using System; using System.IO; using System.IO.MemoryMappedFiles; @@ -24,6 +25,17 @@ public class PotreeData : IDisposable /// internal MemoryMappedFile OctreeMappedFile { get; } + /// + /// Returns a reference to the memory mapped file view accessor for reading. + /// + internal MemoryMappedViewAccessor ReadViewAccessor { get; } + + /// + /// Returns a reference to the memory mapped file view accessor for writing. + /// + internal MemoryMappedViewAccessor WriteViewAccessor { get; } + + /// /// Creats a new instance of PotreeData /// @@ -37,8 +49,22 @@ public PotreeData(PotreeHierarchy potreeHierarchy, PotreeMetadata potreeMetadata var path = Path.Combine(Metadata.FolderPath, Potree2Consts.OctreeFileName); OctreeMappedFile = MemoryMappedFile.CreateFromFile(path, FileMode.Open); + ReadViewAccessor = OctreeMappedFile.CreateViewAccessor(); + WriteViewAccessor = OctreeMappedFile.CreateViewAccessor(); } + /// + /// Returns the node for the given . + /// + /// + /// + public PotreeNode? GetNode(OctantId octantId) + { + return Hierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(octantId)); + } + + #region IDisposable + private bool disposedValue; /// @@ -48,12 +74,11 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - // TODO: dispose managed state (managed objects) + ReadViewAccessor.Dispose(); + WriteViewAccessor.Dispose(); OctreeMappedFile.Dispose(); } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null disposedValue = true; } } @@ -61,9 +86,10 @@ protected virtual void Dispose(bool disposing) /// public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } + + #endregion IDisposable } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs new file mode 100644 index 000000000..7587ec963 --- /dev/null +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -0,0 +1,153 @@ +using CommunityToolkit.Diagnostics; +using Fusee.PointCloud.Potree.V2.Data; + +namespace Fusee.PointCloud.Potree.V2 +{ + /// + /// This is the base class for accessing Potree files + /// + public abstract class Potree2AccessBase + { + /// + /// The + /// + public PotreeData? PotreeData { get; set; } + + /// + /// Pass method how to handle the extra bytes, resulting uint will be passed into . + /// + public HandleExtraBytes? HandleExtraBytes { get; set; } + + /// + /// Constructs a new instance of Potree2AccessBase + /// + /// + public Potree2AccessBase(PotreeData potreeData) + { + PotreeData = potreeData; + } + + /// + /// Constructs a new instance of Potree2AccessBase + /// + protected Potree2AccessBase() { } + + #region Metadata caching + + /// + /// Save if metadata has already been cached + /// + protected bool _isMetadataCached = false; + + /// + /// Offset in bytes to the position value in bytes in raw Potree stream + /// + protected int offsetPosition = -1; + /// + /// Offset in bytes to the intensity value in bytes in raw Potree stream + /// + protected int offsetIntensity = -1; + /// + /// Offset in bytes to the return number value in bytes in raw Potree stream + /// + protected int offsetReturnNumber = -1; + /// + /// Offset in bytes to the number of returns value in bytes in raw Potree stream + /// + protected int offsetNumberOfReturns = -1; + /// + /// Offset in bytes to the classification value in bytes in raw Potree stream + /// + protected int offsetClassification = -1; + /// + /// Offset in bytes to the scan angle rank value in bytes in raw Potree stream + /// + protected int offsetScanAngleRank = -1; + /// + /// Offset in bytes to the user data value in bytes in raw Potree stream + /// + protected int offsetUserData = -1; + /// + /// Offset in bytes to the point source id value in bytes in raw Potree stream + /// + protected int offsetPointSourceId = -1; + /// + /// Offset in bytes to the color value in bytes in raw Potree stream + /// + protected int offsetColor = -1; + + /// + /// Read and cache metadata from the metadata.json file + /// + protected void CacheMetadata(bool force = false) + { + Guard.IsNotNull(PotreeData); + + if (!_isMetadataCached || force) + { + offsetPosition = -1; + offsetIntensity = -1; + offsetReturnNumber = -1; + offsetNumberOfReturns = -1; + offsetClassification = -1; + offsetScanAngleRank = -1; + offsetUserData = -1; + offsetPointSourceId = -1; + offsetColor = -1; + + if (PotreeData.Metadata.Attributes.TryGetValue("position", out var position)) + { + offsetPosition = position.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("intensity", out var intensity)) + { + offsetIntensity = intensity.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("return number", out var returnNumber)) + { + offsetReturnNumber = returnNumber.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("number of returns", out var numberOfReturns)) + { + offsetNumberOfReturns = numberOfReturns.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("classification", out var classification)) + { + offsetClassification = classification.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("scan angle rank", out var scanAngleRank)) + { + offsetScanAngleRank = scanAngleRank.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("user data", out var userData)) + { + offsetUserData = userData.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("point source id", out var pointSourceId)) + { + offsetPointSourceId = pointSourceId.AttributeOffset; + } + if (PotreeData.Metadata.Attributes.TryGetValue("rgb", out var rgb)) + { + offsetColor = rgb.AttributeOffset; + } + + int pointSize = 0; + + if (PotreeData.Metadata != null) + { + foreach (var metaAttributeItem in PotreeData.Metadata.AttributesList) + { + pointSize += metaAttributeItem.Size; + } + + PotreeData.Metadata.PointSize = pointSize; + } + + _isMetadataCached = true; + } + } + + #endregion Metadata caching + } +} diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index b5081f5ce..05bc96b17 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -8,7 +8,11 @@ using Fusee.PointCloud.Core; using Fusee.PointCloud.Core.Scene; using Fusee.PointCloud.Potree.V2.Data; +using Newtonsoft.Json; using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Runtime.InteropServices; namespace Fusee.PointCloud.Potree.V2 @@ -16,24 +20,29 @@ namespace Fusee.PointCloud.Potree.V2 /// /// Reads Potree V2 files and is able to create a point cloud scene component, that can be rendered. /// - public class Potree2Reader : Potree2ReaderBase, IPointReader + public class Potree2Reader : Potree2AccessBase, IPointReader { /// /// Specify the byte offset for one point until the extra byte data is reached /// - public readonly int OffsetToExtraBytes; + public int OffsetToExtraBytes = -1; /// - /// Pass method how to handle the extra bytes, resulting uint will be passed into . + /// Generate a new instance of . /// - public Func? HandleExtraBytes { get; set; } + /// + public Potree2Reader(string filepath) + { + ReadNewFile(filepath); + } /// /// Generate a new instance of . /// - /// - public Potree2Reader(int offsetToExtraBytes = 0) : base() => OffsetToExtraBytes = offsetToExtraBytes; - + /// + public Potree2Reader(PotreeData potreeData) : base(potreeData) + { + } /// /// Returns a renderable point cloud component. @@ -105,7 +114,7 @@ public IPointCloudOctree GetOctree() public MemoryOwner LoadVisualizationPointData(OctantId id) { Guard.IsNotNull(PotreeData); - var node = FindNode(ref PotreeData.Hierarchy, id); + var node = PotreeData.GetNode(id); // if node is null the hierarchy is broken and we look for an octant that isn't there... Guard.IsNotNull(node); @@ -116,22 +125,21 @@ public MemoryOwner LoadVisualizationPointData(OctantId id) private MemoryOwner LoadVisualizationPoint(PotreeNode node) { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); - if (HandleExtraBytes != null) - Guard.IsGreaterThan(OffsetToExtraBytes, 0); Guard.IsNotNull(PotreeData); + Guard.IsNotNull(PotreeData.ReadViewAccessor); - var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; - var pointArray = new byte[potreePointSize]; + var potreeNodeSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreeNodeSize]; var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); - OctreeMappedViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); - - var pointCount = 0; + PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreeNodeSize); - for (var i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) + for (var i = 0; i < node.NumPoints; i++) { - var posSlice = new Span(pointArray).Slice(i + offsetPosition, Marshal.SizeOf() * 3); + var currentPointOffset = i * PotreeData.Metadata.PointSize; + + var posSlice = new Span(pointArray).Slice(currentPointOffset + offsetPosition, Marshal.SizeOf() * 3); var pos = MemoryMarshal.Cast(posSlice); double x = pos[0] * PotreeData.Metadata.Scale.x; @@ -143,7 +151,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var posSpan = MemoryMarshal.Cast(position.ToArray()); - var colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); + var colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetColor, Marshal.SizeOf() * 3); var rgb = MemoryMarshal.Cast(colorSlice); var color = float4.Zero; @@ -161,16 +169,82 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) uint flags = 0; if (HandleExtraBytes != null) { - flags = HandleExtraBytes(extraBytesSpan.ToArray()); + var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; + var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); + + if (HandleExtraBytes != null) + { + flags = HandleExtraBytes(extraBytesSpan); + } } var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); - var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); + var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(i, 1)); posSpan.CopyTo(currentMemoryPt[..]); colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); flagsSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length + colorSpan.Length, Marshal.SizeOf())); + } + + return returnMemory; + } + + private MemoryOwner LoadNodeRaw(PotreeNode node) + { + Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + if (HandleExtraBytes != null) + Guard.IsGreaterThan(OffsetToExtraBytes, 0); + Guard.IsNotNull(PotreeData); + Guard.IsNotNull(PotreeData.ReadViewAccessor); + + var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; + + var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); + + PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); + + for (var i = 0; i < node.NumPoints; i++) + { + var pointOffset = i * PotreeData.Metadata.PointSize; + + // var posSlice = new Span(pointArray).Slice(pointOffset + offsetPosition, Marshal.SizeOf() * 3); + // var pos = MemoryMarshal.Cast(posSlice); + + // double x = pos[0] * PotreeData.Metadata.Scale.x; + // double y = pos[1] * PotreeData.Metadata.Scale.y; + // double z = pos[2] * PotreeData.Metadata.Scale.z; + + // float3 position = new((float)x, (float)y, (float)z); + // position = (float4x4)Potree2Consts.YZflip * position; - pointCount++; + // var posSpan = MemoryMarshal.Cast(position.ToArray()); + + // var colorSlice = new Span(pointArray).Slice(pointOffset + offsetColor, Marshal.SizeOf() * 3); + // var rgb = MemoryMarshal.Cast(colorSlice); + + // var color = float4.Zero; + + // color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); + // color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); + // color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); + // color.a = 1; + + // var colorSpan = MemoryMarshal.Cast(color.ToArray()); + + // var extraByteSize = PotreeData.Metadata.PointSize - OffsetToExtraBytes; + // var extraBytesSpan = pointArray.AsSpan().Slice(pointOffset + OffsetToExtraBytes, extraByteSize); + + // uint flags = 0; + // if (HandleExtraBytes != null) + // { + // flags = HandleExtraBytes(extraBytesSpan.ToArray()); + // } + // var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); + + // var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(i, 1)); + // posSpan.CopyTo(currentMemoryPt[..]); + // colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); + // flagsSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length + colorSpan.Length, Marshal.SizeOf())); } return returnMemory; @@ -199,5 +273,213 @@ private static void MapChildNodesRecursive(IPointCloudOctant octreeNode, PotreeN } } } + + /// + /// Reads a potree file. + /// + /// Path to the file. + /// Meta and octree data of the potree file. + public PotreeData ReadNewFile(string path) + { + (var Metadata, var Hierarchy) = LoadHierarchy(path); + + PotreeData = new PotreeData(Hierarchy, Metadata); + + CacheMetadata(true); + + return PotreeData; + } + + /// + /// Changes the potree data package that is currently bound to the reader. So a reader can be used for multiple data packages, this avoids rereading the potree data like in . + /// + /// Meta and octree data of the potree file. + public void ReadFile(PotreeData potreeData) + { + PotreeData = potreeData; + + CacheMetadata(true); + } + + #region LoadHierarchy + + private (PotreeMetadata, PotreeHierarchy) LoadHierarchy(string folderPath) + { + var metadataFilePath = Path.Combine(folderPath, Potree2Consts.MetadataFileName); + var hierarchyFilePath = Path.Combine(folderPath, Potree2Consts.HierarchyFileName); + + Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); + Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); + + var Metadata = LoadPotreeMetadata(metadataFilePath); + var Hierarchy = new PotreeHierarchy() + { + Root = new() + { + Name = "r", + } + }; + + Metadata.Attributes = GetAttributesDict(Metadata.AttributesList); + + Metadata.FolderPath = folderPath; + + CalculateAttributeOffsets(ref Metadata); + + Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); + + var data = File.ReadAllBytes(hierarchyFilePath); + + Guard.IsNotNull(data, nameof(data)); + + LoadHierarchyRecursive(ref Hierarchy.Root, ref data, 0, Metadata.Hierarchy.FirstChunkSize); + + Hierarchy.Nodes = new(); + Hierarchy.Root.Traverse(n => Hierarchy.Nodes.Add(n)); + + FlipYZAxis(Metadata, Hierarchy); + + Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; + Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; + + return (Metadata, Hierarchy); + } + + private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) + { + var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); + + Guard.IsNotNull(potreeData, nameof(potreeData)); + + return potreeData; + } + + private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) + { + int bytesPerNode = 22; + int numNodes = (int)(size / bytesPerNode); + + var nodes = new List(numNodes) + { + root + }; + + for (int i = 0; i < numNodes; i++) + { + var currentNode = nodes[i]; + if (currentNode == null) + currentNode = new PotreeNode(); + + ulong offsetNode = (ulong)offset + (ulong)(i * bytesPerNode); + + var nodeType = data[offsetNode + 0]; + int childMask = BitConverter.ToInt32(data, (int)offsetNode + 1); + var numPoints = BitConverter.ToUInt32(data, (int)offsetNode + 2); + var byteOffset = BitConverter.ToInt64(data, (int)offsetNode + 6); + var byteSize = BitConverter.ToInt64(data, (int)offsetNode + 14); + + currentNode.NodeType = (NodeType)nodeType; + currentNode.NumPoints = numPoints; + currentNode.ByteOffset = byteOffset; + currentNode.ByteSize = byteSize; + + if (currentNode.NodeType == NodeType.PROXY) + { + LoadHierarchyRecursive(ref currentNode, ref data, byteOffset, byteSize); + } + else + { + for (int childIndex = 0; childIndex < 8; childIndex++) + { + bool childExists = (1 << childIndex & childMask) != 0; + + if (!childExists) + { + continue; + } + + string childName = currentNode.Name + childIndex.ToString(); + + PotreeNode child = new() + { + Aabb = ChildAABB(currentNode.Aabb, childIndex), + Name = childName + }; + currentNode.Children[childIndex] = child; + child.Parent = currentNode; + + nodes.Add(child); + } + } + } + + static AABBd ChildAABB(AABBd aabb, int index) + { + + double3 min = aabb.min; + double3 max = aabb.max; + + double3 size = max - min; + + if ((index & 0b0001) > 0) + { + min.z += size.z / 2; + } + else + { + max.z -= size.z / 2; + } + + if ((index & 0b0010) > 0) + { + min.y += size.y / 2; + } + else + { + max.y -= size.y / 2; + } + + if ((index & 0b0100) > 0) + { + min.x += size.x / 2; + } + else + { + max.x -= size.x / 2; + } + + return new AABBd(min, max); + } + } + + private void FlipYZAxis(PotreeMetadata potreeMetadata, PotreeHierarchy potreeHierarchy) + { + for (int i = 0; i < potreeHierarchy.Nodes.Count; i++) + { + var node = potreeHierarchy.Nodes[i]; + node.Aabb = new AABBd(Potree2Consts.YZflip * (node.Aabb.min - potreeMetadata.Offset), Potree2Consts.YZflip * (node.Aabb.max - potreeMetadata.Offset)); + } + potreeMetadata.OffsetList = new List(3) { potreeMetadata.Offset.x, potreeMetadata.Offset.z, potreeMetadata.Offset.y }; + potreeMetadata.ScaleList = new List(3) { potreeMetadata.Scale.x, potreeMetadata.Scale.z, potreeMetadata.Scale.y }; + } + + private static void CalculateAttributeOffsets(ref PotreeMetadata potreeMetadata) + { + var attributeOffset = 0; + + for (int i = 0; i < potreeMetadata.AttributesList.Count; i++) + { + potreeMetadata.AttributesList[i].AttributeOffset = attributeOffset; + + attributeOffset += potreeMetadata.AttributesList[i].Size; + } + } + + private static Dictionary GetAttributesDict(List attributes) + { + return attributes.ToDictionary(x => x.Name, x => x); + } + + #endregion LoadHierarchy } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs b/src/PointCloud/Potree/V2/Potree2ReaderBase.cs deleted file mode 100644 index 96b266eed..000000000 --- a/src/PointCloud/Potree/V2/Potree2ReaderBase.cs +++ /dev/null @@ -1,422 +0,0 @@ -using CommunityToolkit.Diagnostics; -using Fusee.Math.Core; -using Fusee.PointCloud.Common; -using Fusee.PointCloud.Potree.V2.Data; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.MemoryMappedFiles; -using System.Linq; - -namespace Fusee.PointCloud.Potree.V2 -{ - /// - /// This is the base class for reading and writing s. - /// - public abstract class Potree2ReaderBase : IDisposable - { - /// - /// The - /// - public PotreeData? PotreeData - { - get => _potreeData; - private set - { - _potreeData = value; - } - } - private PotreeData? _potreeData; - - /// - /// Save if metadata has already been cached - /// - protected bool _isMetadataCached = false; - - /// - /// Offset in bytes to the position value in bytes in raw Potree stream - /// - protected int offsetPosition = -1; - /// - /// Offset in bytes to the intensity value in bytes in raw Potree stream - /// - protected int offsetIntensity = -1; - /// - /// Offset in bytes to the return number value in bytes in raw Potree stream - /// - protected int offsetReturnNumber = -1; - /// - /// Offset in bytes to the number of returns value in bytes in raw Potree stream - /// - protected int offsetNumberOfReturns = -1; - /// - /// Offset in bytes to the classification value in bytes in raw Potree stream - /// - protected int offsetClassification = -1; - /// - /// Offset in bytes to the scan angle rank value in bytes in raw Potree stream - /// - protected int offsetScanAngleRank = -1; - /// - /// Offset in bytes to the user data value in bytes in raw Potree stream - /// - protected int offsetUserData = -1; - /// - /// Offset in bytes to the point source id value in bytes in raw Potree stream - /// - protected int offsetPointSourceId = -1; - /// - /// Offset in bytes to the color value in bytes in raw Potree stream - /// - protected int offsetColor = -1; - - private MemoryMappedViewAccessor? _octreeViewAccessor; - private bool disposedValue; - - /// - /// The to the underlying octree.bin file. This is threadsafe since there is never any overlapping access per our design. - /// - protected MemoryMappedViewAccessor OctreeMappedViewAccessor - { - get - { - Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); - - return _octreeViewAccessor; - } - set - { - _octreeViewAccessor?.Dispose(); - - _octreeViewAccessor = value; - } - } - - /// - /// Reads a potree file. - /// - /// Path to the file. - /// Meta and octree data of the potree file. - public PotreeData ReadNewFile(string path) - { - (var Metadata, var Hierarchy) = LoadHierarchy(path); - - PotreeData = new PotreeData(Hierarchy, Metadata); - - CacheMetadata(true); - - OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); - - return PotreeData; - } - - /// - /// Changes the potree data package that is currently bound to the reader. So a reader can be used for multiple data packages, this avoids rereading the potree data like in . - /// - /// Meta and octree data of the potree file. - public void ReadFile(PotreeData potreeData) - { - PotreeData = potreeData; - - CacheMetadata(true); - - OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); - } - - /// - /// Read and cache metadata from the metadata.json file - /// - protected void CacheMetadata(bool force = false) - { - Guard.IsNotNull(_potreeData); - - if (!_isMetadataCached || force) - { - offsetPosition = -1; - offsetIntensity = -1; - offsetReturnNumber = -1; - offsetNumberOfReturns = -1; - offsetClassification = -1; - offsetScanAngleRank = -1; - offsetUserData = -1; - offsetPointSourceId = -1; - offsetColor = -1; - - if (_potreeData.Metadata.Attributes.ContainsKey("position")) - { - offsetPosition = _potreeData.Metadata.Attributes["position"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("intensity")) - { - offsetIntensity = _potreeData.Metadata.Attributes["intensity"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("return number")) - { - offsetReturnNumber = _potreeData.Metadata.Attributes["return number"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("number of returns")) - { - offsetNumberOfReturns = _potreeData.Metadata.Attributes["number of returns"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("classification")) - { - offsetClassification = _potreeData.Metadata.Attributes["classification"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("scan angle rank")) - { - offsetScanAngleRank = _potreeData.Metadata.Attributes["scan angle rank"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("user data")) - { - offsetUserData = _potreeData.Metadata.Attributes["user data"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("point source id")) - { - offsetPointSourceId = _potreeData.Metadata.Attributes["point source id"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("rgb")) - { - offsetColor = _potreeData.Metadata.Attributes["rgb"].AttributeOffset; - } - - int pointSize = 0; - - if (_potreeData.Metadata != null) - { - foreach (var metaAttributeItem in _potreeData.Metadata.AttributesList) - { - pointSize += metaAttributeItem.Size; - } - - _potreeData.Metadata.PointSize = pointSize; - } - - _isMetadataCached = true; - } - } - - /// - /// Iterate the hierarchy, find the node from the given . - /// - /// - /// - /// - public static PotreeNode? FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) - { - return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); - } - - #region LoadHierarchy - - private (PotreeMetadata, PotreeHierarchy) LoadHierarchy(string folderPath) - { - var metadataFilePath = Path.Combine(folderPath, Potree2Consts.MetadataFileName); - var hierarchyFilePath = Path.Combine(folderPath, Potree2Consts.HierarchyFileName); - - Guard.IsTrue(File.Exists(metadataFilePath), metadataFilePath); - Guard.IsTrue(File.Exists(metadataFilePath), hierarchyFilePath); - - var Metadata = LoadPotreeMetadata(metadataFilePath); - var Hierarchy = new PotreeHierarchy() - { - Root = new() - { - Name = "r", - } - }; - - Metadata.Attributes = GetAttributesDict(Metadata.AttributesList); - - Metadata.FolderPath = folderPath; - - CalculateAttributeOffsets(ref Metadata); - - Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); - - var data = File.ReadAllBytes(hierarchyFilePath); - - Guard.IsNotNull(data, nameof(data)); - - LoadHierarchyRecursive(ref Hierarchy.Root, ref data, 0, Metadata.Hierarchy.FirstChunkSize); - - Hierarchy.Nodes = new(); - Hierarchy.Root.Traverse(n => Hierarchy.Nodes.Add(n)); - - FlipYZAxis(Metadata, Hierarchy); - - Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; - Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; - - return (Metadata, Hierarchy); - } - - private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) - { - var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); - - Guard.IsNotNull(potreeData, nameof(potreeData)); - - return potreeData; - } - - private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) - { - int bytesPerNode = 22; - int numNodes = (int)(size / bytesPerNode); - - var nodes = new List(numNodes) - { - root - }; - - for (int i = 0; i < numNodes; i++) - { - var currentNode = nodes[i]; - if (currentNode == null) - currentNode = new PotreeNode(); - - ulong offsetNode = (ulong)offset + (ulong)(i * bytesPerNode); - - var nodeType = data[offsetNode + 0]; - int childMask = BitConverter.ToInt32(data, (int)offsetNode + 1); - var numPoints = BitConverter.ToUInt32(data, (int)offsetNode + 2); - var byteOffset = BitConverter.ToInt64(data, (int)offsetNode + 6); - var byteSize = BitConverter.ToInt64(data, (int)offsetNode + 14); - - currentNode.NodeType = (NodeType)nodeType; - currentNode.NumPoints = numPoints; - currentNode.ByteOffset = byteOffset; - currentNode.ByteSize = byteSize; - - if (currentNode.NodeType == NodeType.PROXY) - { - LoadHierarchyRecursive(ref currentNode, ref data, byteOffset, byteSize); - } - else - { - for (int childIndex = 0; childIndex < 8; childIndex++) - { - bool childExists = (1 << childIndex & childMask) != 0; - - if (!childExists) - { - continue; - } - - string childName = currentNode.Name + childIndex.ToString(); - - PotreeNode child = new() - { - Aabb = ChildAABB(currentNode.Aabb, childIndex), - Name = childName - }; - currentNode.Children[childIndex] = child; - child.Parent = currentNode; - - nodes.Add(child); - } - } - } - - static AABBd ChildAABB(AABBd aabb, int index) - { - - double3 min = aabb.min; - double3 max = aabb.max; - - double3 size = max - min; - - if ((index & 0b0001) > 0) - { - min.z += size.z / 2; - } - else - { - max.z -= size.z / 2; - } - - if ((index & 0b0010) > 0) - { - min.y += size.y / 2; - } - else - { - max.y -= size.y / 2; - } - - if ((index & 0b0100) > 0) - { - min.x += size.x / 2; - } - else - { - max.x -= size.x / 2; - } - - return new AABBd(min, max); - } - } - - private void FlipYZAxis(PotreeMetadata potreeMetadata, PotreeHierarchy potreeHierarchy) - { - for (int i = 0; i < potreeHierarchy.Nodes.Count; i++) - { - var node = potreeHierarchy.Nodes[i]; - node.Aabb = new AABBd(Potree2Consts.YZflip * (node.Aabb.min - potreeMetadata.Offset), Potree2Consts.YZflip * (node.Aabb.max - potreeMetadata.Offset)); - } - potreeMetadata.OffsetList = new List(3) { potreeMetadata.Offset.x, potreeMetadata.Offset.z, potreeMetadata.Offset.y }; - potreeMetadata.ScaleList = new List(3) { potreeMetadata.Scale.x, potreeMetadata.Scale.z, potreeMetadata.Scale.y }; - } - - private static void CalculateAttributeOffsets(ref PotreeMetadata potreeMetadata) - { - var attributeOffset = 0; - - for (int i = 0; i < potreeMetadata.AttributesList.Count; i++) - { - potreeMetadata.AttributesList[i].AttributeOffset = attributeOffset; - - attributeOffset += potreeMetadata.AttributesList[i].Size; - } - } - - private static Dictionary GetAttributesDict(List attributes) - { - return attributes.ToDictionary(x => x.Name, x => x); - } - - #endregion LoadHierarchy - - - /// - /// Disposes of the MemoryMapped objects to free the file. - /// - /// - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects) - _octreeViewAccessor?.Dispose(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - disposedValue = true; - } - } - - /// - /// Manually calling this disposes of the MemoryMapped objects to free the file. - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 56b449c9a..2c2653c82 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -8,7 +8,7 @@ namespace Fusee.PointCloud.Potree.V2 /// /// Writes Potree data /// - public class Potree2Writer : Potree2WriterBase + public class Potree2Writer : Potree2AccessBase { /// /// Generate a instance. diff --git a/src/PointCloud/Potree/V2/Potree2WriterBase.cs b/src/PointCloud/Potree/V2/Potree2WriterBase.cs deleted file mode 100644 index f14552093..000000000 --- a/src/PointCloud/Potree/V2/Potree2WriterBase.cs +++ /dev/null @@ -1,197 +0,0 @@ -using CommunityToolkit.Diagnostics; -using Fusee.PointCloud.Common; -using Fusee.PointCloud.Potree.V2.Data; -using System; -using System.IO; -using System.IO.MemoryMappedFiles; - -namespace Fusee.PointCloud.Potree.V2 -{ - //TODO: cleanup fields/methods that arent necessary for the writer but are present due to this classes historic relation to the reader. - /// - /// This is the base class for reading and writing s. - /// - public abstract class Potree2WriterBase : IDisposable - { - /// - /// The - /// - public PotreeData? PotreeData - { - get => _potreeData; - private set - { - _potreeData = value; - } - } - protected PotreeData? _potreeData; - - /// - /// Save if metadata has already been cached - /// - protected bool isMetadataCached = false; - - /// - /// Offset in bytes to the position value in bytes in raw Potree stream - /// - protected int offsetPosition = -1; - /// - /// Offset in bytes to the intensity value in bytes in raw Potree stream - /// - protected int offsetIntensity = -1; - /// - /// Offset in bytes to the return number value in bytes in raw Potree stream - /// - protected int offsetReturnNumber = -1; - /// - /// Offset in bytes to the number of returns value in bytes in raw Potree stream - /// - protected int offsetNumberOfReturns = -1; - /// - /// Offset in bytes to the classification value in bytes in raw Potree stream - /// - protected int offsetClassification = -1; - /// - /// Offset in bytes to the scan angle rank value in bytes in raw Potree stream - /// - protected int offsetScanAngleRank = -1; - /// - /// Offset in bytes to the user data value in bytes in raw Potree stream - /// - protected int offsetUserData = -1; - /// - /// Offset in bytes to the point source id value in bytes in raw Potree stream - /// - protected int offsetPointSourceId = -1; - /// - /// Offset in bytes to the color value in bytes in raw Potree stream - /// - protected int offsetColor = -1; - - private MemoryMappedViewAccessor? _octreeViewAccessor; - private bool disposedValue; - - /// - /// The to the underlying octree.bin file - /// - protected MemoryMappedViewAccessor OctreeMappedViewAccessor - { - get - { - Guard.IsNotNull(_octreeViewAccessor, nameof(_octreeViewAccessor)); - - return _octreeViewAccessor; - } - set - { - _octreeViewAccessor?.Dispose(); - - _octreeViewAccessor = value; - } - } - - /// - /// Ctor for RW base. Reads and chaches metadata and sets the path to the octree.bin file - /// - public Potree2WriterBase(PotreeData potreeData) - { - PotreeData = potreeData; - - OctreeMappedViewAccessor = PotreeData.OctreeMappedFile.CreateViewAccessor(); - } - - /// - /// Read and cache metadata from the metadata.json file - /// - protected void CacheMetadata() - { - if (!isMetadataCached) - { - if (_potreeData.Metadata.Attributes.ContainsKey("position")) - { - offsetPosition = _potreeData.Metadata.Attributes["position"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("intensity")) - { - offsetIntensity = _potreeData.Metadata.Attributes["intensity"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("return number")) - { - offsetReturnNumber = _potreeData.Metadata.Attributes["return number"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("number of returns")) - { - offsetNumberOfReturns = _potreeData.Metadata.Attributes["number of returns"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("classification")) - { - offsetClassification = _potreeData.Metadata.Attributes["classification"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("scan angle rank")) - { - offsetScanAngleRank = _potreeData.Metadata.Attributes["scan angle rank"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("user data")) - { - offsetUserData = _potreeData.Metadata.Attributes["user data"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("point source id")) - { - offsetPointSourceId = _potreeData.Metadata.Attributes["point source id"].AttributeOffset; - } - if (_potreeData.Metadata.Attributes.ContainsKey("rgb")) - { - offsetColor = _potreeData.Metadata.Attributes["rgb"].AttributeOffset; - } - - int pointSize = 0; - - if (_potreeData.Metadata != null) - { - foreach (var metaAttributeItem in _potreeData.Metadata.AttributesList) - { - pointSize += metaAttributeItem.Size; - } - - _potreeData.Metadata.PointSize = pointSize; - } - - isMetadataCached = true; - } - } - - /// - /// Iterate the hierarchy, find the node from the given . - /// - /// - /// - /// - public static PotreeNode FindNode(ref PotreeHierarchy potreeHierarchy, OctantId id) - { - return potreeHierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(id)); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects) - _octreeViewAccessor?.Dispose(); - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - disposedValue = true; - } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file From 5b6664d64543bbcb119711b364830fdc147b5477 Mon Sep 17 00:00:00 2001 From: ASPePeX Date: Wed, 29 Mar 2023 07:57:14 +0000 Subject: [PATCH 148/294] Linting --- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 7587ec963..4472d5dbc 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -150,4 +150,4 @@ protected void CacheMetadata(bool force = false) #endregion Metadata caching } -} +} \ No newline at end of file From e85aa62c41d099b6035ddc8090ed34d9fe4e5775 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 29 Mar 2023 10:18:13 +0200 Subject: [PATCH 149/294] Fixed merge problems --- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 3 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 91 +++---------------- 2 files changed, 15 insertions(+), 79 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 4472d5dbc..6f3aa54e1 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Diagnostics; using Fusee.PointCloud.Potree.V2.Data; +using System; namespace Fusee.PointCloud.Potree.V2 { @@ -16,7 +17,7 @@ public abstract class Potree2AccessBase /// /// Pass method how to handle the extra bytes, resulting uint will be passed into . /// - public HandleExtraBytes? HandleExtraBytes { get; set; } + public Func? HandleExtraBytes { get; set; } /// /// Constructs a new instance of Potree2AccessBase diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 05bc96b17..fe9d33224 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -125,21 +125,22 @@ public MemoryOwner LoadVisualizationPointData(OctantId id) private MemoryOwner LoadVisualizationPoint(PotreeNode node) { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + if (HandleExtraBytes != null) + Guard.IsGreaterThan(OffsetToExtraBytes, 0); Guard.IsNotNull(PotreeData); - Guard.IsNotNull(PotreeData.ReadViewAccessor); - var potreeNodeSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; - var pointArray = new byte[potreeNodeSize]; + var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); - PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreeNodeSize); + PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); + + var pointCount = 0; - for (var i = 0; i < node.NumPoints; i++) + for (var i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) { - var currentPointOffset = i * PotreeData.Metadata.PointSize; - - var posSlice = new Span(pointArray).Slice(currentPointOffset + offsetPosition, Marshal.SizeOf() * 3); + var posSlice = new Span(pointArray).Slice(i + offsetPosition, Marshal.SizeOf() * 3); var pos = MemoryMarshal.Cast(posSlice); double x = pos[0] * PotreeData.Metadata.Scale.x; @@ -151,7 +152,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var posSpan = MemoryMarshal.Cast(position.ToArray()); - var colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetColor, Marshal.SizeOf() * 3); + var colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); var rgb = MemoryMarshal.Cast(colorSlice); var color = float4.Zero; @@ -169,82 +170,16 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) uint flags = 0; if (HandleExtraBytes != null) { - var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; - var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); - - if (HandleExtraBytes != null) - { - flags = HandleExtraBytes(extraBytesSpan); - } + flags = HandleExtraBytes(extraBytesSpan.ToArray()); } var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); - var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(i, 1)); + var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); posSpan.CopyTo(currentMemoryPt[..]); colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); flagsSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length + colorSpan.Length, Marshal.SizeOf())); - } - - return returnMemory; - } - - private MemoryOwner LoadNodeRaw(PotreeNode node) - { - Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); - if (HandleExtraBytes != null) - Guard.IsGreaterThan(OffsetToExtraBytes, 0); - Guard.IsNotNull(PotreeData); - Guard.IsNotNull(PotreeData.ReadViewAccessor); - - var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; - var pointArray = new byte[potreePointSize]; - - var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); - - PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); - - for (var i = 0; i < node.NumPoints; i++) - { - var pointOffset = i * PotreeData.Metadata.PointSize; - - // var posSlice = new Span(pointArray).Slice(pointOffset + offsetPosition, Marshal.SizeOf() * 3); - // var pos = MemoryMarshal.Cast(posSlice); - - // double x = pos[0] * PotreeData.Metadata.Scale.x; - // double y = pos[1] * PotreeData.Metadata.Scale.y; - // double z = pos[2] * PotreeData.Metadata.Scale.z; - - // float3 position = new((float)x, (float)y, (float)z); - // position = (float4x4)Potree2Consts.YZflip * position; - - // var posSpan = MemoryMarshal.Cast(position.ToArray()); - - // var colorSlice = new Span(pointArray).Slice(pointOffset + offsetColor, Marshal.SizeOf() * 3); - // var rgb = MemoryMarshal.Cast(colorSlice); - - // var color = float4.Zero; - - // color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); - // color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); - // color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); - // color.a = 1; - - // var colorSpan = MemoryMarshal.Cast(color.ToArray()); - - // var extraByteSize = PotreeData.Metadata.PointSize - OffsetToExtraBytes; - // var extraBytesSpan = pointArray.AsSpan().Slice(pointOffset + OffsetToExtraBytes, extraByteSize); - - // uint flags = 0; - // if (HandleExtraBytes != null) - // { - // flags = HandleExtraBytes(extraBytesSpan.ToArray()); - // } - // var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); - // var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(i, 1)); - // posSpan.CopyTo(currentMemoryPt[..]); - // colorSpan.CopyTo(currentMemoryPt[posSpan.Length..]); - // flagsSpan.CopyTo(currentMemoryPt.Slice(posSpan.Length + colorSpan.Length, Marshal.SizeOf())); + pointCount++; } return returnMemory; From eaa2c7b26678dc82cc1dfe273b0b9c09ef3e7818 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 29 Mar 2023 10:18:34 +0200 Subject: [PATCH 150/294] Updated Potree example --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index eb422dd7e..5a1ab783f 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -102,8 +102,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(); - _potreeData = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeReader = new Potree2Reader(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeData = _potreeReader.PotreeData; _rc = rc; } From 0181b43f4cbdd583406e694e0f8fd10515fcb65b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 30 Mar 2023 14:43:01 +0200 Subject: [PATCH 151/294] Auto stash before merge of "feature/726-re-impl-las-writer" and "origin/app/pasta" --- src/PointCloud/Potree/Potree2LAS.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index fea6e4a66..bb02890a8 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -191,6 +191,14 @@ private void ParseAndFillHeader() Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); + if(_potreeData.Metadata.OffsetToExtraBytes != -1) + { + // we have extra bytes to append to each point + // check how many and which + // parse them and add them to the header + // set something for the write method + } + // Initialize unmanged memory to hold the struct. var ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); try From aa86f0bee212f2e0da54acacb3fad69da5499291 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 30 Mar 2023 14:54:00 +0200 Subject: [PATCH 152/294] Fix BROKEN branch --- .../Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index eb422dd7e..5a1ab783f 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -102,8 +102,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(); - _potreeData = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeReader = new Potree2Reader(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeData = _potreeReader.PotreeData; _rc = rc; } From 88cd1de2f939fbadc038f334a103f409f4d5d03f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Thu, 30 Mar 2023 15:31:32 +0200 Subject: [PATCH 153/294] Fix merge conflicts/broken files --- .../Core/PointCloudPotree2Core.cs | 9 +++--- .../Core/PointRenderParams.cs | 2 +- .../Potree/V2/Data/PotreeMetadata.cs | 1 + src/PointCloud/Potree/V2/Potree2Reader.cs | 29 +++++++++++++++++-- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 3a10c0912..fbb592c8a 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -10,10 +10,9 @@ using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; namespace Fusee.Examples.PointCloudPotree2.Core { @@ -103,11 +102,11 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(); - _potreeData = _potreeReader.ReadNewFile(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); + _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2__AG_08\\")); + _potreeData = _potreeReader.PotreeData; var sw = new Stopwatch(); sw.Start(); - using var laswriter = new Potree2LAS(new FileInfo("test.las"), _potreedata); + using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData); laswriter.Write(); Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; diff --git a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs index 9664e0180..c69eaa1fc 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointRenderParams.cs @@ -15,7 +15,7 @@ public sealed class PointRenderingParams public PointThresholdHandler PointThresholdHandler; public ProjectedSizeModifierHandler ProjectedSizeModifierHandler; - public string PathToOocFile = Path.Combine("Assets", "test"/*"Cube1030301", "Potree"*/); + public string PathToOocFile = Path.Combine("Assets", "Cube1030301", "Potree"); public ShaderEffect DepthPassEf; public SurfaceEffectPointCloud ColorPassEf; diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index 8eee4e271..aa9569b0d 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -53,6 +53,7 @@ public class PotreeSettingsAttribute public bool IsExtraByte { get; set; } = false; } + public class PotreeMetadata : IPointWriterMetadata { public string Version { get; set; } diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 1dc61abcd..3272c15b4 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.MemoryMappedFiles; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -341,15 +342,39 @@ public void ReadFile(PotreeData potreeData) return (Metadata, Hierarchy); } + private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) { - var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath)); - + var settings = new JsonSerializerSettings(); + settings.Converters.Add(new ConvertIPointWriterHierarchy()); + var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath), settings); Guard.IsNotNull(potreeData, nameof(potreeData)); return potreeData; } + + internal class ConvertIPointWriterHierarchy : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IPointWriterHierarchy); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return serializer.Deserialize(reader, typeof(PotreeSettingsHierarchy)); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + } + + + + private static void LoadHierarchyRecursive(ref PotreeNode root, ref byte[] data, long offset, long size) { int bytesPerNode = 22; From 6637d74081e33f936bcb7e255191c966b6038402 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Thu, 30 Mar 2023 21:20:57 +0200 Subject: [PATCH 154/294] Somhow Extrabytes got lost --- .../Potree/V2/Data/PotreeMetadata.cs | 4 +++ src/PointCloud/Potree/V2/Potree2AccessBase.cs | 9 ++++++- src/PointCloud/Potree/V2/Potree2Reader.cs | 26 ++++++++++++++----- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index 99f762a57..c97fa8f1b 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -47,6 +47,9 @@ public class PotreeSettingsAttribute [JsonIgnore] public int AttributeOffset { get; set; } + + [JsonIgnore] + public bool IsExtraByte { get; set; } = false; } public class PotreeMetadata @@ -55,6 +58,7 @@ public class PotreeMetadata public string Name { get; set; } public string Description { get; set; } public int Points { get; set; } + public int OffsetToExtraBytes { get; set; } = -1; public string Projection { get; set; } public PotreeSettingsHierarchy Hierarchy { get; set; } diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 6f3aa54e1..202c19fc3 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -4,6 +4,13 @@ namespace Fusee.PointCloud.Potree.V2 { + /// + /// Delegate for a method that knows how to parse a slice of a point's extra bytes to a valid uint. + /// + /// + /// + public delegate uint HandleExtraBytes(Span bytes); + /// /// This is the base class for accessing Potree files /// @@ -17,7 +24,7 @@ public abstract class Potree2AccessBase /// /// Pass method how to handle the extra bytes, resulting uint will be passed into . /// - public Func? HandleExtraBytes { get; set; } + public HandleExtraBytes? HandleExtraBytes { get; set; } /// /// Constructs a new instance of Potree2AccessBase diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index fe9d33224..10491f41d 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -125,8 +125,8 @@ public MemoryOwner LoadVisualizationPointData(OctantId id) private MemoryOwner LoadVisualizationPoint(PotreeNode node) { Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); - if (HandleExtraBytes != null) - Guard.IsGreaterThan(OffsetToExtraBytes, 0); + //if (HandleExtraBytes != null) + // Guard.IsGreaterThan(OffsetToExtraBytes, 0); Guard.IsNotNull(PotreeData); var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; @@ -164,13 +164,16 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var colorSpan = MemoryMarshal.Cast(color.ToArray()); - var extraByteSize = PotreeData.Metadata.PointSize - OffsetToExtraBytes; - var extraBytesSpan = pointArray.AsSpan().Slice(i + OffsetToExtraBytes, extraByteSize); - uint flags = 0; - if (HandleExtraBytes != null) + if (PotreeData.Metadata.OffsetToExtraBytes != -1 && PotreeData.Metadata.OffsetToExtraBytes != 0) { - flags = HandleExtraBytes(extraBytesSpan.ToArray()); + var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; + var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); + + if (HandleExtraBytes != null) + { + flags = HandleExtraBytes(extraBytesSpan); + } } var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); @@ -220,6 +223,15 @@ public PotreeData ReadNewFile(string path) PotreeData = new PotreeData(Hierarchy, Metadata); + foreach (var item in PotreeData.Metadata.Attributes.Values) + { + PotreeData.Metadata.PointSize += item.Size; + if (PotreeData.Metadata.OffsetToExtraBytes > -1 && PotreeData.Metadata.PointSize > PotreeData.Metadata.OffsetToExtraBytes) + item.IsExtraByte = true; + else + item.IsExtraByte = false; + } + CacheMetadata(true); return PotreeData; From ff8c81edb050867201a2a19409a15d110f1d3248 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 31 Mar 2023 00:13:24 +0200 Subject: [PATCH 155/294] Prepare Potree2Writer --- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 17 + src/PointCloud/Potree/V2/Potree2Reader.cs | 5 +- src/PointCloud/Potree/V2/Potree2Writer.cs | 351 ++---------------- 3 files changed, 45 insertions(+), 328 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 202c19fc3..990ba8a18 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -40,6 +40,23 @@ public Potree2AccessBase(PotreeData potreeData) /// protected Potree2AccessBase() { } + /// + /// Returns the raw data of a + /// + /// + /// + internal byte[] ReadRawNodeData(PotreeNode node) + { + Guard.IsLessThanOrEqualTo(node.NumPoints, int.MaxValue); + Guard.IsNotNull(PotreeData); + + var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; + PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); + + return pointArray; + } + #region Metadata caching /// diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 8ad1f494d..f4663297a 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -120,13 +120,10 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) // Guard.IsGreaterThan(OffsetToExtraBytes, 0); Guard.IsNotNull(PotreeData); - var potreePointSize = (int)node.NumPoints * PotreeData.Metadata.PointSize; - var pointArray = new byte[potreePointSize]; + var pointArray = ReadRawNodeData(node); var returnMemory = MemoryOwner.Allocate((int)node.NumPoints); - PotreeData.ReadViewAccessor.ReadArray(node.ByteOffset, pointArray, 0, potreePointSize); - var pointCount = 0; for (var i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 2c2653c82..7ffd4de6b 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -1,7 +1,7 @@ -using Fusee.Math.Core; +using CommunityToolkit.Diagnostics; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; -using System; -using System.IO; namespace Fusee.PointCloud.Potree.V2 { @@ -16,341 +16,44 @@ public class Potree2Writer : Potree2AccessBase public Potree2Writer(PotreeData potreeData) : base(potreeData) { } /// - /// Directly writes the action of one given set of selectors to disk. + /// Writes for a node to disk. /// - /// - /// - /// - /// - /// - public (long octants, long points) Write(Predicate nodeSelector, Predicate pointSelector, Action action, bool dryrun = false) + /// + /// + public void WriteVisualizationPoint(OctantId octantId, MemoryOwner visualizationPoints) { - long octantCount = 0; - long pointsCount = 0; + Guard.IsNotNull(PotreeData); + var node = PotreeData.GetNode(octantId); - using (Stream readStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - { - BinaryReader binaryReader = new BinaryReader(readStream); - BinaryWriter binaryWriter = new BinaryWriter(writeStream); + // if node is null the hierarchy is broken and we look for an octant that isn't there... + Guard.IsNotNull(node); - //foreach (var node in _potreeData.Hierarchy.Nodes) - //{ - // if (nodeSelector(node)) - // { - // octantCount++; - - // var point = new PotreePoint(); - - // for (int i = 0; i < node.NumPoints; i++) - // { - // if (offsetPosition > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - // double x = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.x; - // double y = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.y; - // double z = binaryReader.ReadInt32() * _potreeData.Metadata.Scale.z; - - // double3 position = new(x, y, z); - // position = Potree2Consts.YZflip * position; - - // point.Position = position; - // } - - // if (offsetIntensity > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // point.Intensity = binaryReader.ReadInt16(); - // } - - // if (offsetReturnNumber > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // point.ReturnNumber = binaryReader.ReadByte(); - // } - - // if (offsetNumberOfReturns > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // point.NumberOfReturns = binaryReader.ReadByte(); - // } - - // if (offsetClassification > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - // point.Classification = binaryReader.ReadByte(); - // } - - // if (offsetScanAngleRank > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // point.ScanAngleRank = binaryReader.ReadByte(); - // } - - // if (offsetUserData > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // point.UserData = binaryReader.ReadByte(); - // } - - // if (offsetPointSourceId > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // point.PointSourceId = binaryReader.ReadByte(); - // } - - // if (offsetColor > -1) - // { - // binaryReader.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - // ushort r = binaryReader.ReadUInt16(); - // ushort g = binaryReader.ReadUInt16(); - // ushort b = binaryReader.ReadUInt16(); - - // float3 color = float3.Zero; - - // color.r = ((byte)(r > 255 ? r / 256 : r)); - // color.g = ((byte)(g > 255 ? g / 256 : g)); - // color.b = ((byte)(b > 255 ? b / 256 : b)); - - // point.Color = color; - // } - - // if (pointSelector(point)) - // { - // action(point); - - // if (!dryrun) - // { - // if (offsetPosition > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - // var position = Potree2Consts.YZflip * point.Position; - - // int x = Convert.ToInt32(position.x / _potreeData.Metadata.Scale.x); - // int y = Convert.ToInt32(position.y / _potreeData.Metadata.Scale.y); - // int z = Convert.ToInt32(position.z / _potreeData.Metadata.Scale.z); - - // binaryWriter.Write(x); - // binaryWriter.Write(y); - // binaryWriter.Write(z); - // } - - // if (offsetIntensity > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Intensity); - // } - - // if (offsetReturnNumber > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ReturnNumber); - // } - - // if (offsetNumberOfReturns > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.NumberOfReturns); - // } - - // if (offsetClassification > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Classification); - // } - - // if (offsetScanAngleRank > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ScanAngleRank); - // } - - // if (offsetUserData > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.UserData); - // } - - // if (offsetPointSourceId > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.PointSourceId); - // } - - // if (offsetColor > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - // ushort r = Convert.ToUInt16(point.Color.r * 256); - // ushort g = Convert.ToUInt16(point.Color.g * 256); - // ushort b = Convert.ToUInt16(point.Color.b * 256); - - // binaryWriter.Write(r); - // binaryWriter.Write(g); - // binaryWriter.Write(b); - // } - // } - - // pointsCount++; - // } - // } - // } - //} - - binaryWriter.Close(); - binaryReader.Dispose(); - - binaryReader.Close(); - binaryReader.Dispose(); - } - - return (octantCount, pointsCount); + WriteVisualizationPoint(node, visualizationPoints); } - public (long octants, long points) Label(Predicate nodeSelector, Predicate pointSelector, byte Label, bool dryrun = false) + private void WriteVisualizationPoint(PotreeNode potreeNode, MemoryOwner visualizationPoints) { - long octantCount = 0; - long pointsCount = 0; - - using (Stream readStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - { - BinaryReader binaryReader = new BinaryReader(readStream); - BinaryWriter binaryWriter = new BinaryWriter(writeStream); - - double3 point = double3.Zero; + Guard.IsLessThanOrEqualTo(potreeNode.NumPoints, int.MaxValue); + Guard.IsNotNull(PotreeData); - foreach (var node in PotreeData.Hierarchy.Nodes) - { - if (nodeSelector(node)) - { - octantCount++; + var pointArray = ReadRawNodeData(potreeNode); - for (int i = 0; i < node.NumPoints; i++) - { - binaryReader.BaseStream.Position = node.ByteOffset + 0 + i * PotreeData.Metadata.PointSize; + var visualizationArray = visualizationPoints.Span.ToArray(); - point.x = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.x); - point.z = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.y); - point.y = (binaryReader.ReadInt32() * PotreeData.Metadata.Scale.z); + //MemoryMarshal.Cast() - if (pointSelector(point)) - { - if (!dryrun) - { - binaryWriter.BaseStream.Position = node.ByteOffset + 16 + i * PotreeData.Metadata.PointSize; - binaryWriter.Write(Label); - } - pointsCount++; - } - } - } - } - - binaryWriter.Close(); - binaryReader.Dispose(); - - binaryReader.Close(); - binaryReader.Dispose(); - } - - return (octantCount, pointsCount); + WriteRawNodeData(potreeNode, pointArray); } - //public void WriteRawPoints(OctantId oid, byte[] points) - //{ - // var node = FindNode(ref _potreeData.Hierarchy, oid); - - // if (points.Length != node.NumPoints) - // { - // //TODO: (throw) correct error - // throw new Exception(); - // } - - // using (Stream writeStream = File.Open(Path.Combine(PotreeData.Metadata.FolderPath, Potree2Consts.OctreeFileName), FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) - // { - // BinaryWriter binaryWriter = new BinaryWriter(writeStream); - - // for (int i = 0; i < points.Length; i++) - // { - // var point = points[i]; - - // if (offsetPosition > -1) - // { - // // TODO: Fix position conversion - // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetPosition + i * _potreeData.Metadata.PointSize; - - // //var position = Potree2Consts.YZflip * point.Position; - - // //binaryWriter.Write(position.x); - // //binaryWriter.Write(position.y); - // //binaryWriter.Write(position.z); - // } - - // if (offsetIntensity > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetIntensity + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Intensity); - // } - - // if (offsetReturnNumber > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetReturnNumber + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ReturnNumber); - // } - - // if (offsetNumberOfReturns > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetNumberOfReturns + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.NumberOfReturns); - // } - - // if (offsetClassification > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetClassification + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.Classification); - // } - - // if (offsetScanAngleRank > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetScanAngleRank + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.ScanAngleRank); - // } - - // if (offsetUserData > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetUserData + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.UserData); - // } - - // if (offsetPointSourceId > -1) - // { - // binaryWriter.BaseStream.Position = node.ByteOffset + offsetPointSourceId + i * _potreeData.Metadata.PointSize; - // binaryWriter.Write(point.PointSourceId); - // } - - // if (offsetColor > -1) - // { - // // TODO: Fix color conversion - // //binaryWriter.BaseStream.Position = node.ByteOffset + offsetColor + i * _potreeData.Metadata.PointSize; - - // //ushort r = (ushort)MathF.Floor(point.Color.r >= 1f ? 255 : point.Color.r * 256f); - // //ushort g = (ushort)MathF.Floor(point.Color.g >= 1f ? 255 : point.Color.g * 256f); - // //ushort b = (ushort)MathF.Floor(point.Color.b >= 1f ? 255 : point.Color.b * 256f); - - // //binaryWriter.Write(r); - // //binaryWriter.Write(g); - // //binaryWriter.Write(b); - // } - // } + private void WriteRawNodeData(PotreeNode potreeNode, byte[] rawNodeData) + { + Guard.IsNotNull(PotreeData); + Guard.IsNotNull(rawNodeData); - // binaryWriter.Close(); - // binaryWriter.Dispose(); - // } - //} + var potreePointSize = (int)potreeNode.NumPoints * PotreeData.Metadata.PointSize; + var pointArray = new byte[potreePointSize]; + PotreeData.WriteViewAccessor.WriteArray(potreeNode.ByteOffset, pointArray, 0, potreePointSize); + } } } \ No newline at end of file From 938fa19e2db9373b2ed4641e8eaa6a93916f2ca8 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 31 Mar 2023 12:51:14 +0200 Subject: [PATCH 156/294] Potree2Reader: if there is no rgb, try to use intensity --- src/PointCloud/Potree/V2/Potree2Reader.cs | 39 +++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index d0ce7867e..a46dd5ece 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -149,15 +149,36 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var posSpan = MemoryMarshal.Cast(position.ToArray()); - var colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetColor, Marshal.SizeOf() * 3); - var rgb = MemoryMarshal.Cast(colorSlice); - - var color = float4.Zero; - - color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); - color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); - color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); - color.a = 1; + Span colorSlice; + Span rgb; + float4 color; + if (offsetColor != -1) + { + colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); + rgb = MemoryMarshal.Cast(colorSlice); + color = float4.Zero; + + color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); + color.g = ((byte)(rgb[1] > 255 ? rgb[1] / 256 : rgb[1])); + color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); + color.a = 1; + } + else if(offsetIntensity != -1) + { + var attrib = PotreeData.Metadata.Attributes["intensity"]; + colorSlice = new Span(pointArray).Slice(i + offsetIntensity, Marshal.SizeOf()); + rgb = MemoryMarshal.Cast(colorSlice); + color = float4.Zero; + + color.r = (float)((rgb[0] - attrib.MinList[0]) / (attrib.MaxList[0] - attrib.MinList[0]) * 1f); + color.g = color.r; + color.b = color.r; + color.a = 1; + } + else + { + color = float4.UnitW; + } var colorSpan = MemoryMarshal.Cast(color.ToArray()); From d4d3593e8cb1d20f09f04c73f86725b7296524aa Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 31 Mar 2023 13:27:41 +0200 Subject: [PATCH 157/294] Fixed merge error --- src/PointCloud/Potree/V2/Potree2Reader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index a46dd5ece..4881866b4 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -154,8 +154,9 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) float4 color; if (offsetColor != -1) { - colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); + colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetColor, Marshal.SizeOf() * 3); rgb = MemoryMarshal.Cast(colorSlice); + color = float4.Zero; color.r = ((byte)(rgb[0] > 255 ? rgb[0] / 256 : rgb[0])); @@ -166,7 +167,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) else if(offsetIntensity != -1) { var attrib = PotreeData.Metadata.Attributes["intensity"]; - colorSlice = new Span(pointArray).Slice(i + offsetIntensity, Marshal.SizeOf()); + colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetIntensity, Marshal.SizeOf()); rgb = MemoryMarshal.Cast(colorSlice); color = float4.Zero; From df09cc5f7561e69679ba7a0378f97382f5956658 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 31 Mar 2023 16:37:12 +0200 Subject: [PATCH 158/294] ToString for VisualizationPoint for debugging purposes. --- src/PointCloud/Core/VisualizationPoint.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index 700fd3c5f..ae92605d3 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -1,4 +1,5 @@ using Fusee.Math.Core; +using System; namespace Fusee.PointCloud.Potree.V2.Data { @@ -8,6 +9,11 @@ namespace Fusee.PointCloud.Potree.V2.Data /// public struct VisualizationPoint { + /// + /// How Flags should be converted by ToString. + /// + public static Func FlagsParser = (flags) => flags.ToString(); + /// /// The position of a point. /// @@ -21,6 +27,13 @@ public struct VisualizationPoint /// /// Flags have to be interpreted manually or they will be ignored. /// + public uint Flags; + + /// + public override string ToString() + { + return $"Position: {Position} - Color: {Color} - Flags: {FlagsParser(Flags)}"; + } } } \ No newline at end of file From edff710203a914c0ce40366be22bdb3e48a1c463 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 31 Mar 2023 16:37:59 +0200 Subject: [PATCH 159/294] Moved HandleExtraBytes is now HandleReadExtraBytes --- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 14 +------------- src/PointCloud/Potree/V2/Potree2Reader.cs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 990ba8a18..6058625f6 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -3,14 +3,7 @@ using System; namespace Fusee.PointCloud.Potree.V2 -{ - /// - /// Delegate for a method that knows how to parse a slice of a point's extra bytes to a valid uint. - /// - /// - /// - public delegate uint HandleExtraBytes(Span bytes); - +{ /// /// This is the base class for accessing Potree files /// @@ -21,11 +14,6 @@ public abstract class Potree2AccessBase /// public PotreeData? PotreeData { get; set; } - /// - /// Pass method how to handle the extra bytes, resulting uint will be passed into . - /// - public HandleExtraBytes? HandleExtraBytes { get; set; } - /// /// Constructs a new instance of Potree2AccessBase /// diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index f4663297a..b84719e34 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -17,11 +17,23 @@ namespace Fusee.PointCloud.Potree.V2 { + /// + /// Delegate for a method that knows how to parse a slice of a point's extra bytes to a valid uint. + /// + /// + /// + public delegate uint HandleReadExtraBytes(Span bytes); + /// /// Reads Potree V2 files and is able to create a point cloud scene component, that can be rendered. /// public class Potree2Reader : Potree2AccessBase, IPointReader { + /// + /// Pass method how to handle the extra bytes, resulting uint will be passed into . + /// + public HandleReadExtraBytes? HandleReadExtraBytes { get; set; } + /// /// Specify the byte offset for one point until the extra byte data is reached /// @@ -42,6 +54,7 @@ public Potree2Reader(string filepath) /// public Potree2Reader(PotreeData potreeData) : base(potreeData) { + ReadFile(potreeData); } /// @@ -158,9 +171,9 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); - if (HandleExtraBytes != null) + if (HandleReadExtraBytes != null) { - flags = HandleExtraBytes(extraBytesSpan); + flags = HandleReadExtraBytes(extraBytesSpan); } } var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); From 47a5bbefe9f298646a52aa7c1d74bae4150809f9 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 31 Mar 2023 16:38:44 +0200 Subject: [PATCH 160/294] Potree2Writer now works with MemoryMappedFile --- src/PointCloud/Potree/V2/Potree2Writer.cs | 35 ++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 7ffd4de6b..1952f9a40 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -2,14 +2,27 @@ using CommunityToolkit.HighPerformance.Buffers; using Fusee.PointCloud.Common; using Fusee.PointCloud.Potree.V2.Data; +using System; namespace Fusee.PointCloud.Potree.V2 { + /// + /// Delegate for a method that knows how to parse a enxtra byte uint back to its byte representation. + /// + /// + /// + public delegate Span HandleWriteExtraBytes(uint flag); + /// /// Writes Potree data /// public class Potree2Writer : Potree2AccessBase { + /// + /// Pass method how to handle the extra bytes, resulting uint will be passed into . + /// + public HandleWriteExtraBytes? HandleWriteExtraBytes { get; set; } + /// /// Generate a instance. /// @@ -20,7 +33,8 @@ public Potree2Writer(PotreeData potreeData) : base(potreeData) { } /// /// /// - public void WriteVisualizationPoint(OctantId octantId, MemoryOwner visualizationPoints) + /// + public void WriteVisualizationPoint(OctantId octantId, MemoryOwner visualizationPoints, PotreeSettingsAttribute potreeSettingsAttribute) { Guard.IsNotNull(PotreeData); var node = PotreeData.GetNode(octantId); @@ -28,20 +42,28 @@ public void WriteVisualizationPoint(OctantId octantId, MemoryOwner visualizationPoints) + private void WriteVisualizationPoint(PotreeNode potreeNode, MemoryOwner visualizationPoints, PotreeSettingsAttribute potreeSettingsAttribute) { Guard.IsLessThanOrEqualTo(potreeNode.NumPoints, int.MaxValue); Guard.IsNotNull(PotreeData); + Guard.IsNotNull(HandleWriteExtraBytes); var pointArray = ReadRawNodeData(potreeNode); - var visualizationArray = visualizationPoints.Span.ToArray(); + var visualizationArray = visualizationPoints.Span; + var visualizationIdx = 0; + + for (int i = 0; i < pointArray.Length; i += PotreeData.Metadata.PointSize) + { + var attributeSlice = new Span(pointArray).Slice(i + potreeSettingsAttribute.AttributeOffset, potreeSettingsAttribute.Size); - //MemoryMarshal.Cast() + HandleWriteExtraBytes(visualizationArray[visualizationIdx].Flags).CopyTo(attributeSlice); + visualizationIdx++; + } WriteRawNodeData(potreeNode, pointArray); } @@ -52,8 +74,7 @@ private void WriteRawNodeData(PotreeNode potreeNode, byte[] rawNodeData) Guard.IsNotNull(rawNodeData); var potreePointSize = (int)potreeNode.NumPoints * PotreeData.Metadata.PointSize; - var pointArray = new byte[potreePointSize]; - PotreeData.WriteViewAccessor.WriteArray(potreeNode.ByteOffset, pointArray, 0, potreePointSize); + PotreeData.WriteViewAccessor.WriteArray(potreeNode.ByteOffset, rawNodeData, 0, potreePointSize); } } } \ No newline at end of file From d9347d792a101d40bd281fab404f06b0c39b316d Mon Sep 17 00:00:00 2001 From: ASPePeX Date: Fri, 31 Mar 2023 14:52:29 +0000 Subject: [PATCH 161/294] Linting --- src/PointCloud/Core/VisualizationPoint.cs | 2 +- src/PointCloud/Potree/V2/Potree2AccessBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index ae92605d3..ccbc34ee9 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -27,7 +27,7 @@ public struct VisualizationPoint /// /// Flags have to be interpreted manually or they will be ignored. /// - + public uint Flags; /// diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 6058625f6..63865a9ec 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -3,7 +3,7 @@ using System; namespace Fusee.PointCloud.Potree.V2 -{ +{ /// /// This is the base class for accessing Potree files /// From 5d36a9dd4b5e3209f45d1931de9eb066d7a01230 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Fri, 31 Mar 2023 16:57:32 +0200 Subject: [PATCH 162/294] Working LAS writer, TOOD: extrabytes --- .../Core/PointCloudPotree2Core.cs | 2 +- src/PointCloud/Potree/Potree2LAS.cs | 144 ++++++++++-------- src/PointCloud/Potree/V2/Potree2Reader.cs | 5 +- 3 files changed, 81 insertions(+), 70 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index fbb592c8a..4db7e494e 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -106,7 +106,7 @@ public PointCloudPotree2Core(RenderContext rc) _potreeData = _potreeReader.PotreeData; var sw = new Stopwatch(); sw.Start(); - using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData); + using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Seven); laswriter.Write(); Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index bb02890a8..1e115230b 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -8,6 +8,9 @@ using System.Text; using Fusee.PointCloud.Potree.V2.Data; using System.IO.MemoryMappedFiles; +using System.Diagnostics; +using SixLabors.ImageSharp.PixelFormats; +using System.Collections.Generic; namespace Fusee.PointCloud.Potree { @@ -40,10 +43,10 @@ public LASHeader() { } internal ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; internal ushort FileCreationYear = (ushort)DateTime.Now.Year; internal ushort HeaderSize = 375; - internal uint OffsetToPointData = 375; + internal uint OffsetToPointData = 0; internal uint NumberOfVariableLengthRecords = 0; internal byte PointDataRecordFormat = 2; - internal ushort PointDataRecordLength = 26; + internal ushort PointDataRecordLength = 0; internal uint LegacyNbrOfPoints = 0; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] @@ -76,35 +79,28 @@ public LASHeader() { } internal ulong[] NbrOfPointsByReturn = new ulong[15]; } + public enum LASPointType : byte + { + Zero = 0x0, + Two = 0x2, + Seven = 0x7 + } [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct LASPoint + public struct VariableLengthRecordHeader { - public LASPoint() { } - - internal int X = 0; - internal int Y = 0; - internal int Z = 0; + public VariableLengthRecordHeader() { } - internal ushort Intensity = 0; - internal byte ReturnNbrOfScanDirAndEdgeByte = 0; + public ushort Reserved = 0; - internal byte Classification = 0; - internal byte ScanAngleRank = 0; - internal byte UserData = 0; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] UserId = new byte[16]; - internal ushort PtSrcID = 0; + public ushort RecordId = 0; + public ushort RecordLengthAfterHeader = 0; - internal ushort R = 0; - internal ushort G = 0; - internal ushort B = 0; - } - - public enum LASPointType : byte - { - Zero = 0x0, - Two = 0x2, - Seven = 0x7 + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] Description = new byte[32]; } /// @@ -121,6 +117,8 @@ public class Potree2LAS : IPointWriter, IDisposable private readonly PotreeData _potreeData; private readonly LASPointType _type; + private readonly List _vlrh = new(); + /// /// Generate a writer instance, pass save path, Potree data and optional the point type to write /// @@ -152,7 +150,7 @@ private void ParseAndFillHeader() { var doy = DateTime.Now.DayOfYear; var year = DateTime.Now.Year; - var size = Marshal.SizeOf(); + var size = Metadata.PointSize - 1; // make sure we can cast to and do not lose information Guard.IsLessThan(doy, ushort.MaxValue); @@ -197,6 +195,26 @@ private void ParseAndFillHeader() // check how many and which // parse them and add them to the header // set something for the write method + foreach(var attribute in _potreeData.Metadata.AttributesList) + { + if(attribute.IsExtraByte) + { + _header.NumberOfVariableLengthRecords++; + _header.OffsetToPointData += 54; // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. + + var currentEntry = new VariableLengthRecordHeader + { + RecordLengthAfterHeader = (ushort)_header.OffsetToPointData, + RecordId = 4 + }; + + var desc = Encoding.UTF8.GetBytes(attribute.Description); + Guard.IsLessThan(desc.Length, currentEntry.Description.Length); + Array.Copy(desc, currentEntry.Description, desc.Length); + + _vlrh.Add(currentEntry); + } + } } // Initialize unmanged memory to hold the struct. @@ -215,10 +233,28 @@ private void ParseAndFillHeader() // Free the unmanaged memory. Marshal.FreeHGlobal(ptr); } - } - delegate void ConvertPointMethod(Span data, Stream s); - delegate void WriteStreamChunk(MemoryMappedFile file, long start, long end); + // append all variable length record header + + foreach (var vlr in _vlrh) + { + var mem = Marshal.AllocHGlobal(Marshal.SizeOf()); + try + { + // Copy the struct to unmanaged memory. + Marshal.StructureToPtr(vlr, mem, false); + var dest = new byte[Marshal.SizeOf()]; + Marshal.Copy(mem, dest, 0, Marshal.SizeOf()); + _fileStream.Write(dest); + + } + finally + { + // Free the unmanaged memory. + Marshal.FreeHGlobal(mem); + } + } + } /// /// This methods starts the LASfile write progress. @@ -232,50 +268,24 @@ public void Write(Action? progressCallback = null) Guard.IsNotNull(_header); using var stream = _potreeData.OctreeMappedFile.CreateViewStream(); - var fileLength = Metadata.PointCount * Metadata.PointSize; + ulong fileLength = (ulong)Metadata.PointCount * (ulong)Metadata.PointSize; - Span tmpArry = (int)_type switch - { - //0 => stackalloc byte[666], // TODO - 2 => stackalloc byte[26 + 1], // + 1 due to wrong potree bytes - 7 => stackalloc byte[36 + 1], - _ => throw new NotImplementedException(), - }; ; - - ConvertPointMethod convertPtMethod = (int)_type switch - { - 0 => static (Span pt, Stream s) => - { - throw new NotImplementedException(); - } - , - 2 => static (Span pt, Stream s) => - { - s.Write(pt[..15]); // position - s.Write(pt.Slice(12, 3)); // intensity, and mixed returns according to las 1.4 - // skip number of returns! [15,16] - s.Write(pt.Slice(16, 11)); // rest - } - , - 7 => static (Span pt, Stream s) => - { - s.Write(pt[..12]); // position - s.Write(pt.Slice(12, 3)); // intensity, and mixed returns according to las 1.4 - // skip number of returns! [15,16] - s.Write(pt.Slice(16, 21)); // rest - } - , - _ => throw new NotImplementedException(), - }; + Span tmpArray = stackalloc byte[_potreeData.Metadata.PointSize]; // DO NOT USE stream.Length as the MemoryMappedStream aligns with the page size - for (var i = 0; i < fileLength; i += Metadata.PointSize) + for (ulong i = 0U; i < fileLength; i += (ulong)Metadata.PointSize) { - float progress = (100f / Metadata.PointCount) * (i / Metadata.PointSize); + var strSize = _fileStream.Length; + float progress = (100f / Metadata.PointCount) * (i / (ulong)Metadata.PointSize); progressCallback?.Invoke((int)progress); - // we need to copy each point and shrink it back to 26 (from 27) due to PotreeConvert errors - stream.Read(tmpArry); - convertPtMethod(tmpArry, _fileStream); + + // we need to shrink each point back (skip byte 16) + // extra bytes and everything is already included + // TODO: check if extra bytes are already aligned correctely with LAS spec + stream.Read(tmpArray); + _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) + _fileStream.Write(tmpArray[16..Metadata.PointSize]); // skip array pos [15], byte 16 + Debug.Assert(strSize + 36 == _fileStream.Length); } } diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 3272c15b4..0f6a38b9e 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -336,8 +336,9 @@ public void ReadFile(PotreeData potreeData) FlipYZAxis(Metadata, Hierarchy); - Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; - Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; + // do not adapt the global AABB after conversion, keep for writing + //Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; + //Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; return (Metadata, Hierarchy); } From f0bb0c05baffcbd4c0e1f24986af62ba28dad4e4 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Fri, 31 Mar 2023 17:16:10 +0200 Subject: [PATCH 163/294] Fixed merge --- src/PointCloud/Potree/V2/Potree2Reader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index f4fb2d29f..be1680cf0 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -158,7 +158,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) float4 color; if (offsetColor != -1) { - colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetColor, Marshal.SizeOf() * 3); + colorSlice = new Span(pointArray).Slice(i + offsetColor, Marshal.SizeOf() * 3); rgb = MemoryMarshal.Cast(colorSlice); color = float4.Zero; @@ -171,7 +171,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) else if(offsetIntensity != -1) { var attrib = PotreeData.Metadata.Attributes["intensity"]; - colorSlice = new Span(pointArray).Slice(currentPointOffset + offsetIntensity, Marshal.SizeOf()); + colorSlice = new Span(pointArray).Slice(i + offsetIntensity, Marshal.SizeOf()); rgb = MemoryMarshal.Cast(colorSlice); color = float4.Zero; From 424488ab23cbeb20dbd65a0e27c9a3cc63df0d61 Mon Sep 17 00:00:00 2001 From: ASPePeX Date: Fri, 31 Mar 2023 15:24:59 +0000 Subject: [PATCH 164/294] Linting --- src/PointCloud/Potree/V2/Potree2Reader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index be1680cf0..64a2f5e82 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -168,7 +168,7 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) color.b = ((byte)(rgb[2] > 255 ? rgb[2] / 256 : rgb[2])); color.a = 1; } - else if(offsetIntensity != -1) + else if (offsetIntensity != -1) { var attrib = PotreeData.Metadata.Attributes["intensity"]; colorSlice = new Span(pointArray).Slice(i + offsetIntensity, Marshal.SizeOf()); From 78aa31d122969e55a855e07dd35cd3db6ccdc008 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Fri, 31 Mar 2023 18:06:26 +0200 Subject: [PATCH 165/294] Preparation for ExtraBytes writing --- .../Core/PointCloudPotree2Core.cs | 4 +- src/PointCloud/Potree/Potree2LAS.cs | 198 ++++++++++++++++-- 2 files changed, 188 insertions(+), 14 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 4db7e494e..a796ba369 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -102,11 +102,11 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2__AG_08\\")); + _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2\\plp_tmp_IPM_A-E-97")); _potreeData = _potreeReader.PotreeData; var sw = new Stopwatch(); sw.Start(); - using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Seven); + using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Two); laswriter.Write(); Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 1e115230b..725019375 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -11,6 +11,7 @@ using System.Diagnostics; using SixLabors.ImageSharp.PixelFormats; using System.Collections.Generic; +using System.Linq; namespace Fusee.PointCloud.Potree { @@ -43,7 +44,7 @@ public LASHeader() { } internal ushort FileCreationDayOfYear = (ushort)DateTime.Now.Day; internal ushort FileCreationYear = (ushort)DateTime.Now.Year; internal ushort HeaderSize = 375; - internal uint OffsetToPointData = 0; + internal uint OffsetToPointData = 375; internal uint NumberOfVariableLengthRecords = 0; internal byte PointDataRecordFormat = 2; internal ushort PointDataRecordLength = 0; @@ -103,6 +104,69 @@ public VariableLengthRecordHeader() { } public byte[] Description = new byte[32]; } + internal struct InternalVariableLengthRecord + { + public int Type; + public double[] Max; + public double[] Min; + public string Name; + public string Description; + + } + + /// + /// Struct for the Specification Defined VLR "Extra Bytes". This has always 192 bytes. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LasExtraBytes + { + public LasExtraBytes() { } + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] reserved = new byte[2]; // 2 bytes + + public byte data_type = 0; // 1 byte + + public byte options = 0; // 1 byte + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] name = new byte[32]; // 32 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public byte[] unused = new byte[4]; // 4 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] no_data = new byte[8]; // 8 bytes anytype + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] deprecated1 = new byte[16]; // 16 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] min = new byte[8]; // 8 bytes anytype + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] deprecated2 = new byte[16]; // 16 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] max = new byte[8]; // 8 bytes anytype + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] deprecated3 = new byte[16]; // 16 bytes + + public double scale = 0; // 8 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] deprecated4 = new byte[16]; // 16 bytes + + public double offset = 0; // 8 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] deprecated5 = new byte[16]; // 16 bytes + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] description = new byte[32]; // 32 bytes + } + /// /// This class provides methods to convert and saves clouds to LAS 1.4 /// @@ -118,6 +182,7 @@ public class Potree2LAS : IPointWriter, IDisposable private readonly LASPointType _type; private readonly List _vlrh = new(); + private readonly List _extraByteDesc = new(); /// /// Generate a writer instance, pass save path, Potree data and optional the point type to write @@ -182,38 +247,122 @@ private void ParseAndFillHeader() PointDataRecordFormat = (byte)_type }; - //_header.LeacyNbrOfPointsByRtn[0] = (uint)Metadata.PointCount; - //_header.NbrOfPointsByReturn[0] = (ulong)Metadata.PointCount; + _header.LeacyNbrOfPointsByRtn[0] = (uint)Metadata.PointCount; + _header.NbrOfPointsByReturn[0] = (ulong)Metadata.PointCount; var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}"); Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); - if(_potreeData.Metadata.OffsetToExtraBytes != -1) + if (_potreeData.Metadata.OffsetToExtraBytes != -1) { + var offset = 0; // we have extra bytes to append to each point // check how many and which // parse them and add them to the header // set something for the write method - foreach(var attribute in _potreeData.Metadata.AttributesList) + foreach (var attribute in _potreeData.Metadata.AttributesList) { - if(attribute.IsExtraByte) + if (attribute == null) continue; + if (offset >= _potreeData.Metadata.OffsetToExtraBytes) { _header.NumberOfVariableLengthRecords++; - _header.OffsetToPointData += 54; // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. + _header.OffsetToPointData += 54; + // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. + + var desc = Encoding.ASCII.GetBytes(attribute.Description.Append('\0').ToArray()); + var name = Encoding.ASCII.GetBytes(attribute.Name.Append('\0').ToArray()); var currentEntry = new VariableLengthRecordHeader { - RecordLengthAfterHeader = (ushort)_header.OffsetToPointData, - RecordId = 4 + RecordLengthAfterHeader = (ushort)_header.OffsetToPointData, // current offset + RecordId = 4 }; - var desc = Encoding.UTF8.GetBytes(attribute.Description); Guard.IsLessThan(desc.Length, currentEntry.Description.Length); Array.Copy(desc, currentEntry.Description, desc.Length); + // this is just for the LAS header, we should check if we can already + // build an internal list to update later when writing the actual extra bytes _vlrh.Add(currentEntry); + + + var extraByteType = attribute.Type switch + { + //see Las Specification + "uint8" => 1, // uchar + "int8" => 2, // char + "uint16" => 3, // ushort + "int16" => 4, // short + "uint32" => 5, // ulong + "int32" => 6, // long + "int64" => 7, // longlong + "uint64" => 8, // ulonglong, + "float" => 9, // float + "double" => 10 // double +, + _ => throw new ArgumentException("Invalid data type!") + }; + + + var currentExtra = new LasExtraBytes + { + data_type = (byte)extraByteType + }; + + Guard.IsLessThan(desc.Length, currentExtra.description.Length); + Guard.IsLessThan(name.Length, currentExtra.name.Length); + + Array.Copy(desc, currentExtra.description, desc.Length); + Array.Copy(name, currentExtra.name, name.Length); + + var extraByteMarshalMin = attribute.Type switch + { + //see Las Specification + "uint8" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (sbyte)x).ToArray()), + "int8" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (byte)x).ToArray()), + "uint16" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (ushort)x).ToArray()), + "int16" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (short)x).ToArray()), + "uint32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (ulong)x).ToArray()), + "int32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (long)x).ToArray()), + "int64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (Int64)x).ToArray()), + "uint64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (UInt64)x).ToArray()), + "float" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (float)x).ToArray()), + "double" => MemoryMarshal.AsBytes(attribute.MinList.ToArray()), + _ => throw new ArgumentException("Invalid data type!") + }; + + var extraByteMarshalMax = attribute.Type switch + { + //see Las Specification + "uint8" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (sbyte)x).ToArray()), + "int8" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (byte)x).ToArray()), + "uint16" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (ushort)x).ToArray()), + "int16" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (short)x).ToArray()), + "uint32" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (ulong)x).ToArray()), + "int32" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (long)x).ToArray()), + "int64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (Int64)x).ToArray()), + "uint64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (UInt64)x).ToArray()), + "float" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (float)x).ToArray()), + "double" => MemoryMarshal.AsBytes(attribute.MaxList.ToArray()), + _ => throw new ArgumentException("Invalid data type!") + }; + + + var min = extraByteMarshalMin.ToArray(); + var max = extraByteMarshalMax.ToArray(); + Array.Copy(min, currentExtra.min, min.Length); + Array.Copy(max, currentExtra.max, max.Length); + + + _extraByteDesc.Add(currentExtra); + + _header.OffsetToPointData += 192; + // each description is 192 bytes + } + + offset += attribute.Size; } } @@ -254,6 +403,25 @@ private void ParseAndFillHeader() Marshal.FreeHGlobal(mem); } } + + foreach (var extraByte in _extraByteDesc) + { + var memExtra = Marshal.AllocHGlobal(Marshal.SizeOf()); + try + { + // Copy the struct to unmanaged memory. + Marshal.StructureToPtr(extraByte, memExtra, false); + var dest = new byte[Marshal.SizeOf()]; + Marshal.Copy(memExtra, dest, 0, Marshal.SizeOf()); + _fileStream.Write(dest); + + } + finally + { + // Free the unmanaged memory. + Marshal.FreeHGlobal(memExtra); + } + } } /// @@ -282,10 +450,16 @@ public void Write(Action? progressCallback = null) // we need to shrink each point back (skip byte 16) // extra bytes and everything is already included // TODO: check if extra bytes are already aligned correctely with LAS spec + // probably not, after conversion + // TODO: Convert bytes to extra bytes, offset to bytes should be given or inside the VRL stream.Read(tmpArray); - _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) + + + _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) _fileStream.Write(tmpArray[16..Metadata.PointSize]); // skip array pos [15], byte 16 - Debug.Assert(strSize + 36 == _fileStream.Length); + //Debug.Assert(strSize + 36 == _fileStream.Length); + + } } From d7f1d1eb423beb97f433ed1c65e0e0d68e53d379 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 3 Apr 2023 12:18:24 +0200 Subject: [PATCH 166/294] Write extra bytes works, TOOD: Cleanup, tests, merge --- src/PointCloud/Potree/Potree2LAS.cs | 49 +++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 725019375..dcfcf96f0 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -257,6 +257,10 @@ private void ParseAndFillHeader() if (_potreeData.Metadata.OffsetToExtraBytes != -1) { var offset = 0; + var sizeofExtraBytesAfterHeader = 0; // extra variable for vlr entries + _header.NumberOfVariableLengthRecords++; + + // we have extra bytes to append to each point // check how many and which // parse them and add them to the header @@ -266,27 +270,10 @@ private void ParseAndFillHeader() if (attribute == null) continue; if (offset >= _potreeData.Metadata.OffsetToExtraBytes) { - _header.NumberOfVariableLengthRecords++; - _header.OffsetToPointData += 54; - // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. - + var desc = Encoding.ASCII.GetBytes(attribute.Description.Append('\0').ToArray()); var name = Encoding.ASCII.GetBytes(attribute.Name.Append('\0').ToArray()); - var currentEntry = new VariableLengthRecordHeader - { - RecordLengthAfterHeader = (ushort)_header.OffsetToPointData, // current offset - RecordId = 4 - }; - - Guard.IsLessThan(desc.Length, currentEntry.Description.Length); - Array.Copy(desc, currentEntry.Description, desc.Length); - - // this is just for the LAS header, we should check if we can already - // build an internal list to update later when writing the actual extra bytes - _vlrh.Add(currentEntry); - - var extraByteType = attribute.Type switch { //see Las Specification @@ -359,11 +346,34 @@ private void ParseAndFillHeader() _header.OffsetToPointData += 192; // each description is 192 bytes - + sizeofExtraBytesAfterHeader += 192; } offset += attribute.Size; } + + // add the actual variable record header + var vlr = new VariableLengthRecordHeader + { + RecordLengthAfterHeader = (ushort)sizeofExtraBytesAfterHeader, // offset with all extraBytes + RecordId = 4 + }; + + var description = Encoding.UTF8.GetBytes("Extra Bytes Record\0"); + var userId = Encoding.UTF8.GetBytes("LASF_Spec\0"); + + Guard.IsLessThan(description.Length, vlr.Description.Length); + Guard.IsLessThan(userId.Length, vlr.UserId.Length); + Array.Copy(description, vlr.Description, description.Length); + Array.Copy(userId, vlr.UserId, userId.Length); + + // this is just for the LAS header, we should check if we can already + // build an internal list to update later when writing the actual extra bytes + _vlrh.Add(vlr); + + _header.OffsetToPointData += 54; + // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. + // add, too. Complete: header + variableLengthRecord + n * extraBytes (192bytes) } // Initialize unmanged memory to hold the struct. @@ -384,7 +394,6 @@ private void ParseAndFillHeader() } // append all variable length record header - foreach (var vlr in _vlrh) { var mem = Marshal.AllocHGlobal(Marshal.SizeOf()); From 20ea14fcf94a9dfbb09b37a3f61e869adf403e8e Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 3 Apr 2023 12:55:45 +0200 Subject: [PATCH 167/294] housekeeping --- src/PointCloud/Potree/Potree2LAS.cs | 156 +++++----------------- src/PointCloud/Potree/V2/Potree2Reader.cs | 4 +- 2 files changed, 34 insertions(+), 126 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index dcfcf96f0..50bb20eb5 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -80,15 +80,27 @@ public LASHeader() { } internal ulong[] NbrOfPointsByReturn = new ulong[15]; } + /// + /// LAS point type + /// public enum LASPointType : byte { + /// + /// 0 + /// Zero = 0x0, + /// + /// 2 + /// Two = 0x2, + /// + /// 7 + /// Seven = 0x7 } [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct VariableLengthRecordHeader + internal struct VariableLengthRecordHeader { public VariableLengthRecordHeader() { } @@ -312,8 +324,8 @@ private void ParseAndFillHeader() "int16" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (short)x).ToArray()), "uint32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (ulong)x).ToArray()), "int32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (long)x).ToArray()), - "int64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (Int64)x).ToArray()), - "uint64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (UInt64)x).ToArray()), + "int64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (long)x).ToArray()), + "uint64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (UInt64)x).ToArray()), "float" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (float)x).ToArray()), "double" => MemoryMarshal.AsBytes(attribute.MinList.ToArray()), _ => throw new ArgumentException("Invalid data type!") @@ -328,8 +340,8 @@ private void ParseAndFillHeader() "int16" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (short)x).ToArray()), "uint32" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (ulong)x).ToArray()), "int32" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (long)x).ToArray()), - "int64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (Int64)x).ToArray()), - "uint64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (UInt64)x).ToArray()), + "int64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (long)x).ToArray()), + "uint64" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (ulong)x).ToArray()), "float" => MemoryMarshal.AsBytes(attribute.MaxList.Select(x => (float)x).ToArray()), "double" => MemoryMarshal.AsBytes(attribute.MaxList.ToArray()), _ => throw new ArgumentException("Invalid data type!") @@ -373,62 +385,59 @@ private void ParseAndFillHeader() _header.OffsetToPointData += 54; // LAS 1.4 Spec: Each Variable Length Record Header is 54 bytes in length. - // add, too. Complete: header + variableLengthRecord + n * extraBytes (192bytes) + // add, too. Complete size: header + variableLengthRecord + n * extraBytes (192bytes) } // Initialize unmanged memory to hold the struct. - var ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + var headerPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { // Copy the struct to unmanaged memory. - Marshal.StructureToPtr(_header, ptr, false); + Marshal.StructureToPtr(_header, headerPtr, false); var dest = new byte[Marshal.SizeOf()]; - Marshal.Copy(ptr, dest, 0, Marshal.SizeOf()); + Marshal.Copy(headerPtr, dest, 0, Marshal.SizeOf()); _fileStream.Write(dest); - } finally { // Free the unmanaged memory. - Marshal.FreeHGlobal(ptr); + Marshal.FreeHGlobal(headerPtr); } // append all variable length record header foreach (var vlr in _vlrh) { - var mem = Marshal.AllocHGlobal(Marshal.SizeOf()); + var vlrPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { // Copy the struct to unmanaged memory. - Marshal.StructureToPtr(vlr, mem, false); + Marshal.StructureToPtr(vlr, vlrPtr, false); var dest = new byte[Marshal.SizeOf()]; - Marshal.Copy(mem, dest, 0, Marshal.SizeOf()); + Marshal.Copy(vlrPtr, dest, 0, Marshal.SizeOf()); _fileStream.Write(dest); - } finally { // Free the unmanaged memory. - Marshal.FreeHGlobal(mem); + Marshal.FreeHGlobal(vlrPtr); } } foreach (var extraByte in _extraByteDesc) { - var memExtra = Marshal.AllocHGlobal(Marshal.SizeOf()); + var extraBytePtr = Marshal.AllocHGlobal(Marshal.SizeOf()); try { // Copy the struct to unmanaged memory. - Marshal.StructureToPtr(extraByte, memExtra, false); + Marshal.StructureToPtr(extraByte, extraBytePtr, false); var dest = new byte[Marshal.SizeOf()]; - Marshal.Copy(memExtra, dest, 0, Marshal.SizeOf()); + Marshal.Copy(extraBytePtr, dest, 0, Marshal.SizeOf()); _fileStream.Write(dest); - } finally { // Free the unmanaged memory. - Marshal.FreeHGlobal(memExtra); + Marshal.FreeHGlobal(extraBytePtr); } } } @@ -458,17 +467,10 @@ public void Write(Action? progressCallback = null) // we need to shrink each point back (skip byte 16) // extra bytes and everything is already included - // TODO: check if extra bytes are already aligned correctely with LAS spec - // probably not, after conversion - // TODO: Convert bytes to extra bytes, offset to bytes should be given or inside the VRL stream.Read(tmpArray); - - _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) + _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) _fileStream.Write(tmpArray[16..Metadata.PointSize]); // skip array pos [15], byte 16 - //Debug.Assert(strSize + 36 == _fileStream.Length); - - } } @@ -486,19 +488,10 @@ protected virtual void Dispose(bool disposing) _fileStream.Dispose(); } - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null disposedValue = true; } } - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~Potree2LAS() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - /// /// Dispose /// @@ -508,92 +501,5 @@ public void Dispose() Dispose(disposing: true); GC.SuppressFinalize(this); } - - // old code, replace - //private static void WritePotree2LAS(IEnumerable points, PotreeMetadata metadata, FileInfo savePath) - //{ - // Guard.IsNotNull(savePath); - // Guard.IsNotNull(points); - // Guard.IsTrue(savePath.Extension == ".las"); - - // Guard.IsEqualTo(Marshal.SizeOf(), 26); - // Guard.IsEqualTo(Marshal.SizeOf(), 375); - - // if (savePath.Exists) - // Diagnostics.Warn($"File {savePath.FullName} does exist, overwriting ..."); - - // var scaleFactor = metadata.Scale; - - // const float maxColorValuePotree = byte.MaxValue; - // const short maxIntensityValuePotree = short.MaxValue; - // const ushort maxColorAndIntensityValueLAS = ushort.MaxValue; - - // var invFlipMatrix = Potree2Consts.YZflip.Invert(); - - // convertedData.Add(new LASPoint - // { - // X = (uint)((ptFlipped.x) / scaleFactor.x), - // Y = (uint)((ptFlipped.y) / scaleFactor.y), - // Z = (uint)((ptFlipped.z) / scaleFactor.z), - // Classification = p.Classification, - // //Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), - // R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), - // G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), - // B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), - // }); - //} - - // foreach (var p in points) - // { - // // flipped y/z - // var ptFlipped = invFlipMatrix * p.Position; - - // convertedData.Add(new LASPoint - // { - // X = (uint)((ptFlipped.x) / scaleFactor.x), - // Y = (uint)((ptFlipped.y) / scaleFactor.y), - // Z = (uint)((ptFlipped.z) / scaleFactor.z), - // Classification = p.Classification, - // Intensity = (ushort)((p.Intensity / maxIntensityValuePotree) * maxColorAndIntensityValueLAS), - // R = (ushort)(p.Color.r / maxColorValuePotree * maxColorAndIntensityValueLAS), - // G = (ushort)(p.Color.g / maxColorValuePotree * maxColorAndIntensityValueLAS), - // B = (ushort)(p.Color.b / maxColorValuePotree * maxColorAndIntensityValueLAS), - // }); - // } - - // var min = metadata.Attributes["position"].Min; - // var max = metadata.Attributes["position"].Max; - - // var header = new LASHeader - // { - // // flipped y/z - // OffsetX = metadata.Offset.x, - // OffsetY = metadata.Offset.y, - // OffsetZ = metadata.Offset.z, - // ScaleFactorX = metadata.Scale.x, - // ScaleFactorY = metadata.Scale.y, - // ScaleFactorZ = metadata.Scale.z, - // NumberOfPtRecords = (ulong)convertedData.Count, - // MinX = min.x, - // MaxX = max.x, - // MinY = min.y, - // MaxY = max.y, - // MinZ = min.z, - // MaxZ = max.z - // }; - - // using var fs = savePath.Create(); - // using var bw = new BinaryWriter(fs); - - // bw.Write(ToByteArray(header)); - - // foreach (var p in convertedData) - // { - // bw.Write(ToByteArray(p)); - // } - - // bw.Close(); - // fs.Close(); - //} } } \ No newline at end of file diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 0f6a38b9e..41b3319b2 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -323,6 +323,8 @@ public void ReadFile(PotreeData potreeData) CalculateAttributeOffsets(ref Metadata); + // TODO(mr): Add isExtraBytes, adapt in LASReader + Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); var data = File.ReadAllBytes(hierarchyFilePath); @@ -336,7 +338,7 @@ public void ReadFile(PotreeData potreeData) FlipYZAxis(Metadata, Hierarchy); - // do not adapt the global AABB after conversion, keep for writing + // do not adapt the global AABB after conversion, keep original for LAS writing //Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; //Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; From 6659d92dd80dde5ce076ebba3cf86a2f51de6c71 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 3 Apr 2023 18:04:08 +0200 Subject: [PATCH 168/294] Faster parsing, some housekeeping --- .../Core/PointCloudPotree2Core.cs | 4 +- src/PointCloud/Potree/Potree2LAS.cs | 46 ++++++++++++------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index a796ba369..26bf6c6b9 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -102,11 +102,11 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2\\plp_tmp_IPM_A-E-97")); + _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2__AG_08\\plp_tmp_Halle2__AG_08")); _potreeData = _potreeReader.PotreeData; var sw = new Stopwatch(); sw.Start(); - using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Two); + using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Seven); laswriter.Write(); Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 50bb20eb5..cf4d1aaca 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -12,6 +12,8 @@ using SixLabors.ImageSharp.PixelFormats; using System.Collections.Generic; using System.Linq; +using CommunityToolkit.HighPerformance; +using System.Runtime.CompilerServices; namespace Fusee.PointCloud.Potree { @@ -130,7 +132,7 @@ internal struct InternalVariableLengthRecord /// Struct for the Specification Defined VLR "Extra Bytes". This has always 192 bytes. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct LasExtraBytes + internal struct LasExtraBytes { public LasExtraBytes() { } @@ -184,7 +186,14 @@ public LasExtraBytes() { } /// public class Potree2LAS : IPointWriter, IDisposable { + /// + /// Path to save the las file to + /// public FileInfo SavePath { get; private set; } + + /// + /// Metadata (scale, offset) + /// public IPointWriterMetadata Metadata { get; private set; } private readonly Stream _fileStream; @@ -234,9 +243,16 @@ private void ParseAndFillHeader() Guard.IsLessThan(year, ushort.MaxValue); Guard.IsLessThan(size, ushort.MaxValue); - _fileStream.Seek(0, SeekOrigin.Begin); + // check if the point type selected by the user is correct(ish) + Guard.IsGreaterThanOrEqualTo(Metadata.PointSize, _type switch + { + LASPointType.Zero => 21, + LASPointType.Two => 27, + LASPointType.Seven => 37, + _ => throw new NotImplementedException(), + }); + - // TODO: Parse / generate fitting point type and extra bytes, etc... _header = new LASHeader { PointDataRecordLength = (ushort)size, @@ -379,7 +395,7 @@ private void ParseAndFillHeader() Array.Copy(description, vlr.Description, description.Length); Array.Copy(userId, vlr.UserId, userId.Length); - // this is just for the LAS header, we should check if we can already + // this is just for the LAS header, we should check if we can already // build an internal list to update later when writing the actual extra bytes _vlrh.Add(vlr); @@ -445,30 +461,28 @@ private void ParseAndFillHeader() /// /// This methods starts the LASfile write progress. /// - /// This methods returns the current progress [0-100] (per-cent) - public void Write(Action? progressCallback = null) + public void Write() { + Guard.IsNotNull(_header); + // advance to end of stream _fileStream.Seek(0, SeekOrigin.End); + var fileLength = (long)Metadata.PointCount * (long)Metadata.PointSize; - Guard.IsNotNull(_header); + // set complete length before writing, this generates the full file + // writing operations are much faster afterwards + _fileStream.SetLength(fileLength); - using var stream = _potreeData.OctreeMappedFile.CreateViewStream(); - ulong fileLength = (ulong)Metadata.PointCount * (ulong)Metadata.PointSize; + using var inputStream = _potreeData.OctreeMappedFile.CreateViewStream(); Span tmpArray = stackalloc byte[_potreeData.Metadata.PointSize]; // DO NOT USE stream.Length as the MemoryMappedStream aligns with the page size - for (ulong i = 0U; i < fileLength; i += (ulong)Metadata.PointSize) + for (long i = 0U; i < fileLength; i += Metadata.PointSize) { - var strSize = _fileStream.Length; - float progress = (100f / Metadata.PointCount) * (i / (ulong)Metadata.PointSize); - progressCallback?.Invoke((int)progress); - // we need to shrink each point back (skip byte 16) // extra bytes and everything is already included - stream.Read(tmpArray); - + inputStream.Read(tmpArray); _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) _fileStream.Write(tmpArray[16..Metadata.PointSize]); // skip array pos [15], byte 16 } From 986dcb41f78d8b339612bc33aac1f04e3f93b60b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 3 Apr 2023 18:08:47 +0200 Subject: [PATCH 169/294] comments & empty spaces --- src/PointCloud/Potree/Potree2LAS.cs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index cf4d1aaca..0f3721cae 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -150,29 +150,29 @@ public LasExtraBytes() { } public byte[] unused = new byte[4]; // 4 bytes [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] no_data = new byte[8]; // 8 bytes anytype + public byte[] no_data = new byte[8]; // 8 bytes anytype [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] deprecated1 = new byte[16]; // 16 bytes [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] min = new byte[8]; // 8 bytes anytype + public byte[] min = new byte[8]; // 8 bytes anytype [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] deprecated2 = new byte[16]; // 16 bytes [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] max = new byte[8]; // 8 bytes anytype + public byte[] max = new byte[8]; // 8 bytes anytype [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] deprecated3 = new byte[16]; // 16 bytes - public double scale = 0; // 8 bytes + public double scale = 0; // 8 bytes [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] deprecated4 = new byte[16]; // 16 bytes - public double offset = 0; // 8 bytes + public double offset = 0; // 8 bytes [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] deprecated5 = new byte[16]; // 16 bytes @@ -182,7 +182,7 @@ public LasExtraBytes() { } } /// - /// This class provides methods to convert and saves clouds to LAS 1.4 + /// This class provides methods to convert and saves to LAS 1.4 specification /// public class Potree2LAS : IPointWriter, IDisposable { @@ -252,7 +252,6 @@ private void ParseAndFillHeader() _ => throw new NotImplementedException(), }); - _header = new LASHeader { PointDataRecordLength = (ushort)size, @@ -278,17 +277,16 @@ private void ParseAndFillHeader() _header.LeacyNbrOfPointsByRtn[0] = (uint)Metadata.PointCount; _header.NbrOfPointsByReturn[0] = (ulong)Metadata.PointCount; - var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}"); + var generatingSoftware = Encoding.UTF8.GetBytes($"Fusee v.{Assembly.GetExecutingAssembly().GetName().Version}\0"); Guard.IsLessThan(generatingSoftware.Length, _header.GeneratingSoftware.Length); Array.Copy(generatingSoftware, _header.GeneratingSoftware, generatingSoftware.Length); if (_potreeData.Metadata.OffsetToExtraBytes != -1) { var offset = 0; - var sizeofExtraBytesAfterHeader = 0; // extra variable for vlr entries + var sizeOfExtraBytesAfterHeader = 0; // extra variable for vlr entries _header.NumberOfVariableLengthRecords++; - // we have extra bytes to append to each point // check how many and which // parse them and add them to the header @@ -319,7 +317,6 @@ private void ParseAndFillHeader() _ => throw new ArgumentException("Invalid data type!") }; - var currentExtra = new LasExtraBytes { data_type = (byte)extraByteType @@ -363,18 +360,16 @@ private void ParseAndFillHeader() _ => throw new ArgumentException("Invalid data type!") }; - var min = extraByteMarshalMin.ToArray(); var max = extraByteMarshalMax.ToArray(); Array.Copy(min, currentExtra.min, min.Length); Array.Copy(max, currentExtra.max, max.Length); - _extraByteDesc.Add(currentExtra); _header.OffsetToPointData += 192; // each description is 192 bytes - sizeofExtraBytesAfterHeader += 192; + sizeOfExtraBytesAfterHeader += 192; } offset += attribute.Size; @@ -383,7 +378,7 @@ private void ParseAndFillHeader() // add the actual variable record header var vlr = new VariableLengthRecordHeader { - RecordLengthAfterHeader = (ushort)sizeofExtraBytesAfterHeader, // offset with all extraBytes + RecordLengthAfterHeader = (ushort)sizeOfExtraBytesAfterHeader, // offset with all extraBytes RecordId = 4 }; From 1f1f98efbd6e8d88f35bce25b6f55f284f9ed996 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 5 Apr 2023 14:31:11 +0200 Subject: [PATCH 170/294] Improved GetNode --- src/PointCloud/Potree/V2/Data/PotreeData.cs | 16 ++++++++++++-- src/PointCloud/Potree/V2/Data/PotreeNode.cs | 23 +++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/PointCloud/Potree/V2/Data/PotreeData.cs b/src/PointCloud/Potree/V2/Data/PotreeData.cs index d07c05ffb..985df30f6 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeData.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeData.cs @@ -54,13 +54,25 @@ public PotreeData(PotreeHierarchy potreeHierarchy, PotreeMetadata potreeMetadata } /// - /// Returns the node for the given . + /// Returns the node for a given . /// /// /// public PotreeNode? GetNode(OctantId octantId) { - return Hierarchy.Nodes.Find(n => n.Name == OctantId.OctantIdToPotreeName(octantId)); + return Hierarchy.Nodes.Find(n => n.OctantId == octantId); + } + + /// + /// Returns the node for a given name of a . + /// + /// + /// + public PotreeNode? GetNode(string nodeName) + { + var octantId = new OctantId(nodeName); + + return GetNode(octantId); } #region IDisposable diff --git a/src/PointCloud/Potree/V2/Data/PotreeNode.cs b/src/PointCloud/Potree/V2/Data/PotreeNode.cs index 84d9f58b1..c5dc6d5d6 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeNode.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeNode.cs @@ -1,5 +1,9 @@ -using Fusee.Math.Core; +using CommunityToolkit.Diagnostics; +using Fusee.Math.Core; +using Fusee.PointCloud.Common; +using Newtonsoft.Json; using System; +using System.Threading.Tasks.Sources; #pragma warning disable CS1591 @@ -11,7 +15,22 @@ public PotreeNode() { } - public string Name = ""; + [JsonIgnore] + private string _name; + + public string Name + { + get { return _name; } + set + { + Guard.IsNotNull(value); + _name = value; + OctantId = new OctantId(value); + } + } + + [JsonIgnore] + public OctantId OctantId; public AABBd Aabb { get; set; } public PotreeNode Parent { get; set; } From 65205a4e67d292ccfd6054feb64bcfa21847fd52 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 5 Apr 2023 14:33:16 +0200 Subject: [PATCH 171/294] Added IEquatable and GetHashCode to OctantId --- src/PointCloud/Common/OctantID.cs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/PointCloud/Common/OctantID.cs b/src/PointCloud/Common/OctantID.cs index 1e3400722..05f3c38b2 100644 --- a/src/PointCloud/Common/OctantID.cs +++ b/src/PointCloud/Common/OctantID.cs @@ -64,7 +64,7 @@ enum OctantHelper : long // - public struct OctantId : IEnumerable<(int, OctantOrientation)> + public struct OctantId : IEnumerable<(int, OctantOrientation)>, IEquatable { private long _id = -1; @@ -227,9 +227,28 @@ IEnumerator IEnumerable.GetEnumerator() public static bool IsDown(OctantOrientation oo) => (oo & OctantOrientation.DownUpMask) == 0; public static bool IsUp(OctantOrientation oo) => (oo & OctantOrientation.DownUpMask) != 0; + /// public override string ToString() { return Convert.ToString(this, 2); } + + /// + public override int GetHashCode() + { + return _id.GetHashCode(); + } + + /// + public override bool Equals(object obj) + { + return Equals((OctantId)obj); + } + + /// + public bool Equals(OctantId other) + { + return _id == other._id; + } } } \ No newline at end of file From a94483262e39b8cedbc4d834f30c9e6c5397d6bb Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 11 Apr 2023 09:14:37 +0200 Subject: [PATCH 172/294] Comments --- src/PointCloud/Potree/Potree2LAS.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 0f3721cae..74085916c 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -7,13 +7,8 @@ using System.Runtime.InteropServices; using System.Text; using Fusee.PointCloud.Potree.V2.Data; -using System.IO.MemoryMappedFiles; -using System.Diagnostics; -using SixLabors.ImageSharp.PixelFormats; using System.Collections.Generic; using System.Linq; -using CommunityToolkit.HighPerformance; -using System.Runtime.CompilerServices; namespace Fusee.PointCloud.Potree { @@ -139,9 +134,9 @@ public LasExtraBytes() { } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] reserved = new byte[2]; // 2 bytes - public byte data_type = 0; // 1 byte + public byte data_type = 0; // 1 byte - public byte options = 0; // 1 byte + public byte options = 0; // 1 byte [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] name = new byte[32]; // 32 bytes From 9c8735e93c708440964637aade0ec5343098b1e4 Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Tue, 11 Apr 2023 07:22:56 +0000 Subject: [PATCH 173/294] Linting --- src/PointCloud/Common/IPointWriter.cs | 2 +- src/PointCloud/Potree/Potree2LAS.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index 801e33251..ba4523f50 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -81,7 +81,7 @@ public interface IPointWriterMetadata /// /// Global of the point cloud /// - public AABBd AABB { get; } + public AABBd AABB { get; } /// /// The encoding of every point, as we save the point cloud as elements diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 74085916c..6917872b5 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -1,14 +1,14 @@ using CommunityToolkit.Diagnostics; using Fusee.Base.Core; using Fusee.PointCloud.Common; +using Fusee.PointCloud.Potree.V2.Data; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; -using Fusee.PointCloud.Potree.V2.Data; -using System.Collections.Generic; -using System.Linq; namespace Fusee.PointCloud.Potree { From e4743a0d68622b2de3edbf226f76323c8bceb345 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 02:58:27 +0000 Subject: [PATCH 174/294] Bump Microsoft.AspNetCore.Components.WebAssembly from 7.0.4 to 7.0.5 Bumps [Microsoft.AspNetCore.Components.WebAssembly](https://github.com/dotnet/aspnetcore) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.4...v7.0.5) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 4 ++-- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 4 ++-- .../Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 4 ++-- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index da8637bef..af90dca0d 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index 5774bd5ee..6cd422e74 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index 9bba96e27..e67f43081 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 5cc6521a1..2479e1ed6 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index d074a1fb1..c5fb9e605 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -14,7 +14,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 8454b9f3d..8b0bd8719 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -14,7 +14,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 04217852e..b6498c91a 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -23,7 +23,7 @@ analyzers - + From 14ae93cacabaa380e21b2cc00be40d5b91a97e26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 02:58:33 +0000 Subject: [PATCH 175/294] Bump Microsoft.JSInterop.WebAssembly from 7.0.4 to 7.0.5 Bumps [Microsoft.JSInterop.WebAssembly](https://github.com/dotnet/aspnetcore) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.4...v7.0.5) --- updated-dependencies: - dependency-name: Microsoft.JSInterop.WebAssembly dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 4 ++-- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 4 ++-- .../Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 4 ++-- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index da8637bef..f52f9e85f 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index 5774bd5ee..0bfb2f526 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -17,7 +17,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index 9bba96e27..aadd002ba 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 5cc6521a1..918b4cd22 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index d074a1fb1..5dbe372b2 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 8454b9f3d..da63e6ab8 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -16,7 +16,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 04217852e..e414f5491 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -25,7 +25,7 @@ - + From 1e8b5fcb2a5b8e672349e335a75b4dd08454a12f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 02:58:38 +0000 Subject: [PATCH 176/294] Bump Microsoft.JSInterop from 7.0.4 to 7.0.5 Bumps [Microsoft.JSInterop](https://github.com/dotnet/aspnetcore) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.4...v7.0.5) --- updated-dependencies: - dependency-name: Microsoft.JSInterop dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj | 4 ++-- .../Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj index 44ab3c16f..99eb96f7a 100644 --- a/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj +++ b/src/Base/Imp/Blazor/Fusee.Base.Imp.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -16,6 +16,6 @@ analyzers - + diff --git a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj index fff90079c..e5181852a 100644 --- a/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj +++ b/src/Engine/Imp/Graphics/Blazor/Fusee.Engine.Imp.Graphics.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -23,7 +23,7 @@ analyzers - + From e3cd9ac2c726ff5fd8270ca1418f6a6c83d22fa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 02:58:43 +0000 Subject: [PATCH 177/294] Bump Microsoft.AspNetCore.Components.WebAssembly.DevServer Bumps [Microsoft.AspNetCore.Components.WebAssembly.DevServer](https://github.com/dotnet/aspnetcore) from 7.0.4 to 7.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md) - [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.4...v7.0.5) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.Components.WebAssembly.DevServer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj | 2 +- .../Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj | 4 ++-- .../Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj | 4 ++-- .../Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj | 4 ++-- .../Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj | 4 ++-- src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj index da8637bef..b226fc116 100644 --- a/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj +++ b/Examples/Complete/Camera/Blazor/Fusee.Examples.Camera.Blazor.csproj @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj index 5774bd5ee..799eec71f 100644 --- a/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj +++ b/Examples/Complete/Deferred/Blazor/Fusee.Examples.Deferred.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -16,7 +16,7 @@ - + diff --git a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj index 9bba96e27..05a61cd0b 100644 --- a/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj +++ b/Examples/Complete/Picking/Blazor/Fusee.Examples.Picking.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj index 5cc6521a1..f5922eea9 100644 --- a/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj +++ b/Examples/Complete/PickingRayCast/Blazor/Fusee.Examples.PickingRayCast.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj index d074a1fb1..627a38c5f 100644 --- a/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj +++ b/Examples/Complete/RenderContextOnly/Blazor/Fusee.Examples.RenderContextOnly.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -15,7 +15,7 @@ - + diff --git a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj index 8454b9f3d..883894f88 100644 --- a/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj +++ b/Examples/Complete/Simple/Blazor/Fusee.Examples.Simple.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 service-worker-assets.js @@ -15,7 +15,7 @@ - + diff --git a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj index 04217852e..2fb38b49a 100644 --- a/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj +++ b/src/Engine/Player/Blazor/Fusee.Engine.Player.Blazor.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -24,7 +24,7 @@ - + From 268be14ee9bac8fe945d1c8b2359b1becf623bf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Apr 2023 02:58:24 +0000 Subject: [PATCH 178/294] Bump ImGui.NET from 1.89.4 to 1.89.5 Bumps [ImGui.NET](https://github.com/mellinoe/imgui.net) from 1.89.4 to 1.89.5. - [Release notes](https://github.com/mellinoe/imgui.net/releases) - [Commits](https://github.com/mellinoe/imgui.net/compare/v1.89.4...v1.89.5) --- updated-dependencies: - dependency-name: ImGui.NET dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj index 8eb4fac93..b196663d7 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Fusee.ImGuiImp.Desktop.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -13,7 +13,7 @@ - + From 222d006e8d0a5039ed9eb1d340ec34d3d92b78bc Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 24 Apr 2023 11:23:51 +0200 Subject: [PATCH 179/294] ChildList: only remove from old parent's child list if parent object differs --- src/Engine/Core/Scene/ChildList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Engine/Core/Scene/ChildList.cs b/src/Engine/Core/Scene/ChildList.cs index c3dd1b0a5..ea45aa706 100644 --- a/src/Engine/Core/Scene/ChildList.cs +++ b/src/Engine/Core/Scene/ChildList.cs @@ -75,7 +75,8 @@ private void AddSceneNode(SceneNode snc) else { //remove from old parent's child list - snc.Parent.Children.Remove(snc); + if(this != snc.Parent.Children) + snc.Parent.Children.Remove(snc); OnAdd?.Invoke(this, new AddChildEventArgs(snc)); } } From ad10334bf7da6d16dff08dde9225cbe0646cca6c Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Thu, 27 Apr 2023 16:52:09 +0200 Subject: [PATCH 180/294] Rework --- src/PointCloud/Common/IPointCloudImp.cs | 12 +++++- .../Common/InvalidateGpuDataCache.cs | 11 ++++++ src/PointCloud/Core/MeshMaker.cs | 1 - src/PointCloud/Core/PointCloudDataHandler.cs | 38 ++++++++++++++++--- .../PointCloudDataHandlerBase.cs | 10 ++++- .../Core/Scene/PointCloudComponent.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 4 +- .../Core/Scene/PointCloudRenderModule.cs | 6 +-- src/PointCloud/Core/VisualizationPoint.cs | 2 +- src/PointCloud/Potree/Potree2Cloud.cs | 15 +++++++- src/PointCloud/Potree/Potree2CloudDynamic.cs | 34 +++++++++++++++-- .../Potree/Potree2CloudInstanced.cs | 15 +++++++- src/PointCloud/Potree/V2/Potree2Writer.cs | 1 + 13 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 src/PointCloud/Common/InvalidateGpuDataCache.cs rename src/PointCloud/{Common => Core}/PointCloudDataHandlerBase.cs (93%) diff --git a/src/PointCloud/Common/IPointCloudImp.cs b/src/PointCloud/Common/IPointCloudImp.cs index 7dc571e89..cca4609ef 100644 --- a/src/PointCloud/Common/IPointCloudImp.cs +++ b/src/PointCloud/Common/IPointCloudImp.cs @@ -1,4 +1,6 @@ -using Fusee.Engine.Core; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Engine.Common; +using Fusee.Engine.Core; using Fusee.Math.Core; using System.Collections.Generic; @@ -9,6 +11,8 @@ namespace Fusee.PointCloud.Common /// public interface IPointCloudImpBase { + public InvalidateGpuDataCache InvalidateGpuDataCache { get; } + /// /// Center of the PointCloud's AABB /// @@ -63,15 +67,19 @@ public float UpdateRate public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 camPos, float4x4 modelMat); } + public delegate void UpdateGpuData(IEnumerable gpuData, MemoryOwner points); + /// /// Smallest common set of properties that are needed to render point clouds out of core. /// - public interface IPointCloudImp : IPointCloudImpBase + public interface IPointCloudImp : IPointCloudImpBase { /// /// The , created from visible octants/point chunks, that are ready to be rendered. /// public List GpuDataToRender { get; set; } + public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points); + } } \ No newline at end of file diff --git a/src/PointCloud/Common/InvalidateGpuDataCache.cs b/src/PointCloud/Common/InvalidateGpuDataCache.cs new file mode 100644 index 000000000..654d63126 --- /dev/null +++ b/src/PointCloud/Common/InvalidateGpuDataCache.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Fusee.PointCloud.Common +{ + public class InvalidateGpuDataCache + { + public bool IsDirty; + } +} diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index eedf5264c..9dc26d2c2 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -4,7 +4,6 @@ using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Potree.V2.Data; using System.Collections.Generic; namespace Fusee.PointCloud.Core diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index f43af883d..baf5f48ef 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -1,9 +1,9 @@ +using CommunityToolkit.Diagnostics; using CommunityToolkit.HighPerformance.Buffers; using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Scene; using Fusee.PointCloud.Common; -using Fusee.PointCloud.Potree.V2.Data; using Microsoft.Extensions.Caching.Memory; using System; using System.Collections.Generic; @@ -123,7 +123,34 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin public override IEnumerable? GetGpuData(OctantId octantId) { if (_gpuDataCache.TryGetValue(octantId, out var gpuData)) + { + Guard.IsNotNull(InvalidateCacheToken); + if (InvalidateCacheToken.IsDirty) + { + if (_pointCache.TryGetValue(octantId, out var points)) + { + if (UpdateGpuDataCache != null) + { + UpdateGpuDataCache.Invoke(gpuData, points); + } + else + { + if (!_doRenderInstanced) + gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); + else + gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); + } + _gpuDataCache.AddOrUpdate(octantId, gpuData); + } + else + { + throw new Exception("Load points!"); + } + } + return gpuData; + } + else if (DisposeQueue.TryGetValue(octantId, out gpuData)) { lock (LockDisposeQueue) @@ -183,6 +210,8 @@ public override void TriggerPointLoading(OctantId guid) { if (!LoadingQueue.Contains(guid) && LoadingQueue.Count <= MaxNumberOfNodesToLoad) { + if (_pointCache.TryGetValue(guid, out var points)) return; + lock (LockLoadingQueue) { LoadingQueue.Add(guid); @@ -190,11 +219,8 @@ public override void TriggerPointLoading(OctantId guid) _ = Task.Run(() => { - if (!_pointCache.TryGetValue(guid, out var points)) - { - points = _loadPointsHandler.Invoke(guid); - _pointCache.Add(guid, points); - } + points = _loadPointsHandler.Invoke(guid); + _pointCache.Add(guid, points); lock (LockLoadingQueue) { diff --git a/src/PointCloud/Common/PointCloudDataHandlerBase.cs b/src/PointCloud/Core/PointCloudDataHandlerBase.cs similarity index 93% rename from src/PointCloud/Common/PointCloudDataHandlerBase.cs rename to src/PointCloud/Core/PointCloudDataHandlerBase.cs index e4d163b58..3cd2ffc78 100644 --- a/src/PointCloud/Common/PointCloudDataHandlerBase.cs +++ b/src/PointCloud/Core/PointCloudDataHandlerBase.cs @@ -1,13 +1,17 @@ -using System; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.PointCloud.Common; +using System; using System.Collections.Generic; -namespace Fusee.PointCloud.Common +namespace Fusee.PointCloud.Core { /// /// Manages the caching and loading of point and mesh data. /// public abstract class PointCloudDataHandlerBase : IDisposable where TGpuData : IDisposable { + public InvalidateGpuDataCache InvalidateCacheToken; + /// /// Used to manage gpu pressure when disposing of a large quantity of meshes. /// @@ -57,6 +61,8 @@ public abstract class PointCloudDataHandlerBase : IDisposable where TG /// public abstract void ProcessDisposeQueue(); + public UpdateGpuData, MemoryOwner>? UpdateGpuDataCache; + private bool _disposed = false; /// diff --git a/src/PointCloud/Core/Scene/PointCloudComponent.cs b/src/PointCloud/Core/Scene/PointCloudComponent.cs index 92cdd8241..3f5267df4 100644 --- a/src/PointCloud/Core/Scene/PointCloudComponent.cs +++ b/src/PointCloud/Core/Scene/PointCloudComponent.cs @@ -1,4 +1,4 @@ -using Fusee.Engine.Core.Scene; +using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 452715d01..a91627881 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -41,7 +41,7 @@ public class PointCloudPickerModule : IPickerModule { private PickerState? _state; private readonly PointCloudOctree? _octree; - private readonly IPointCloudImp? _pcImp; + private readonly IPointCloudImp? _pcImp; private readonly float _pointSpacing; /// @@ -193,7 +193,7 @@ private List PickOctantRecursively(PointCloudOctant node, RayD /// The of the . /// The , needs to be of type /// The spacing between points. For Potree use the metadata spacing component * 0.1f
e. g. Spacing = 2.18f, pass 0.218f to this ctor. - public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp, float pointSpacing) + public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp, float pointSpacing) { if (pcImp == null) Diagnostics.Warn("No per point picking possible, no PointCloud type loaded"); diff --git a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs index b5eebeea8..de6d5d6f5 100644 --- a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs @@ -110,19 +110,19 @@ public void RenderPointCloud(PointCloudComponent pointCloud) switch (pointCloud.RenderMode) { case RenderMode.StaticMesh: - foreach (var mesh in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) + foreach (var mesh in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) { _rc.Render(mesh, _isForwardModule); } break; case RenderMode.Instanced: - foreach (var instanceData in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) + foreach (var instanceData in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) { _rc.Render(quad, instanceData, _isForwardModule); } break; case RenderMode.DynamicMesh: - foreach (var mesh in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) + foreach (var mesh in ((IPointCloudImp)pointCloud.PointCloudImp).GpuDataToRender) { _rc.Render(mesh, null, _isForwardModule); } diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index ccbc34ee9..8d58fef93 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -1,7 +1,7 @@ using Fusee.Math.Core; using System; -namespace Fusee.PointCloud.Potree.V2.Data +namespace Fusee.PointCloud.Core { /// /// This point is used for visualization purposes. diff --git a/src/PointCloud/Potree/Potree2Cloud.cs b/src/PointCloud/Potree/Potree2Cloud.cs index 1f94d52be..ee0c9372b 100644 --- a/src/PointCloud/Potree/Potree2Cloud.cs +++ b/src/PointCloud/Potree/Potree2Cloud.cs @@ -1,7 +1,9 @@ -using Fusee.Engine.Core; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Engine.Core; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; +using System; using System.Collections.Generic; namespace Fusee.PointCloud.Potree @@ -9,8 +11,10 @@ namespace Fusee.PointCloud.Potree /// /// Non-point-type-specific implementation of Potree2 clouds. /// - public class Potree2Cloud : IPointCloudImp + public class Potree2Cloud : IPointCloudImp { + public InvalidateGpuDataCache InvalidateGpuDataCache { get; } = new(); + /// /// The complete list of meshes that can be rendered. /// @@ -92,10 +96,17 @@ public Potree2Cloud(PointCloudDataHandlerBase dataHandler, IPointCloudO { GpuDataToRender = new List(); DataHandler = dataHandler; + DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; + DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); _getMeshes = dataHandler.GetGpuData; } + public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + { + throw new NotImplementedException(); + } + /// /// Uses the and to update the visible meshes. /// Called every frame. diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index a9eef4718..a38a231e1 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -1,18 +1,24 @@ -using Fusee.Engine.Core.Scene; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; +using Fusee.PointCloud.Potree.V2.Data; +using SixLabors.ImageSharp.Processing; using System; using System.Collections.Generic; using System.Linq; namespace Fusee.PointCloud.Potree { + /// /// Non-point-type-specific implementation of Potree2 clouds. /// - public class Potree2CloudDynamic : IPointCloudImp + public class Potree2CloudDynamic : IPointCloudImp { + public InvalidateGpuDataCache InvalidateGpuDataCache { get; } = new(); + /// /// The complete list of meshes that can be rendered. /// @@ -85,7 +91,7 @@ public float UpdateRate public float3 Size => new((float)VisibilityTester.Octree.Root.Size); private readonly GetDynamicMeshes _getMeshes; - private bool _doUpdate = true; + private bool _doUpdate = true; /// /// Creates a new instance of type @@ -94,6 +100,8 @@ public Potree2CloudDynamic(PointCloudDataHandlerBase dataHandler, IPointCl { GpuDataToRender = new List(); DataHandler = dataHandler; + DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; + DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); _getMeshes = dataHandler.GetGpuData; } @@ -103,6 +111,24 @@ public Potree2CloudDynamic(PointCloudDataHandlerBase dataHandler, IPointCl /// public Action NewMeshAction; + + public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + { + var countStartSlice = 0; + + foreach (var mesh in meshes) + { + var slice = points.Span.Slice(countStartSlice, mesh.Flags.Length); + + for (int i = 0; i < slice.Length; i++) + { + var pt = slice[i]; + mesh.Flags[i] = pt.Flags; + } + countStartSlice += mesh.Flags.Length; + } + } + /// /// Determins if new Meshes should be loaded. /// @@ -164,6 +190,8 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 } } + InvalidateGpuDataCache.IsDirty = false; + GpuDataToRender.Clear(); GpuDataToRender.AddRange(meshes); } diff --git a/src/PointCloud/Potree/Potree2CloudInstanced.cs b/src/PointCloud/Potree/Potree2CloudInstanced.cs index 830e397c4..00b705a53 100644 --- a/src/PointCloud/Potree/Potree2CloudInstanced.cs +++ b/src/PointCloud/Potree/Potree2CloudInstanced.cs @@ -1,7 +1,9 @@ -using Fusee.Engine.Core.Scene; +using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; +using System; using System.Collections.Generic; namespace Fusee.PointCloud.Potree @@ -9,8 +11,10 @@ namespace Fusee.PointCloud.Potree /// /// Non-point-type-specific implementation of Potree2 clouds. /// - public class Potree2CloudInstanced : IPointCloudImp + public class Potree2CloudInstanced : IPointCloudImp { + public InvalidateGpuDataCache InvalidateGpuDataCache { get; } = new(); + /// /// The complete list of meshes that can be rendered. /// @@ -92,10 +96,17 @@ public Potree2CloudInstanced(PointCloudDataHandlerBase dataHandler { GpuDataToRender = new List(); DataHandler = dataHandler; + DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; + DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); _getInstanceData = dataHandler.GetGpuData; } + public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + { + throw new NotImplementedException(); + } + /// /// Uses the and to update the visible meshes. /// Called every frame. diff --git a/src/PointCloud/Potree/V2/Potree2Writer.cs b/src/PointCloud/Potree/V2/Potree2Writer.cs index 1952f9a40..6328d9e40 100644 --- a/src/PointCloud/Potree/V2/Potree2Writer.cs +++ b/src/PointCloud/Potree/V2/Potree2Writer.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Diagnostics; using CommunityToolkit.HighPerformance.Buffers; using Fusee.PointCloud.Common; +using Fusee.PointCloud.Core; using Fusee.PointCloud.Potree.V2.Data; using System; From 37d40c6b480cd53209912c6bac038973fd2a1294 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 28 Apr 2023 08:09:53 +0200 Subject: [PATCH 181/294] Mesh: reset "Flags" dirty flag --- src/Engine/Core/Scene/Mesh.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Engine/Core/Scene/Mesh.cs b/src/Engine/Core/Scene/Mesh.cs index 757d98c42..00d82eef6 100644 --- a/src/Engine/Core/Scene/Mesh.cs +++ b/src/Engine/Core/Scene/Mesh.cs @@ -205,6 +205,8 @@ public void ResetIndexLists() Colors1.DirtyIndex = false; if (Colors2Set) Colors2.DirtyIndex = false; + if (FlagsSet) + Flags.DirtyIndex = false; From 7853b9e4225476540bd8e3537ee3062b0c34fc70 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 28 Apr 2023 08:10:14 +0200 Subject: [PATCH 182/294] UpdateGpuDataCache: IEnumerable is passed by ref --- src/PointCloud/Common/IPointCloudImp.cs | 4 ++-- src/PointCloud/Core/PointCloudDataHandler.cs | 11 ++++------- src/PointCloud/Potree/Potree2Cloud.cs | 2 +- src/PointCloud/Potree/Potree2CloudDynamic.cs | 2 +- src/PointCloud/Potree/Potree2CloudInstanced.cs | 2 +- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/PointCloud/Common/IPointCloudImp.cs b/src/PointCloud/Common/IPointCloudImp.cs index cca4609ef..0be6e7eeb 100644 --- a/src/PointCloud/Common/IPointCloudImp.cs +++ b/src/PointCloud/Common/IPointCloudImp.cs @@ -67,7 +67,7 @@ public float UpdateRate public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 camPos, float4x4 modelMat); } - public delegate void UpdateGpuData(IEnumerable gpuData, MemoryOwner points); + public delegate void UpdateGpuData(ref IEnumerable gpuData, MemoryOwner points); /// /// Smallest common set of properties that are needed to render point clouds out of core. @@ -79,7 +79,7 @@ public interface IPointCloudImp : IPointCloudImpBase /// public List GpuDataToRender { get; set; } - public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points); + public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points); } } \ No newline at end of file diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index baf5f48ef..d517e262e 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -125,32 +125,28 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin if (_gpuDataCache.TryGetValue(octantId, out var gpuData)) { Guard.IsNotNull(InvalidateCacheToken); + if (InvalidateCacheToken.IsDirty) { if (_pointCache.TryGetValue(octantId, out var points)) { if (UpdateGpuDataCache != null) { - UpdateGpuDataCache.Invoke(gpuData, points); + UpdateGpuDataCache.Invoke(ref gpuData, points); } else { if (!_doRenderInstanced) gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); else - gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); + gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); } _gpuDataCache.AddOrUpdate(octantId, gpuData); } - else - { - throw new Exception("Load points!"); - } } return gpuData; } - else if (DisposeQueue.TryGetValue(octantId, out gpuData)) { lock (LockDisposeQueue) @@ -168,6 +164,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); _gpuDataCache.Add(octantId, gpuData); } + //no points yet, probably in loading queue return null; } diff --git a/src/PointCloud/Potree/Potree2Cloud.cs b/src/PointCloud/Potree/Potree2Cloud.cs index ee0c9372b..c92d3adae 100644 --- a/src/PointCloud/Potree/Potree2Cloud.cs +++ b/src/PointCloud/Potree/Potree2Cloud.cs @@ -102,7 +102,7 @@ public Potree2Cloud(PointCloudDataHandlerBase dataHandler, IPointCloudO _getMeshes = dataHandler.GetGpuData; } - public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { throw new NotImplementedException(); } diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index a38a231e1..7b4c28338 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -112,7 +112,7 @@ public Potree2CloudDynamic(PointCloudDataHandlerBase dataHandler, IPointCl public Action NewMeshAction; - public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { var countStartSlice = 0; diff --git a/src/PointCloud/Potree/Potree2CloudInstanced.cs b/src/PointCloud/Potree/Potree2CloudInstanced.cs index 00b705a53..acbb502a7 100644 --- a/src/PointCloud/Potree/Potree2CloudInstanced.cs +++ b/src/PointCloud/Potree/Potree2CloudInstanced.cs @@ -102,7 +102,7 @@ public Potree2CloudInstanced(PointCloudDataHandlerBase dataHandler _getInstanceData = dataHandler.GetGpuData; } - public void UpdateGpuDataCache(IEnumerable meshes, MemoryOwner points) + public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { throw new NotImplementedException(); } From f5f889c1d56fd60afe0a6b56a62fa45a638b1993 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 28 Apr 2023 08:43:44 +0200 Subject: [PATCH 183/294] Potree2Reader: call HandeExtraBytes even if there are none --- src/PointCloud/Potree/V2/Potree2Reader.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 64a2f5e82..6e02396b1 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -188,16 +188,17 @@ private MemoryOwner LoadVisualizationPoint(PotreeNode node) var colorSpan = MemoryMarshal.Cast(color.ToArray()); uint flags = 0; + Span extraBytesSpan = null; if (PotreeData.Metadata.OffsetToExtraBytes != -1 && PotreeData.Metadata.OffsetToExtraBytes != 0) { var extraByteSize = PotreeData.Metadata.PointSize - PotreeData.Metadata.OffsetToExtraBytes; - var extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); - - if (HandleReadExtraBytes != null) - { - flags = HandleReadExtraBytes(extraBytesSpan); - } + extraBytesSpan = pointArray.AsSpan().Slice(i + PotreeData.Metadata.OffsetToExtraBytes, extraByteSize); } + if (HandleReadExtraBytes != null) + { + flags = HandleReadExtraBytes(extraBytesSpan); + } + var flagsSpan = MemoryMarshal.Cast(new uint[] { flags }); var currentMemoryPt = MemoryMarshal.Cast(returnMemory.Span.Slice(pointCount, 1)); From fc8d029952c6b617b655ee2a81d0dc6b9cc1aa87 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 2 May 2023 11:25:17 +0200 Subject: [PATCH 184/294] Cleanup and comments --- src/PointCloud/Common/IPointCloudImp.cs | 13 ++++++++++- .../Common/InvalidateGpuDataCache.cs | 6 +---- src/PointCloud/Core/PointCloudDataHandler.cs | 4 ++-- .../Core/PointCloudDataHandlerBase.cs | 3 +++ src/PointCloud/Potree/Potree2Cloud.cs | 9 ++++++-- src/PointCloud/Potree/Potree2CloudDynamic.cs | 23 +++++++++++-------- .../Potree/Potree2CloudInstanced.cs | 9 ++++++-- src/PointCloud/Potree/V2/Data/PotreeNode.cs | 1 - src/PointCloud/Potree/V2/Potree2AccessBase.cs | 1 - 9 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/PointCloud/Common/IPointCloudImp.cs b/src/PointCloud/Common/IPointCloudImp.cs index 0be6e7eeb..08b25d50c 100644 --- a/src/PointCloud/Common/IPointCloudImp.cs +++ b/src/PointCloud/Common/IPointCloudImp.cs @@ -1,5 +1,4 @@ using CommunityToolkit.HighPerformance.Buffers; -using Fusee.Engine.Common; using Fusee.Engine.Core; using Fusee.Math.Core; using System.Collections.Generic; @@ -67,6 +66,13 @@ public float UpdateRate public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 camPos, float4x4 modelMat); } + /// + /// Delegate that allows to inject a method that knows how to update gpu/mesh data with data from points. + /// + /// + /// + /// The gpu/mesh data. + /// The points with the desired values. public delegate void UpdateGpuData(ref IEnumerable gpuData, MemoryOwner points); /// @@ -79,6 +85,11 @@ public interface IPointCloudImp : IPointCloudImpBase /// public List GpuDataToRender { get; set; } + /// + /// Allows to update meshes with data from the points. + /// + /// The meshes that have to be updated. + /// The points with the desired values. public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points); } diff --git a/src/PointCloud/Common/InvalidateGpuDataCache.cs b/src/PointCloud/Common/InvalidateGpuDataCache.cs index 654d63126..fc7239ded 100644 --- a/src/PointCloud/Common/InvalidateGpuDataCache.cs +++ b/src/PointCloud/Common/InvalidateGpuDataCache.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Fusee.PointCloud.Common +namespace Fusee.PointCloud.Common { public class InvalidateGpuDataCache { diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index d517e262e..09974aa7a 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -120,7 +120,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin /// /// The unique id of an octant. /// - public override IEnumerable? GetGpuData(OctantId octantId) + public override IEnumerable GetGpuData(OctantId octantId) { if (_gpuDataCache.TryGetValue(octantId, out var gpuData)) { @@ -130,7 +130,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin { if (_pointCache.TryGetValue(octantId, out var points)) { - if (UpdateGpuDataCache != null) + if (UpdateGpuDataCache != null) { UpdateGpuDataCache.Invoke(ref gpuData, points); } diff --git a/src/PointCloud/Core/PointCloudDataHandlerBase.cs b/src/PointCloud/Core/PointCloudDataHandlerBase.cs index 3cd2ffc78..0c8673b7e 100644 --- a/src/PointCloud/Core/PointCloudDataHandlerBase.cs +++ b/src/PointCloud/Core/PointCloudDataHandlerBase.cs @@ -61,6 +61,9 @@ public abstract class PointCloudDataHandlerBase : IDisposable where TG /// public abstract void ProcessDisposeQueue(); + /// + /// Allows to update meshes with data from the points. + /// public UpdateGpuData, MemoryOwner>? UpdateGpuDataCache; private bool _disposed = false; diff --git a/src/PointCloud/Potree/Potree2Cloud.cs b/src/PointCloud/Potree/Potree2Cloud.cs index c92d3adae..1fa35c3e4 100644 --- a/src/PointCloud/Potree/Potree2Cloud.cs +++ b/src/PointCloud/Potree/Potree2Cloud.cs @@ -1,9 +1,9 @@ using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; -using System; using System.Collections.Generic; namespace Fusee.PointCloud.Potree @@ -102,9 +102,14 @@ public Potree2Cloud(PointCloudDataHandlerBase dataHandler, IPointCloudO _getMeshes = dataHandler.GetGpuData; } + /// + /// Allows to update meshes with data from the points. + /// + /// The meshes that have to be updated. + /// The points with the desired values. public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { - throw new NotImplementedException(); + Diagnostics.Warn("Not implemented. Cache will not be updated."); } /// diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index 7b4c28338..9b88d291d 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -3,8 +3,6 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; -using Fusee.PointCloud.Potree.V2.Data; -using SixLabors.ImageSharp.Processing; using System; using System.Collections.Generic; using System.Linq; @@ -90,8 +88,13 @@ public float UpdateRate /// public float3 Size => new((float)VisibilityTester.Octree.Root.Size); + /// + /// Action that is run on every mesh that is determined as newly visible. + /// + public Action? NewMeshAction; + private readonly GetDynamicMeshes _getMeshes; - private bool _doUpdate = true; + private bool _doUpdate = true; /// /// Creates a new instance of type @@ -107,17 +110,17 @@ public Potree2CloudDynamic(PointCloudDataHandlerBase dataHandler, IPointCl } /// - /// Action that is run on every mesh that is loaded to be visible. + /// Allows to update meshes with data from the points. /// - public Action NewMeshAction; - - + /// The meshes that have to be updated. + /// The points with the desired values. public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { var countStartSlice = 0; - + foreach (var mesh in meshes) { + if (mesh.Flags == null) continue; var slice = points.Span.Slice(countStartSlice, mesh.Flags.Length); for (int i = 0; i < slice.Length; i++) @@ -130,12 +133,12 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner - /// Determins if new Meshes should be loaded. + /// Determines if new Meshes should be loaded. /// public bool LoadNewMeshes { get; set; } = true; /// - /// Uses the and to update the visible meshes. + /// Uses the and to update the visible meshes. /// Called every frame. /// /// The camera's field of view. diff --git a/src/PointCloud/Potree/Potree2CloudInstanced.cs b/src/PointCloud/Potree/Potree2CloudInstanced.cs index acbb502a7..48cd5d1f8 100644 --- a/src/PointCloud/Potree/Potree2CloudInstanced.cs +++ b/src/PointCloud/Potree/Potree2CloudInstanced.cs @@ -1,9 +1,9 @@ using CommunityToolkit.HighPerformance.Buffers; +using Fusee.Base.Core; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; -using System; using System.Collections.Generic; namespace Fusee.PointCloud.Potree @@ -102,9 +102,14 @@ public Potree2CloudInstanced(PointCloudDataHandlerBase dataHandler _getInstanceData = dataHandler.GetGpuData; } + /// + /// Allows to update meshes with data from the points. + /// + /// The meshes that have to be updated. + /// The points with the desired values. public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner points) { - throw new NotImplementedException(); + Diagnostics.Warn("Not implemented. Cache will not be updated."); } /// diff --git a/src/PointCloud/Potree/V2/Data/PotreeNode.cs b/src/PointCloud/Potree/V2/Data/PotreeNode.cs index c5dc6d5d6..cda17dd87 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeNode.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeNode.cs @@ -3,7 +3,6 @@ using Fusee.PointCloud.Common; using Newtonsoft.Json; using System; -using System.Threading.Tasks.Sources; #pragma warning disable CS1591 diff --git a/src/PointCloud/Potree/V2/Potree2AccessBase.cs b/src/PointCloud/Potree/V2/Potree2AccessBase.cs index 63865a9ec..894d410f4 100644 --- a/src/PointCloud/Potree/V2/Potree2AccessBase.cs +++ b/src/PointCloud/Potree/V2/Potree2AccessBase.cs @@ -1,6 +1,5 @@ using CommunityToolkit.Diagnostics; using Fusee.PointCloud.Potree.V2.Data; -using System; namespace Fusee.PointCloud.Potree.V2 { From b58e03218a06f8f3a85ce1faa996c1ff0f2f7143 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 3 May 2023 08:56:27 +0200 Subject: [PATCH 185/294] MeshMaker does not set the mesh name / Potree2CloudDynamic UpdateGpuDataCache resets the mesh name --- src/PointCloud/Core/MeshMaker.cs | 3 --- src/PointCloud/Potree/Potree2CloudDynamic.cs | 5 ++++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PointCloud/Core/MeshMaker.cs b/src/PointCloud/Core/MeshMaker.cs index 9dc26d2c2..4324cba61 100644 --- a/src/PointCloud/Core/MeshMaker.cs +++ b/src/PointCloud/Core/MeshMaker.cs @@ -102,7 +102,6 @@ public static GpuMesh CreateStaticMesh(MemoryOwner points, O flags[i] = points.Span[i].Flags; } var mesh = ModuleExtensionPoint.CreateGpuMesh(PrimitiveType.Points, vertices, triangles, null, colors, null, null, null, null, null, null, null, flags); - mesh.Name = octantId.ToString(); mesh.BoundingBox = boundingBox; return mesh; } @@ -139,7 +138,6 @@ public static Mesh CreateDynamicMesh(MemoryOwner points, Oct return new Mesh(triangles, vertices, null, null, null, null, null, null, colors, null, null, flags) { - Name = OctantId.OctantIdToPotreeName(octantId), MeshType = PrimitiveType.Points }; } @@ -176,7 +174,6 @@ public static InstanceData CreateInstanceData(MemoryOwner po // TODO: Add flags to InstanceData return new InstanceData(points.Length, vertices, null, null, colors) { - Name = octantId.ToString() }; } diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index 9b88d291d..7615cd183 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -9,12 +9,14 @@ namespace Fusee.PointCloud.Potree { - /// /// Non-point-type-specific implementation of Potree2 clouds. /// public class Potree2CloudDynamic : IPointCloudImp { + /// + /// Object for handling the invalidation of the gpu data cache. + /// public InvalidateGpuDataCache InvalidateGpuDataCache { get; } = new(); /// @@ -120,6 +122,7 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner Date: Tue, 9 May 2023 15:25:48 +0200 Subject: [PATCH 186/294] Splitted FilePicker and FolderPicker in two seperate classes Closes #745 Closes #744 --- .../PointCloudPotree2/ImGui/ImGuiApp.cs | 6 +- .../Templates/ImGuiFilePicker.cs | 166 +++----- .../Templates/ImGuiFolderPicker.cs | 374 ++++++++++++++++++ 3 files changed, 440 insertions(+), 106 deletions(-) create mode 100644 src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs diff --git a/Examples/Complete/PointCloudPotree2/ImGui/ImGuiApp.cs b/Examples/Complete/PointCloudPotree2/ImGui/ImGuiApp.cs index b8f750998..4b8fedacd 100644 --- a/Examples/Complete/PointCloudPotree2/ImGui/ImGuiApp.cs +++ b/Examples/Complete/PointCloudPotree2/ImGui/ImGuiApp.cs @@ -57,12 +57,12 @@ public override async Task InitAsync() _fuControl.Init(); await base.InitAsync(); - _picker = new ImGuiFilePicker(Path.Combine(Environment.CurrentDirectory, ""), false, ".json"); + _picker = new ImGuiFilePicker(new DirectoryInfo(Environment.CurrentDirectory), ".json"); _picker.OnPicked += (s, file) => { - if (string.IsNullOrEmpty(file)) return; + if (file == null || !file.Exists) return; - PointRenderingParams.Instance.PathToOocFile = new FileInfo(file).Directory.FullName; + PointRenderingParams.Instance.PathToOocFile = file.DirectoryName; if (_fuControl != null) { diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 568232462..1a928f419 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -11,7 +11,7 @@ public class ImGuiFilePicker /// /// Invoked on clicked "open". /// - public EventHandler? OnPicked; + public EventHandler? OnPicked; /// /// Invoked on cancel. @@ -66,19 +66,18 @@ public class ImGuiFilePicker public string ParentFolderTxt = "Parent"; public string BackTxt = "Back"; - public bool OnlyAllowFolders; public List? AllowedExtensions; - public string? SelectedFile { get; protected set; } - public string RootFolder { get; protected set; } + public FileInfo? SelectedFile { get; protected set; } + public DirectoryInfo RootFolder { get; protected set; } public int FontSize; public ImFontPtr SymbolsFontPtr = null; - protected string CurrentOpenFolder; - protected readonly Stack LastOpenendFolders = new(); - protected string CurrentlySelectedFolder; - protected readonly string StartingFolder; + protected DirectoryInfo CurrentOpenFolder; + protected readonly Stack LastOpenendFolders = new(); + protected DirectoryInfo CurrentlySelectedFolder; + protected readonly DirectoryInfo StartingFolder; protected const float FolderTextInputWidth = 350; protected const float FileTextInputWidth = 300; @@ -152,28 +151,21 @@ public Vector4 WindowBackground /// /// Generate a new ImGuiFilePicker instance /// - /// Starting path, defaults to C:\ - /// Allow folder picking only + /// Starting path, defaults to /// search filter with dot. Example (".json") - public ImGuiFilePicker(string startingPath = "C:\\", bool onlyAllowFolders = true, string allowedExtensions = "") + public ImGuiFilePicker(DirectoryInfo? startingPath = null, string allowedExtensions = "") { _filePickerCount++; - if (File.Exists(startingPath)) + if (startingPath == null || !startingPath.Exists) { - startingPath = new FileInfo(startingPath)?.DirectoryName ?? "C:\\"; - } - else if (string.IsNullOrEmpty(startingPath) || !Directory.Exists(startingPath)) - { - startingPath = Environment.CurrentDirectory; - if (string.IsNullOrEmpty(startingPath)) - startingPath = AppContext.BaseDirectory; + startingPath = new DirectoryInfo(AppContext.BaseDirectory); } RootFolder = startingPath; CurrentOpenFolder = startingPath; StartingFolder = startingPath; - OnlyAllowFolders = onlyAllowFolders; + CurrentlySelectedFolder = startingPath; if (!string.IsNullOrEmpty(allowedExtensions)) { @@ -207,14 +199,13 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.PushFont(SymbolsFontPtr); ImGui.BeginGroup(); - var di = new DirectoryInfo(CurrentOpenFolder); if (ImGui.Button($"{ParentFolderTxt}##{_filePickerCount}", TopButtonSize)) { - if (di.Exists && di.Parent != null) + if (CurrentOpenFolder.Exists && CurrentOpenFolder.Parent != null) { LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = di.Parent.FullName; - SelectedFile = ""; + CurrentOpenFolder = CurrentOpenFolder.Parent; + SelectedFile = null; } } ImGui.SameLine(); @@ -225,11 +216,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) { var lastFolder = LastOpenendFolders.Pop(); - var lastDi = new DirectoryInfo(lastFolder); + var lastDi = lastFolder; if (lastDi.Exists) { CurrentOpenFolder = lastFolder; - SelectedFile = ""; + SelectedFile = null; } } } @@ -246,7 +237,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.EndGroup(); // Folder Selection - var currentFolder = CurrentOpenFolder; + var currentFolder = CurrentOpenFolder.FullName; ImGui.SameLine(DriveSelectionWidth + WindowPadding.X + ImGui.GetStyle().ItemSpacing.X); ImGui.SetNextItemWidth(FolderTextInputWidth - ImGui.CalcTextSize(FolderLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); ImGui.InputTextWithHint($"{FolderLabelTxt}##{_filePickerCount}", PathToFolderTxt, ref currentFolder, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => @@ -264,15 +255,15 @@ public virtual unsafe void Draw(ref bool filePickerOpen) }); if (Directory.Exists(currentFolder)) { - CurrentOpenFolder = currentFolder; + CurrentOpenFolder = new DirectoryInfo(currentFolder); } else if (File.Exists(currentFolder)) { var fi = new FileInfo(currentFolder); - if (fi.DirectoryName != null) + if (fi.Directory != null) { - CurrentOpenFolder = fi.DirectoryName; - SelectedFile = fi.Name; + CurrentOpenFolder = fi.Directory; + SelectedFile = fi; } } else @@ -298,10 +289,10 @@ public virtual unsafe void Draw(ref bool filePickerOpen) { if (ImGui.Selectable($"{drive.Name} {drive.DriveType}##{_filePickerCount}")) { - RootFolder = drive.Name; + RootFolder = new DirectoryInfo(drive.Name); LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = drive.Name; - SelectedFile = ""; + CurrentOpenFolder = new DirectoryInfo(drive.Name); + SelectedFile = null; } driveCount++; } @@ -311,28 +302,27 @@ public virtual unsafe void Draw(ref bool filePickerOpen) if (ImGui.BeginChild($"#FolderBrowser##{_filePickerCount}", new Vector2(FolderTextInputWidth, BrowserHeight), false, ImGuiWindowFlags.AlwaysUseWindowPadding | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.HorizontalScrollbar)) { - di = new DirectoryInfo(CurrentOpenFolder); - if (di.Exists) + if (CurrentOpenFolder != null && CurrentOpenFolder.Exists) { - var fileSystemEntries = GetFileSystemEntries(di.FullName); + var fileSystemEntries = GetFileSystemEntries(CurrentOpenFolder.FullName); foreach (var fse in fileSystemEntries) { - if (Directory.Exists(fse)) + if (Directory.Exists(fse.FullName)) { - var name = Path.GetFileName(fse); + var directory = new DirectoryInfo(fse.FullName); ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); - if (ImGui.Selectable(name + "/", CurrentlySelectedFolder == fse, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + if (ImGui.Selectable(directory.Name + "/", CurrentlySelectedFolder == directory, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { - SelectedFile = ""; - CurrentlySelectedFolder = fse; + SelectedFile = null; + CurrentlySelectedFolder = directory; if (ImGui.IsMouseDoubleClicked(0)) { - if ((SelectedFile == null || SelectedFile == "") && ImGui.GetIO().WantCaptureMouse) + if (SelectedFile == null && ImGui.GetIO().WantCaptureMouse) { LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = fse; + CurrentOpenFolder = directory; } } } @@ -341,28 +331,28 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } else { - var name = Path.GetFileName(fse); + var name = new FileInfo(fse.FullName); ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); - if (ImGui.Selectable(name, SelectedFile == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + if (ImGui.Selectable(name.Name, SelectedFile == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { if (ImGui.IsMouseDoubleClicked(0)) { - if ((SelectedFile != null && SelectedFile != "") && ImGui.GetIO().WantCaptureMouse) + if (SelectedFile != null && ImGui.GetIO().WantCaptureMouse) { if (HandlePickedFile(SelectedFile)) { filePickerOpen = false; - OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, SelectedFile)); + OnPicked?.Invoke(this, SelectedFile); } } } else { if (SelectedFile == name) - SelectedFile = ""; + SelectedFile = null; else SelectedFile = name; } @@ -382,12 +372,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.NewLine(); ImGui.BeginChild($"FileSelector##{_filePickerCount}", new Vector2(-1, -1), false, ImGuiWindowFlags.AlwaysAutoResize); - var selectedFile = !string.IsNullOrWhiteSpace(SelectedFile) ? SelectedFile : ""; + var selectedFile = SelectedFile?.Name ?? ""; ImGui.SetNextItemWidth(FileTextInputWidth - ImGui.CalcTextSize(FileLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); if (ImGui.InputTextWithHint(FileLabelTxt, FileInputHintTxt, ref selectedFile, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => { var arr = selectedFile.ToCharArray(); - if (x->SelectionStart < x->SelectionEnd && x->SelectionStart >= 0 && x->SelectionEnd <= arr.Length) { var selectedText = arr[x->SelectionStart..x->SelectionEnd]; @@ -397,24 +386,24 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - SelectedFile = selectedFile; + SelectedFile = new FileInfo(selectedFile); } if (_sizeOfInputText == Vector2.Zero) _sizeOfInputText = ImGui.GetItemRectSize(); var sameLineOffset = WinSize.X - WindowPadding.X - (BottomButtonSize.X * 2 + ImGui.GetStyle().ItemSpacing.X * 4); - if (!OnlyAllowFolders && !string.IsNullOrWhiteSpace(SelectedFile)) + if (SelectedFile != null) { - var fi = new FileInfo(Path.Combine(CurrentOpenFolder, SelectedFile)); + var fi = SelectedFile; if (AllowedExtensions != null && AllowedExtensions.Contains(fi.Extension)) { ImGui.SameLine(sameLineOffset); if (ImGui.Button($"{PickedFileTxt}##{_filePickerCount}", BottomButtonSize)) { - if (HandlePickedFile(selectedFile)) + if (HandlePickedFile(fi)) { - OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, selectedFile)); + OnPicked?.Invoke(this, fi); filePickerOpen = false; } } @@ -427,15 +416,6 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.EndDisabled(); } } - else if (OnlyAllowFolders && !string.IsNullOrWhiteSpace(CurrentlySelectedFolder) && string.IsNullOrWhiteSpace(SelectedFile)) - { - ImGui.SameLine(sameLineOffset); - if (ImGui.Button($"{PickedFileTxt}##{_filePickerCount}", BottomButtonSize)) - { - OnPicked?.Invoke(this, Path.Combine(CurrentOpenFolder, selectedFile)); - filePickerOpen = false; - } - } else { ImGui.SameLine(sameLineOffset); @@ -461,33 +441,27 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return; } - private List GetFileSystemEntries(string fullName) + private List GetFileSystemEntries(string fullName) { try { - var files = new List(); - var dirs = new List(); + var folders = new List(); + var files = new List(); - foreach (var fse in Directory.GetFileSystemEntries(fullName, "")) + foreach (var f in Directory.GetFileSystemEntries(fullName, "")) { - if (Directory.Exists(fse)) + if (Directory.Exists(f)) { - dirs.Add(fse); + folders.Add(new DirectoryInfo(f)); } else { - if (!OnlyAllowFolders) + var fse = new FileInfo(f); + if (AllowedExtensions != null) { - if (AllowedExtensions != null) - { - var ext = Path.GetExtension(fse); - if (AllowedExtensions.Contains(ext)) - files.Add(fse); - } - else - { + var ext = fse.Extension; + if (AllowedExtensions.Contains(ext)) files.Add(fse); - } } else { @@ -496,42 +470,28 @@ private List GetFileSystemEntries(string fullName) } } - var ret = new List(dirs); + var ret = new List(folders); ret.AddRange(files); - - return ret; } catch (Exception) { - return new List(); + return new List(); } } - protected virtual bool HandlePickedFile(string selectedFile) + protected virtual bool HandlePickedFile(FileInfo selectedFile) { - if (File.Exists(selectedFile)) + if (selectedFile.Exists) { - var fi = new FileInfo(selectedFile); - if (fi.DirectoryName != null) + if (selectedFile.Directory != null) { - CurrentOpenFolder = fi.DirectoryName; - SelectedFile = fi.Name; + CurrentOpenFolder = selectedFile.Directory; + SelectedFile = selectedFile; return true; } } - else if (File.Exists(Path.Combine(CurrentOpenFolder, selectedFile))) - { - SelectedFile = selectedFile; - return true; - } - else if (Directory.Exists(selectedFile)) - { - SelectedFile = ""; - CurrentOpenFolder = selectedFile; - return false; - } - else if (!string.IsNullOrWhiteSpace(selectedFile)) + else if (selectedFile != null) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); ImGui.BeginTooltip(); diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs new file mode 100644 index 000000000..0e7ffe957 --- /dev/null +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -0,0 +1,374 @@ +using ImGuiNET; +using System; +using System.Collections.Generic; +using System.IO; +using System.Numerics; + +namespace Fusee.ImGuiImp.Desktop.Templates +{ + public class ImGuiFolderPicker + { + /// + /// Invoked on clicked "open". + /// + public EventHandler? OnPicked; + + /// + /// Invoked on cancel. + /// + public EventHandler? OnCancel; + + /// + /// Title of window (visible in top bar). + /// + public string Id = "Open Folder"; + + /// + /// Caption of the "Open" button. + /// + public string PickedFileTxt = "Open"; + + /// + /// Caption of the "Cancel" button. + /// + public string CancelFileOpenTxt = "Cancel"; + + /// + /// Path to folder text. + /// + public string PathToFolderTxt = "Path to folder"; + + /// + /// Folder not found warning text. + /// + public string FolderNotFoundTxt = "Folder not found!"; + + /// + /// Caption of folder input text + /// + public string FolderLabelTxt = "Folder"; + + /// + /// Caption of file input text + /// + public string FileLabelTxt = "Folder"; + + public string ParentFolderTxt = "Parent"; + public string BackTxt = "Back"; + + public DirectoryInfo? SelectedFolder { get; protected set; } + public DirectoryInfo RootFolder { get; protected set; } + + public int FontSize; + public ImFontPtr SymbolsFontPtr = null; + + protected DirectoryInfo CurrentOpenFolder; + protected readonly Stack LastOpenendFolders = new(); + protected DirectoryInfo? CurrentlySelectedFolder; + protected readonly DirectoryInfo StartingFolder; + + protected const float FolderTextInputWidth = 350; + protected const float FileTextInputWidth = 300; + protected const float DriveSelectionWidth = 100; + protected const float BrowserHeight = 200; + protected readonly Vector2 WindowPadding = new(15, 15); + protected readonly Vector2 BottomButtonSize = new(55, 26); + protected readonly Vector2 TopButtonSize = new(35, 30); + protected Vector2 WinSize; + protected bool DoFocusPicker = true; + + private static int _folderPickerCount = 0; + + public bool IsOpen + { + get + { + return _isOpen; + } + set + { + if (value != _isOpen) + { + _isOpen = value; + if (!_isOpen) + CurrentOpenFolder = StartingFolder; + } + } + } + protected bool _isOpen; + + // needed for width calculation + protected Vector2 _sizeOfInputText; + + /// + /// Text color of folder + /// + public Vector4 FolderColor = new(255, 0, 255, 255); + + /// + /// Background color of pop up window + /// + public Vector4 WindowBackground + { + get => _windowBackground; + set + { + _windowBackground = value; + _windowBackgroundUint = _windowBackground.ToUintColor(); + } + } + private Vector4 _windowBackground = new(200, 200, 200, 255); + + public uint _windowBackgroundUint = new Vector4(200, 200, 200, 255).ToUintColor(); + + /// + /// Background of file selection menu + /// + public Vector4 FileSelectionMenuBackground = new(125, 125, 125, 255); + + /// + /// Color of when an error occurs + /// + public Vector4 WarningTextColor = new(200, 0, 0, 255); + + /// + /// Background color of one object + /// + public Vector4 SelectedColor = new(125, 75, 75, 255); + + /// + /// Generate a new ImGuiFolderPicker instance + /// + /// Starting path, defaults to + public ImGuiFolderPicker(DirectoryInfo? startingPath = null) + { + _folderPickerCount++; + + if (startingPath == null || !startingPath.Exists) + { + startingPath = new DirectoryInfo(AppContext.BaseDirectory); + } + + RootFolder = startingPath; + CurrentOpenFolder = startingPath; + StartingFolder = startingPath; + SelectedFolder = startingPath; + } + + public virtual unsafe void Draw(ref bool filePickerOpen) + { + IsOpen = filePickerOpen; + if (!filePickerOpen) return; + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, WindowPadding); + ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, 0); + ImGui.PushStyleColor(ImGuiCol.WindowBg, _windowBackgroundUint); + + if (DoFocusPicker) + ImGui.SetNextWindowFocus(); + var headerHeight = FontSize + WindowPadding.Y * 2; + var itemSpacing = ImGui.GetStyle().ItemSpacing; + WinSize = new Vector2(FolderTextInputWidth + DriveSelectionWidth + (WindowPadding.X * 2) + itemSpacing.X, headerHeight + BrowserHeight + TopButtonSize.Y + BottomButtonSize.Y + 4 * WindowPadding.Y + 3 * itemSpacing.Y + 5); + ImGui.SetNextWindowSize(WinSize); + ImGui.Begin(Id, ref filePickerOpen, ImGuiWindowFlags.Modal | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDocking); + + if ((IntPtr)SymbolsFontPtr.NativePtr != IntPtr.Zero) + ImGui.PushFont(SymbolsFontPtr); + + ImGui.BeginGroup(); + if (ImGui.Button($"{ParentFolderTxt}##{_folderPickerCount}", TopButtonSize)) + { + if (CurrentOpenFolder.Exists && CurrentOpenFolder.Parent != null) + { + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = CurrentOpenFolder.Parent; + } + } + ImGui.SameLine(); + + if (LastOpenendFolders.Count != 0) + { + if (ImGui.Button($"{BackTxt}##{_folderPickerCount}", TopButtonSize)) + { + + var lastFolder = LastOpenendFolders.Pop(); + if (lastFolder.Exists) + { + CurrentOpenFolder = lastFolder; + } + } + } + else + { + ImGui.BeginDisabled(); + ImGui.Button($"{BackTxt}##{_folderPickerCount}", TopButtonSize); + ImGui.EndDisabled(); + } + + if ((IntPtr)SymbolsFontPtr.NativePtr != IntPtr.Zero) + ImGui.PopFont(); + + ImGui.EndGroup(); + + // Folder Selection + var currentFolder = CurrentOpenFolder.FullName; + ImGui.SameLine(DriveSelectionWidth + WindowPadding.X + ImGui.GetStyle().ItemSpacing.X); + ImGui.SetNextItemWidth(FolderTextInputWidth - ImGui.CalcTextSize(FolderLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); + ImGui.InputTextWithHint($"{FolderLabelTxt}##{_folderPickerCount}", PathToFolderTxt, ref currentFolder, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => + { + var arr = currentFolder.ToCharArray(); + + if (x->SelectionStart < x->SelectionEnd && x->SelectionStart >= 0 && x->SelectionEnd <= arr.Length) + { + var selectedText = arr[x->SelectionStart..x->SelectionEnd]; + if (selectedText != null) + ImGuiInputImp.CurrentlySelectedText = new string(selectedText); + } + + return 0; + }); + if (Directory.Exists(currentFolder)) + { + CurrentOpenFolder = new DirectoryInfo(currentFolder); + CurrentlySelectedFolder = new DirectoryInfo(currentFolder); + } + else + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); + ImGui.BeginTooltip(); + ImGui.TextColored(WarningTextColor, FolderNotFoundTxt); + ImGui.EndTooltip(); + ImGui.PopStyleVar(); + } + + // Folder Browser + ImGui.NewLine(); + ImGui.PushStyleColor(ImGuiCol.ChildBg, FileSelectionMenuBackground.ToUintColor()); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(10, 10)); + + ImGui.BeginChild($"DriveSelection##{_folderPickerCount}", new Vector2(DriveSelectionWidth, BrowserHeight), false, ImGuiWindowFlags.AlwaysUseWindowPadding | ImGuiWindowFlags.AlwaysAutoResize); + // Drive Selection + var driveCount = 0; + foreach (var drive in DriveInfo.GetDrives()) + { + if (drive.IsReady) + { + if (ImGui.Selectable($"{drive.Name} {drive.DriveType}##{_folderPickerCount}")) + { + RootFolder = new DirectoryInfo(drive.Name); + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = new DirectoryInfo(drive.Name); + } + driveCount++; + } + } + ImGui.EndChild(); + ImGui.SameLine(); + + if (ImGui.BeginChild($"#FolderBrowser##{_folderPickerCount}", new Vector2(FolderTextInputWidth, BrowserHeight), false, ImGuiWindowFlags.AlwaysUseWindowPadding | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.HorizontalScrollbar)) + { + + var fileSystemEntries = GetFileSystemEntries(CurrentOpenFolder.FullName); + foreach (var fse in fileSystemEntries) + { + + var name = fse.Name; + + ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); + ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); + + if (ImGui.Selectable(name + "/", CurrentlySelectedFolder?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + { + CurrentlySelectedFolder = fse; + if (ImGui.IsMouseDoubleClicked(0)) + { + if (ImGui.GetIO().WantCaptureMouse) + { + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = fse; + } + } + } + + ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + } + + + ImGui.PopStyleColor(); + ImGui.PopStyleVar(); + ImGui.EndChild(); + } + + //// Folder Selector + ImGui.NewLine(); + ImGui.BeginChild($"FileSelector##{_folderPickerCount}", new Vector2(-1, -1), false, ImGuiWindowFlags.AlwaysAutoResize); + + var selectedFile = CurrentlySelectedFolder?.Name; + ImGui.SetNextItemWidth(FileTextInputWidth - ImGui.CalcTextSize(FileLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); + ImGui.Dummy(new Vector2(-1, -1)); + + var sameLineOffset = WinSize.X - WindowPadding.X - (BottomButtonSize.X * 2 + ImGui.GetStyle().ItemSpacing.X * 4); + + if (CurrentlySelectedFolder != null && CurrentlySelectedFolder.Exists) + { + ImGui.SameLine(sameLineOffset); + if (ImGui.Button($"{PickedFileTxt}##{_folderPickerCount}", BottomButtonSize)) + { + if (CurrentlySelectedFolder != null) + OnPicked?.Invoke(this, CurrentlySelectedFolder); + else + OnPicked?.Invoke(this, CurrentOpenFolder); + filePickerOpen = false; + } + } + else + { + ImGui.SameLine(sameLineOffset); + ImGui.BeginDisabled(); + ImGui.Button(PickedFileTxt, BottomButtonSize); + ImGui.EndDisabled(); + } + + ImGui.SameLine(); + if (ImGui.Button($"{CancelFileOpenTxt}##{_folderPickerCount}", BottomButtonSize)) + { + OnCancel?.Invoke(this, EventArgs.Empty); + filePickerOpen = false; + } + + ImGui.EndChild(); + + ImGui.End(); + + ImGui.PopStyleVar(2); + ImGui.PopStyleColor(); + + return; + } + + private static List GetFileSystemEntries(string fullName) + { + try + { + var dirs = new List(); + + foreach (var f in Directory.GetFileSystemEntries(fullName, "")) + { + var fse = new DirectoryInfo(f); + if (fse.Exists) + { + dirs.Add(fse); + } + } + + + return dirs; + } + catch (Exception) + { + return new List(); + } + } + } +} \ No newline at end of file From a835368fe2c67cae98018a5bba507433e5c58891 Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Tue, 9 May 2023 13:44:04 +0000 Subject: [PATCH 187/294] Linting --- src/Engine/Core/Scene/ChildList.cs | 2 +- .../Templates/ImGuiFolderPicker.cs | 26 +++++++++---------- .../Common/InvalidateGpuDataCache.cs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Engine/Core/Scene/ChildList.cs b/src/Engine/Core/Scene/ChildList.cs index ea45aa706..4ee918059 100644 --- a/src/Engine/Core/Scene/ChildList.cs +++ b/src/Engine/Core/Scene/ChildList.cs @@ -75,7 +75,7 @@ private void AddSceneNode(SceneNode snc) else { //remove from old parent's child list - if(this != snc.Parent.Children) + if (this != snc.Parent.Children) snc.Parent.Children.Remove(snc); OnAdd?.Invoke(this, new AddChildEventArgs(snc)); } diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index 0e7ffe957..0f3583c7f 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -272,26 +272,26 @@ public virtual unsafe void Draw(ref bool filePickerOpen) foreach (var fse in fileSystemEntries) { - var name = fse.Name; + var name = fse.Name; - ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); - ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); + ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); + ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); - if (ImGui.Selectable(name + "/", CurrentlySelectedFolder?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + if (ImGui.Selectable(name + "/", CurrentlySelectedFolder?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + { + CurrentlySelectedFolder = fse; + if (ImGui.IsMouseDoubleClicked(0)) { - CurrentlySelectedFolder = fse; - if (ImGui.IsMouseDoubleClicked(0)) + if (ImGui.GetIO().WantCaptureMouse) { - if (ImGui.GetIO().WantCaptureMouse) - { - LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = fse; - } + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = fse; } } + } - ImGui.PopStyleColor(); - ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + ImGui.PopStyleColor(); } diff --git a/src/PointCloud/Common/InvalidateGpuDataCache.cs b/src/PointCloud/Common/InvalidateGpuDataCache.cs index fc7239ded..fc483991a 100644 --- a/src/PointCloud/Common/InvalidateGpuDataCache.cs +++ b/src/PointCloud/Common/InvalidateGpuDataCache.cs @@ -4,4 +4,4 @@ public class InvalidateGpuDataCache { public bool IsDirty; } -} +} \ No newline at end of file From 5d9c236d239a894356f633b99befe07a074e4220 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 9 May 2023 17:19:45 +0200 Subject: [PATCH 188/294] Fixed file/folder picking --- .../Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 9 ++++----- .../Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs | 4 +--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 1a928f419..9d0e014cd 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -331,12 +331,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } else { - var name = new FileInfo(fse.FullName); - + var name = fse.Name; ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); - if (ImGui.Selectable(name.Name, SelectedFile == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + if (ImGui.Selectable(name, SelectedFile?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { if (ImGui.IsMouseDoubleClicked(0)) { @@ -351,10 +350,10 @@ public virtual unsafe void Draw(ref bool filePickerOpen) } else { - if (SelectedFile == name) + if (SelectedFile == fse) SelectedFile = null; else - SelectedFile = name; + SelectedFile = new FileInfo(fse.FullName); } } diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index 0f3583c7f..c0f5d4045 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using ImGuiNET; using System; using System.Collections.Generic; using System.IO; @@ -230,7 +230,6 @@ public virtual unsafe void Draw(ref bool filePickerOpen) if (Directory.Exists(currentFolder)) { CurrentOpenFolder = new DirectoryInfo(currentFolder); - CurrentlySelectedFolder = new DirectoryInfo(currentFolder); } else { @@ -271,7 +270,6 @@ public virtual unsafe void Draw(ref bool filePickerOpen) var fileSystemEntries = GetFileSystemEntries(CurrentOpenFolder.FullName); foreach (var fse in fileSystemEntries) { - var name = fse.Name; ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); From 3c96f6967c133ee60f78d72fa5fe8a9d765c1e63 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 10 May 2023 11:26:46 +0200 Subject: [PATCH 189/294] Remove test code --- .../PointCloudPotree2/Core/PointCloudPotree2Core.cs | 7 +------ src/PointCloud/Potree/Potree2LAS.cs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 26bf6c6b9..8412f08db 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -102,13 +102,8 @@ public void OnLoadNewFile(object sender, EventArgs e) public PointCloudPotree2Core(RenderContext rc) { - _potreeReader = new Potree2Reader(Path.Combine("D:\\Halle2__AG_08\\plp_tmp_Halle2__AG_08")); + _potreeReader = new Potree2Reader(Path.Combine(AssetsPath, PointRenderingParams.Instance.PathToOocFile)); _potreeData = _potreeReader.PotreeData; - var sw = new Stopwatch(); - sw.Start(); - using var laswriter = new Potree2LAS(new FileInfo("D:\\test\\test.las"), _potreeData, LASPointType.Seven); - laswriter.Write(); - Console.WriteLine($"{sw.Elapsed} ready"); _rc = rc; } diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6917872b5..116c3a000 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -333,7 +333,7 @@ private void ParseAndFillHeader() "uint32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (ulong)x).ToArray()), "int32" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (long)x).ToArray()), "int64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (long)x).ToArray()), - "uint64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (UInt64)x).ToArray()), + "uint64" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (ulong)x).ToArray()), "float" => MemoryMarshal.AsBytes(attribute.MinList.Select(x => (float)x).ToArray()), "double" => MemoryMarshal.AsBytes(attribute.MinList.ToArray()), _ => throw new ArgumentException("Invalid data type!") From d216fe4e60608dfc63dab5c3c7f5c22baf1f4285 Mon Sep 17 00:00:00 2001 From: wrestledBearOnce Date: Wed, 10 May 2023 09:33:31 +0000 Subject: [PATCH 190/294] Linting --- src/Engine/Core/Scene/ChildList.cs | 2 +- src/PointCloud/Common/InvalidateGpuDataCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engine/Core/Scene/ChildList.cs b/src/Engine/Core/Scene/ChildList.cs index ea45aa706..4ee918059 100644 --- a/src/Engine/Core/Scene/ChildList.cs +++ b/src/Engine/Core/Scene/ChildList.cs @@ -75,7 +75,7 @@ private void AddSceneNode(SceneNode snc) else { //remove from old parent's child list - if(this != snc.Parent.Children) + if (this != snc.Parent.Children) snc.Parent.Children.Remove(snc); OnAdd?.Invoke(this, new AddChildEventArgs(snc)); } diff --git a/src/PointCloud/Common/InvalidateGpuDataCache.cs b/src/PointCloud/Common/InvalidateGpuDataCache.cs index fc7239ded..fc483991a 100644 --- a/src/PointCloud/Common/InvalidateGpuDataCache.cs +++ b/src/PointCloud/Common/InvalidateGpuDataCache.cs @@ -4,4 +4,4 @@ public class InvalidateGpuDataCache { public bool IsDirty; } -} +} \ No newline at end of file From 9fc04130659a9fb18b88d035baadb3af0526f1e0 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 10 May 2023 13:51:28 +0200 Subject: [PATCH 191/294] Changes after PR review --- .../Templates/ImGuiFilePicker.cs | 35 ++++----- .../Templates/ImGuiFolderPicker.cs | 76 ++++++++++++++----- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 9d0e014cd..b8584c52e 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -253,20 +253,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; }); - if (Directory.Exists(currentFolder)) - { - CurrentOpenFolder = new DirectoryInfo(currentFolder); - } - else if (File.Exists(currentFolder)) - { - var fi = new FileInfo(currentFolder); - if (fi.Directory != null) - { - CurrentOpenFolder = fi.Directory; - SelectedFile = fi; - } - } - else + if (!Directory.Exists(currentFolder)) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); ImGui.BeginTooltip(); @@ -307,7 +294,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) var fileSystemEntries = GetFileSystemEntries(CurrentOpenFolder.FullName); foreach (var fse in fileSystemEntries) { - if (Directory.Exists(fse.FullName)) + if (fse.Attributes.HasFlag(FileAttributes.Directory)) { var directory = new DirectoryInfo(fse.FullName); @@ -440,16 +427,30 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return; } + /// + /// We differentiate between files and folders, as we want to print the folders first + /// If we collect everything in one list all files and folders are being sorted alphabetically + /// + /// + /// private List GetFileSystemEntries(string fullName) { try { var folders = new List(); - var files = new List(); + var files = new List(); foreach (var f in Directory.GetFileSystemEntries(fullName, "")) { - if (Directory.Exists(f)) + var attr = File.GetAttributes(f); + // skip unaccessible files and folders + if (attr.HasFlag(FileAttributes.Encrypted) + || attr.HasFlag(FileAttributes.Hidden) + || attr.HasFlag(FileAttributes.System) + || attr.HasFlag(FileAttributes.Temporary)) + continue; + + if (attr.HasFlag(FileAttributes.Directory)) { folders.Add(new DirectoryInfo(f)); } diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index c0f5d4045..6abde3c87 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -136,6 +136,12 @@ public Vector4 WindowBackground /// public Vector4 SelectedColor = new(125, 75, 75, 255); + /// + /// Color of one file object + /// This should be a lighter color, as these elements are being printed, but are not selectable + /// + public Vector4 LightFileColor = new(125, 125, 125, 255); + /// /// Generate a new ImGuiFolderPicker instance /// @@ -266,30 +272,40 @@ public virtual unsafe void Draw(ref bool filePickerOpen) if (ImGui.BeginChild($"#FolderBrowser##{_folderPickerCount}", new Vector2(FolderTextInputWidth, BrowserHeight), false, ImGuiWindowFlags.AlwaysUseWindowPadding | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.HorizontalScrollbar)) { - var fileSystemEntries = GetFileSystemEntries(CurrentOpenFolder.FullName); foreach (var fse in fileSystemEntries) { var name = fse.Name; - ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); - ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); - if (ImGui.Selectable(name + "/", CurrentlySelectedFolder?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) + if (fse.Attributes.HasFlag(FileAttributes.Directory)) { - CurrentlySelectedFolder = fse; - if (ImGui.IsMouseDoubleClicked(0)) + ImGui.PushStyleColor(ImGuiCol.Text, FolderColor.ToUintColor()); + ImGui.PushStyleColor(ImGuiCol.Header, SelectedColor.ToUintColor()); + if (ImGui.Selectable(name + "/", CurrentlySelectedFolder?.Name == name, ImGuiSelectableFlags.DontClosePopups | ImGuiSelectableFlags.AllowDoubleClick)) { - if (ImGui.GetIO().WantCaptureMouse) + CurrentlySelectedFolder = new DirectoryInfo(fse.FullName); + if (ImGui.IsMouseDoubleClicked(0)) { - LastOpenendFolders.Push(CurrentOpenFolder); - CurrentOpenFolder = fse; + if (ImGui.GetIO().WantCaptureMouse) + { + LastOpenendFolders.Push(CurrentOpenFolder); + CurrentOpenFolder = new DirectoryInfo(fse.FullName); + } } } + ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + } + else + { + // just print the files, but with lighter color + ImGui.PushStyleColor(ImGuiCol.Text, LightFileColor.ToUintColor()); + ImGui.Selectable(name, false, ImGuiSelectableFlags.DontClosePopups); + ImGui.PopStyleColor(); } - ImGui.PopStyleColor(); - ImGui.PopStyleColor(); + } @@ -300,7 +316,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) //// Folder Selector ImGui.NewLine(); - ImGui.BeginChild($"FileSelector##{_folderPickerCount}", new Vector2(-1, -1), false, ImGuiWindowFlags.AlwaysAutoResize); + ImGui.BeginChild($"FolderSelector##{_folderPickerCount}", new Vector2(-1, -1), false, ImGuiWindowFlags.AlwaysAutoResize); var selectedFile = CurrentlySelectedFolder?.Name; ImGui.SetNextItemWidth(FileTextInputWidth - ImGui.CalcTextSize(FileLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); @@ -345,27 +361,47 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return; } - private static List GetFileSystemEntries(string fullName) + /// + /// We differentiate between files and folders, as we want to print the folders first + /// If we collect everything in one list all files and folders are being sorted alphabetically + /// + /// + /// + private List GetFileSystemEntries(string fullName) { try { - var dirs = new List(); + var folders = new List(); + var files = new List(); foreach (var f in Directory.GetFileSystemEntries(fullName, "")) { - var fse = new DirectoryInfo(f); - if (fse.Exists) + var attr = File.GetAttributes(f); + // skip unaccessible files and folders + if (attr.HasFlag(FileAttributes.Encrypted) + || attr.HasFlag(FileAttributes.Hidden) + || attr.HasFlag(FileAttributes.System) + || attr.HasFlag(FileAttributes.Temporary)) + continue; + + if (attr.HasFlag(FileAttributes.Directory)) { - dirs.Add(fse); + folders.Add(new DirectoryInfo(f)); + } + else + { + var fse = new FileInfo(f); + files.Add(fse); } } - - return dirs; + var ret = new List(folders); + ret.AddRange(files); + return ret; } catch (Exception) { - return new List(); + return new List(); } } } From 4dbf5a50abeb27d1b45dac8ef9b3a7c6c25bb3d0 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 10 May 2023 14:04:57 +0200 Subject: [PATCH 192/294] Removed try/catch --- .../Templates/ImGuiFilePicker.cs | 61 ++++++++----------- .../Templates/ImGuiFolderPicker.cs | 56 ++++++++--------- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index b8584c52e..fc3d02610 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -435,49 +435,42 @@ public virtual unsafe void Draw(ref bool filePickerOpen) /// private List GetFileSystemEntries(string fullName) { - try - { - var folders = new List(); - var files = new List(); + var folders = new List(); + var files = new List(); - foreach (var f in Directory.GetFileSystemEntries(fullName, "")) + foreach (var f in Directory.GetFileSystemEntries(fullName, "")) + { + var attr = File.GetAttributes(f); + // skip unaccessible files and folders + if (attr.HasFlag(FileAttributes.Encrypted) + || attr.HasFlag(FileAttributes.Hidden) + || attr.HasFlag(FileAttributes.System) + || attr.HasFlag(FileAttributes.Temporary)) + continue; + + if (attr.HasFlag(FileAttributes.Directory)) + { + folders.Add(new DirectoryInfo(f)); + } + else { - var attr = File.GetAttributes(f); - // skip unaccessible files and folders - if (attr.HasFlag(FileAttributes.Encrypted) - || attr.HasFlag(FileAttributes.Hidden) - || attr.HasFlag(FileAttributes.System) - || attr.HasFlag(FileAttributes.Temporary)) - continue; - - if (attr.HasFlag(FileAttributes.Directory)) + var fse = new FileInfo(f); + if (AllowedExtensions != null) { - folders.Add(new DirectoryInfo(f)); + var ext = fse.Extension; + if (AllowedExtensions.Contains(ext)) + files.Add(fse); } else { - var fse = new FileInfo(f); - if (AllowedExtensions != null) - { - var ext = fse.Extension; - if (AllowedExtensions.Contains(ext)) - files.Add(fse); - } - else - { - files.Add(fse); - } + files.Add(fse); } } - - var ret = new List(folders); - ret.AddRange(files); - return ret; - } - catch (Exception) - { - return new List(); } + + var ret = new List(folders); + ret.AddRange(files); + return ret; } protected virtual bool HandlePickedFile(FileInfo selectedFile) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index 6abde3c87..ab90a1ffa 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -367,42 +367,36 @@ public virtual unsafe void Draw(ref bool filePickerOpen) /// /// /// - private List GetFileSystemEntries(string fullName) + private static List GetFileSystemEntries(string fullName) { - try - { - var folders = new List(); - var files = new List(); + var folders = new List(); + var files = new List(); - foreach (var f in Directory.GetFileSystemEntries(fullName, "")) + foreach (var f in Directory.GetFileSystemEntries(fullName, "")) + { + var attr = File.GetAttributes(f); + // skip unaccessible files and folders + if (attr.HasFlag(FileAttributes.Encrypted) + || attr.HasFlag(FileAttributes.Hidden) + || attr.HasFlag(FileAttributes.System) + || attr.HasFlag(FileAttributes.Temporary)) + continue; + + if (attr.HasFlag(FileAttributes.Directory)) { - var attr = File.GetAttributes(f); - // skip unaccessible files and folders - if (attr.HasFlag(FileAttributes.Encrypted) - || attr.HasFlag(FileAttributes.Hidden) - || attr.HasFlag(FileAttributes.System) - || attr.HasFlag(FileAttributes.Temporary)) - continue; - - if (attr.HasFlag(FileAttributes.Directory)) - { - folders.Add(new DirectoryInfo(f)); - } - else - { - var fse = new FileInfo(f); - files.Add(fse); - } + folders.Add(new DirectoryInfo(f)); + } + else + { + var fse = new FileInfo(f); + files.Add(fse); } - - var ret = new List(folders); - ret.AddRange(files); - return ret; - } - catch (Exception) - { - return new List(); } + + var ret = new List(folders); + ret.AddRange(files); + return ret; + } } } \ No newline at end of file From e43034002d4959a78b113d72d9af770076706379 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 10 May 2023 14:11:11 +0200 Subject: [PATCH 193/294] Removed uneccessary variable --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index ab90a1ffa..a308d961b 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -317,8 +317,6 @@ public virtual unsafe void Draw(ref bool filePickerOpen) //// Folder Selector ImGui.NewLine(); ImGui.BeginChild($"FolderSelector##{_folderPickerCount}", new Vector2(-1, -1), false, ImGuiWindowFlags.AlwaysAutoResize); - - var selectedFile = CurrentlySelectedFolder?.Name; ImGui.SetNextItemWidth(FileTextInputWidth - ImGui.CalcTextSize(FileLabelTxt).X - ImGui.GetStyle().ItemSpacing.X); ImGui.Dummy(new Vector2(-1, -1)); From afd94b1bbbe755cb716a3e966a831df1aa448da7 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 12 May 2023 10:38:25 +0200 Subject: [PATCH 194/294] DataHandler internally resets InvalidateGpuDataCache.IsDirty --- src/Base/Core/MemoryCache.cs | 105 +++++++++++++----- .../Common/Fusee.PointCloud.Common.csproj | 1 + .../Common/InvalidateGpuDataCache.cs | 28 ++++- src/PointCloud/Common/PointRenderEnums.cs | 27 +++++ src/PointCloud/Core/PointCloudDataHandler.cs | 79 +++++++++---- .../Core/PointCloudDataHandlerBase.cs | 17 ++- .../Core/Scene/PointCloudComponent.cs | 2 +- .../Core/Scene/PointCloudPickerModule.cs | 4 +- .../Core/Scene/PointCloudRenderModule.cs | 2 + src/PointCloud/Potree/Potree2Cloud.cs | 7 +- src/PointCloud/Potree/Potree2CloudDynamic.cs | 69 +++++++----- .../Potree/Potree2CloudInstanced.cs | 7 +- 12 files changed, 253 insertions(+), 95 deletions(-) diff --git a/src/Base/Core/MemoryCache.cs b/src/Base/Core/MemoryCache.cs index 262f9d77c..f1171052a 100644 --- a/src/Base/Core/MemoryCache.cs +++ b/src/Base/Core/MemoryCache.cs @@ -1,8 +1,75 @@ using Microsoft.Extensions.Caching.Memory; using System; +using System.Collections.Generic; +using System.Reflection.Emit; +using System.Reflection; +using System.Collections; +using System.Linq; namespace Fusee.Base.Core { + /// + /// Extensions for Microsoft.Extensions.Caching.Memory MemoryCache + /// Source: https://stackoverflow.com/questions/45597057/how-to-retrieve-a-list-of-memory-cache-keys-in-asp-net-core + /// + public static class MemoryCacheExtensions + { + #region Microsoft.Extensions.Caching.Memory_6_OR_OLDER + + private static readonly Lazy> GetEntries6 = + new Lazy>(() => (Func)Delegate.CreateDelegate( + typeof(Func), + typeof(MemoryCache).GetProperty("EntriesCollection", BindingFlags.NonPublic | BindingFlags.Instance).GetGetMethod(true), + throwOnBindFailure: true)); + + #endregion + + #region Microsoft.Extensions.Caching.Memory_7_OR_NEWER + + private static readonly Lazy> GetCoherentState = + new Lazy>(() => + CreateGetter(typeof(MemoryCache) + .GetField("_coherentState", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static readonly Lazy> GetEntries7 = + new Lazy>(() => + CreateGetter(typeof(MemoryCache) + .GetNestedType("CoherentState", BindingFlags.NonPublic) + .GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance))); + + private static Func CreateGetter(FieldInfo field) + { + var methodName = $"{field.ReflectedType.FullName}.get_{field.Name}"; + var method = new DynamicMethod(methodName, typeof(TReturn), new[] { typeof(TParam) }, typeof(TParam), true); + var ilGen = method.GetILGenerator(); + ilGen.Emit(OpCodes.Ldarg_0); + ilGen.Emit(OpCodes.Ldfld, field); + ilGen.Emit(OpCodes.Ret); + return (Func)method.CreateDelegate(typeof(Func)); + } + + #endregion + + private static readonly Func GetEntries = + Assembly.GetAssembly(typeof(MemoryCache)).GetName().Version.Major < 7 + ? (cache => (IDictionary)GetEntries6.Value(cache)) + : cache => GetEntries7.Value(GetCoherentState.Value(cache)); + + /// + /// Returns all currently cached keys as . + /// + /// The source cache. + public static ICollection GetKeys(this IMemoryCache memoryCache) => + GetEntries((MemoryCache)memoryCache).Keys; + + /// + /// Returns all currently cached keys as . + /// + /// The source cache. + public static IEnumerable GetKeys(this IMemoryCache memoryCache) => + memoryCache.GetKeys().OfType(); + } + /// /// Generic implementation of . /// The type of the key. @@ -10,6 +77,16 @@ namespace Fusee.Base.Core /// public class MemoryCache : IDisposable { + /// + /// Snapshot of the caches keys as . + /// + public IEnumerable GetKeys => _cache.GetKeys(); + + /// + /// Gets the number of items in the cache for diagnostic purposes. + /// + public int Count => _cache.Count; + /// /// Sets how long a cache entry can be inactive (not accessed) before it will be removed. /// @@ -54,11 +131,11 @@ public bool TryGetValue(TKey key, out TItem item) } /// - /// Adds the given item to the cache. Will not check if the item is already in the cache! + /// Adds the given item to the cache or updates it if the item is already present. /// /// The key of the cache item. /// The cache item. - public void Add(TKey key, TItem cacheEntry) + public void AddOrUpdate(TKey key, TItem cacheEntry) { var cacheEntryOptions = new MemoryCacheEntryOptions() .SetPriority(CacheItemPriority.High) @@ -74,30 +151,6 @@ public void Add(TKey key, TItem cacheEntry) _cache.Set(key, cacheEntry, cacheEntryOptions); } - /// - /// If the item isn't in the cache, add it, otherwise override the value in the cache. - /// - /// The key of the cache item. - /// The cache item. - public void AddOrUpdate(TKey key, TItem cacheEntry) - { - if (!_cache.TryGetValue(key, out cacheEntry)) - { - var cacheEntryOptions = new MemoryCacheEntryOptions() - .SetPriority(CacheItemPriority.High) - // Keep in cache for this time, reset time if accessed. - .SetSlidingExpiration(TimeSpan.FromSeconds(SlidingExpiration)); - - cacheEntryOptions.RegisterPostEvictionCallback((subkey, subValue, reason, state) => - { - HandleEvictedItem?.Invoke(subkey, subValue, reason, state); - }); - - // Key not in cache, so get data. - _cache.Set(key, cacheEntry, cacheEntryOptions); - } - } - /// /// Implement IDisposable. /// Do not make this method virtual. diff --git a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj index d67dfe2cf..7a43e2772 100644 --- a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj +++ b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj @@ -3,6 +3,7 @@ netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml + enable diff --git a/src/PointCloud/Common/InvalidateGpuDataCache.cs b/src/PointCloud/Common/InvalidateGpuDataCache.cs index fc483991a..66316fdd5 100644 --- a/src/PointCloud/Common/InvalidateGpuDataCache.cs +++ b/src/PointCloud/Common/InvalidateGpuDataCache.cs @@ -1,7 +1,31 @@ -namespace Fusee.PointCloud.Common +using System; + +namespace Fusee.PointCloud.Common { + /// + /// Token for invalidating the cached gpu data . + /// public class InvalidateGpuDataCache { - public bool IsDirty; + /// + /// Called when the value of changes. + /// + public Action? IsDirtyPropertyChanged; + + /// + /// Set this to true if the Data Handler should invalidate the gpu data cache. + /// Is set to false internally. + /// + public bool IsDirty + { + get => _isDirty; + set + { + _isDirty = value; + IsDirtyPropertyChanged?.Invoke(_isDirty); + } + + } + private bool _isDirty; } } \ No newline at end of file diff --git a/src/PointCloud/Common/PointRenderEnums.cs b/src/PointCloud/Common/PointRenderEnums.cs index 33f0954e1..b21ab691f 100644 --- a/src/PointCloud/Common/PointRenderEnums.cs +++ b/src/PointCloud/Common/PointRenderEnums.cs @@ -1,5 +1,32 @@ namespace Fusee.PointCloud.Common { + /// + /// The gpu data can take different states in its life cycle. + /// Gpu data may need to be handled differently according to its current state. + /// + public enum GpuDataState + { + /// + /// Default, gpu data wasn't created yet. + /// + None = -1, + + /// + /// Gpu data was newly created. + /// + New = 0, + + /// + /// Gpu data accessed but hasn't changed. + /// + Unchanged = 1, + + /// + /// Gpu data accessed and has changed. For example if a property of the data was updated. + /// + Changed = 2, + } + /// /// Available render modes. /// diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 09974aa7a..79f7d586c 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -1,4 +1,3 @@ -using CommunityToolkit.Diagnostics; using CommunityToolkit.HighPerformance.Buffers; using Fusee.Base.Core; using Fusee.Engine.Core; @@ -60,6 +59,8 @@ namespace Fusee.PointCloud.Core /// Generic for the point/mesh type. public class PointCloudDataHandler : PointCloudDataHandlerBase, IDisposable where TGpuData : IDisposable { + private HashSet _meshesToUpdate = new(); + /// /// Caches loaded points. /// @@ -111,6 +112,40 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin } }; + InvalidateCacheToken.IsDirtyPropertyChanged += (isDirty) => + { + if (isDirty) + { + //_meshesToUpdate = _gpuDataCache.GetKeys.ToHashSet(); + _meshesToUpdate = _pointCache.GetKeys.ToHashSet(); + } + }; + + } + + private void DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuData) + { + if (_pointCache.TryGetValue(octantId, out var points)) + { + if (UpdateGpuDataCache != null) + { + UpdateGpuDataCache.Invoke(ref gpuData, points); + } + else + { + if (!_doRenderInstanced) + gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); + else + gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); + } + _gpuDataCache.AddOrUpdate(octantId, gpuData); + _meshesToUpdate.Remove(octantId); + + if (_meshesToUpdate.Count == 0) + { + InvalidateCacheToken.IsDirty = false; + } + } } /// @@ -119,31 +154,21 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin /// else look in the point cache, if there are points create a mesh and add to the _meshCache. /// /// The unique id of an octant. + /// Allows inserting a condition, if true the mesh will be updated. This is an addition to + /// State of the gpu data in it's life cycle. /// - public override IEnumerable GetGpuData(OctantId octantId) + public override IEnumerable? GetGpuData(OctantId octantId, Func? doUpdateIf, out GpuDataState gpuDataState) { if (_gpuDataCache.TryGetValue(octantId, out var gpuData)) { - Guard.IsNotNull(InvalidateCacheToken); - - if (InvalidateCacheToken.IsDirty) + var doUpdate = doUpdateIf != null ? doUpdateIf.Invoke() : false; + if (_meshesToUpdate.Contains(octantId) || doUpdate) { - if (_pointCache.TryGetValue(octantId, out var points)) - { - if (UpdateGpuDataCache != null) - { - UpdateGpuDataCache.Invoke(ref gpuData, points); - } - else - { - if (!_doRenderInstanced) - gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); - else - gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); - } - _gpuDataCache.AddOrUpdate(octantId, gpuData); - } + DoUpdateGpuData(octantId, ref gpuData); + gpuDataState = GpuDataState.Changed; } + else + gpuDataState = GpuDataState.Unchanged; return gpuData; } @@ -152,9 +177,10 @@ public override IEnumerable GetGpuData(OctantId octantId) lock (LockDisposeQueue) { DisposeQueue.Remove(octantId); - _gpuDataCache.Add(octantId, gpuData); - return gpuData; } + DoUpdateGpuData(octantId, ref gpuData); + gpuDataState = GpuDataState.Changed; + return gpuData; } else if (_pointCache.TryGetValue(octantId, out var points)) { @@ -162,9 +188,14 @@ public override IEnumerable GetGpuData(OctantId octantId) gpuData = MeshMaker.CreateMeshes(points, _createGpuDataHandler, octantId); else gpuData = MeshMaker.CreateInstanceData(points, _createGpuDataHandler, octantId); - _gpuDataCache.Add(octantId, gpuData); + + _gpuDataCache.AddOrUpdate(octantId, gpuData); + gpuDataState = GpuDataState.New; + return gpuData; } + gpuDataState = GpuDataState.None; + //no points yet, probably in loading queue return null; } @@ -217,7 +248,7 @@ public override void TriggerPointLoading(OctantId guid) _ = Task.Run(() => { points = _loadPointsHandler.Invoke(guid); - _pointCache.Add(guid, points); + _pointCache.AddOrUpdate(guid, points); lock (LockLoadingQueue) { diff --git a/src/PointCloud/Core/PointCloudDataHandlerBase.cs b/src/PointCloud/Core/PointCloudDataHandlerBase.cs index 0c8673b7e..caf2a5eda 100644 --- a/src/PointCloud/Core/PointCloudDataHandlerBase.cs +++ b/src/PointCloud/Core/PointCloudDataHandlerBase.cs @@ -1,4 +1,6 @@ -using CommunityToolkit.HighPerformance.Buffers; +// Ignore Spelling: kvp + +using CommunityToolkit.HighPerformance.Buffers; using Fusee.PointCloud.Common; using System; using System.Collections.Generic; @@ -10,7 +12,10 @@ namespace Fusee.PointCloud.Core /// public abstract class PointCloudDataHandlerBase : IDisposable where TGpuData : IDisposable { - public InvalidateGpuDataCache InvalidateCacheToken; + /// + /// Token, that allows to invalidate the complete GpuData cache. + /// + public InvalidateGpuDataCache InvalidateCacheToken { get; } = new(); /// /// Used to manage gpu pressure when disposing of a large quantity of meshes. @@ -26,12 +31,12 @@ public abstract class PointCloudDataHandlerBase : IDisposable where TG /// /// Contains nodes that are queued for loading in the background. /// - protected List LoadingQueue; + protected List LoadingQueue = new(); /// /// Contains meshes that are marked for disposal. /// - protected Dictionary> DisposeQueue; + protected Dictionary> DisposeQueue = new(); /// /// Locking object for the loading queue. @@ -48,7 +53,9 @@ public abstract class PointCloudDataHandlerBase : IDisposable where TG /// else look in the point cache, if there are points create a mesh and add to the MeshCache. /// /// The unique id of an octant. - public abstract IEnumerable GetGpuData(OctantId guid); + /// Allows inserting a condition, if true the mesh will be updated. + /// State of the gpu data in it's life cycle. + public abstract IEnumerable? GetGpuData(OctantId guid, Func? doUpdateIf, out GpuDataState gpuDataState); /// /// Loads points from the hard drive if they are neither in the loading queue nor in the PointCahce. diff --git a/src/PointCloud/Core/Scene/PointCloudComponent.cs b/src/PointCloud/Core/Scene/PointCloudComponent.cs index 3f5267df4..d11b4074e 100644 --- a/src/PointCloud/Core/Scene/PointCloudComponent.cs +++ b/src/PointCloud/Core/Scene/PointCloudComponent.cs @@ -35,7 +35,7 @@ public class PointCloudComponent : SceneComponent, IPointCloud public Camera? Camera; /// - /// Instantiates the . + /// Instantiates the . /// public PointCloudComponent(IPointCloudImpBase imp, RenderMode renderMode = RenderMode.StaticMesh) { diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index a91627881..6f9fb76aa 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -190,8 +190,8 @@ private List PickOctantRecursively(PointCloudOctant node, RayD /// Inject this to a to be able to pick s. /// The actual point and data needs to be present a priori, however it's type is polymorph, therefore we need to inject those data, too. /// - /// The of the . - /// The , needs to be of type + /// The of the . + /// The , needs to be of type /// The spacing between points. For Potree use the metadata spacing component * 0.1f
e. g. Spacing = 2.18f, pass 0.218f to this ctor. public PointCloudPickerModule(IPointCloudOctree octree, IPointCloudImp pcImp, float pointSpacing) { diff --git a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs index de6d5d6f5..65252a6f6 100644 --- a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs @@ -1,3 +1,5 @@ +// Ignore Spelling: fov + using Fusee.Base.Core; using Fusee.Engine.Core; using Fusee.Engine.Core.Primitives; diff --git a/src/PointCloud/Potree/Potree2Cloud.cs b/src/PointCloud/Potree/Potree2Cloud.cs index 1fa35c3e4..c84e8db4c 100644 --- a/src/PointCloud/Potree/Potree2Cloud.cs +++ b/src/PointCloud/Potree/Potree2Cloud.cs @@ -86,7 +86,6 @@ public float UpdateRate ///
public float3 Size => new((float)VisibilityTester.Octree.Root.Size); - private readonly GetMeshes _getMeshes; private bool _doUpdate = true; /// @@ -97,9 +96,7 @@ public Potree2Cloud(PointCloudDataHandlerBase dataHandler, IPointCloudO GpuDataToRender = new List(); DataHandler = dataHandler; DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; - DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); - _getMeshes = dataHandler.GetGpuData; } /// @@ -113,7 +110,7 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner - /// Uses the and to update the visible meshes. + /// Uses the and to update the visible meshes. /// Called every frame. /// /// The camera's field of view. @@ -145,7 +142,7 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 { if (!guid.Valid) continue; - var meshes = _getMeshes(guid); + var meshes = DataHandler.GetGpuData(guid, null, out _); if (meshes == null) continue; //points for this octant aren't loaded yet. diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index 7615cd183..c8bafc515 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -3,9 +3,9 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; +using SixLabors.ImageSharp.ColorSpaces; using System; using System.Collections.Generic; -using System.Linq; namespace Fusee.PointCloud.Potree { @@ -17,7 +17,7 @@ public class Potree2CloudDynamic : IPointCloudImp /// /// Object for handling the invalidation of the gpu data cache. /// - public InvalidateGpuDataCache InvalidateGpuDataCache { get; } = new(); + public InvalidateGpuDataCache InvalidateGpuDataCache { get => DataHandler.InvalidateCacheToken; } /// /// The complete list of meshes that can be rendered. @@ -95,7 +95,11 @@ public float UpdateRate /// public Action? NewMeshAction; - private readonly GetDynamicMeshes _getMeshes; + /// + /// Action that is run on every mesh that was updated. + /// + public Action? UpdatedMeshAction; + private bool _doUpdate = true; /// @@ -106,9 +110,7 @@ public Potree2CloudDynamic(PointCloudDataHandlerBase dataHandler, IPointCl GpuDataToRender = new List(); DataHandler = dataHandler; DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; - DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); - _getMeshes = dataHandler.GetGpuData; } /// @@ -140,6 +142,9 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner public bool LoadNewMeshes { get; set; } = true; + + private List _visibleOctantsCache = new(); + /// /// Uses the and to update the visible meshes. /// Called every frame. @@ -169,37 +174,51 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 VisibilityTester.Model = modelMat; VisibilityTester.Update(); + GpuDataToRender.Clear(); - var meshes = new List(); + var currentOctants = new List(); foreach (var guid in VisibilityTester.VisibleNodes) { if (!guid.Valid) continue; - var guidMeshes = _getMeshes(guid); - - if (guidMeshes == null) continue; //points for this octant aren't loaded yet. + var guidMeshes = DataHandler.GetGpuData(guid, () => !_visibleOctantsCache.Contains(guid), out GpuDataState meshStatus); - meshes.AddRange(guidMeshes); - } - - if (NewMeshAction != null) - { - var newMeshes = meshes.Except(GpuDataToRender); - - if (newMeshes.Any()) + switch (meshStatus) { - foreach (var mesh in newMeshes) - { - NewMeshAction(mesh); - } + //Octants that are now visible but the points for this octant aren't loaded yet. + //Nothing to do here. + case GpuDataState.None: + continue; + //Octants that are now visible and the meshes are newly created. + //They we have to call "NewMeshAction" when they are loaded. + case GpuDataState.New: + if (guidMeshes == null) continue; + foreach (var mesh in guidMeshes) + { + NewMeshAction?.Invoke(mesh); + } + break; + //Octants that are now visible and the existing meshes where updated. + case GpuDataState.Changed: + if (guidMeshes == null) continue; + foreach (var mesh in guidMeshes) + { + UpdatedMeshAction?.Invoke(mesh); + } + break; + case GpuDataState.Unchanged: + if (guidMeshes == null) continue; + break; + default: + throw new ArgumentException($"Invalid mesh status {meshStatus}."); } - } - InvalidateGpuDataCache.IsDirty = false; + GpuDataToRender.AddRange(guidMeshes); + currentOctants.Add(guid); + } - GpuDataToRender.Clear(); - GpuDataToRender.AddRange(meshes); + _visibleOctantsCache = new(currentOctants); } } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2CloudInstanced.cs b/src/PointCloud/Potree/Potree2CloudInstanced.cs index 48cd5d1f8..b270e10b1 100644 --- a/src/PointCloud/Potree/Potree2CloudInstanced.cs +++ b/src/PointCloud/Potree/Potree2CloudInstanced.cs @@ -86,7 +86,6 @@ public float UpdateRate /// public float3 Size => new((float)VisibilityTester.Octree.Root.Size); - private readonly GetInstanceData _getInstanceData; private bool _doUpdate = true; /// @@ -97,9 +96,7 @@ public Potree2CloudInstanced(PointCloudDataHandlerBase dataHandler GpuDataToRender = new List(); DataHandler = dataHandler; DataHandler.UpdateGpuDataCache = UpdateGpuDataCache; - DataHandler.InvalidateCacheToken = InvalidateGpuDataCache; VisibilityTester = new VisibilityTester(octree, dataHandler.TriggerPointLoading); - _getInstanceData = dataHandler.GetGpuData; } /// @@ -113,7 +110,7 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner } /// - /// Uses the and to update the visible meshes. + /// Uses the and to update the visible meshes. /// Called every frame. /// /// The camera's field of view. @@ -145,7 +142,7 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 { if (!guid.Valid) continue; - var instanceData = _getInstanceData(guid); + var instanceData = DataHandler.GetGpuData(guid, null, out _); if (instanceData == null) continue; //points for this octant aren't loaded yet. From 0fd36a5b508e044ddbf6fdae041752167c1ad070 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Fri, 12 May 2023 15:12:42 +0200 Subject: [PATCH 195/294] Update only if flags differ --- src/PointCloud/Core/PointCloudDataHandler.cs | 2 +- src/PointCloud/Potree/Potree2CloudDynamic.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 79f7d586c..4388c2777 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -263,7 +263,7 @@ private void OnItemEvictedFromCache(object guid, object? meshes, EvictionReason lock (LockDisposeQueue) { if (meshes == null) return; - DisposeQueue.Add((OctantId)guid, (IEnumerable)meshes); + DisposeQueue.TryAdd((OctantId)guid, (IEnumerable)meshes); } } diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index c8bafc515..36100c2b7 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -131,7 +131,8 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner Date: Mon, 15 May 2023 12:09:37 +0200 Subject: [PATCH 196/294] Bugfix FilePicker, enable picking of non existing files for file saving --- .../Templates/ImGuiFilePicker.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index fc3d02610..ec8d3a796 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -112,6 +112,12 @@ public bool IsOpen // needed for width calculation protected Vector2 _sizeOfInputText; + /// + /// Checks input before returning if file exists. + /// Disable this check for file saving. + /// + public bool NonExistingFilesAllowed = false; + /// /// Text color of folder /// @@ -475,26 +481,28 @@ private List GetFileSystemEntries(string fullName) protected virtual bool HandlePickedFile(FileInfo selectedFile) { - if (selectedFile.Exists) + + if (selectedFile.Directory != null) { - if (selectedFile.Directory != null) + if (NonExistingFilesAllowed || selectedFile.Exists) { CurrentOpenFolder = selectedFile.Directory; SelectedFile = selectedFile; return true; } - } - else if (selectedFile != null) - { - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); - ImGui.BeginTooltip(); - ImGui.TextColored(WarningTextColor, FileNotFoundTxt); - ImGui.EndTooltip(); - ImGui.PopStyleVar(); + else if (selectedFile != null) + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); + ImGui.BeginTooltip(); + ImGui.TextColored(WarningTextColor, FileNotFoundTxt); + ImGui.EndTooltip(); + ImGui.PopStyleVar(); - return false; + return false; + } } + return false; } } From a17422a1f3a1348c589f967a78f11af047860fcb Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 15 May 2023 14:25:57 +0200 Subject: [PATCH 197/294] Cleanup --- src/PointCloud/Core/VisualizationPoint.cs | 19 ------------------- src/PointCloud/Potree/Potree2LAS.cs | 2 ++ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/PointCloud/Core/VisualizationPoint.cs b/src/PointCloud/Core/VisualizationPoint.cs index b8d1a87bc..8d58fef93 100644 --- a/src/PointCloud/Core/VisualizationPoint.cs +++ b/src/PointCloud/Core/VisualizationPoint.cs @@ -36,23 +36,4 @@ public override string ToString() return $"Position: {Position} - Color: {Color} - Flags: {FlagsParser(Flags)}"; } } - - /// - /// This point is used for data handling purposes. - /// It's read from a file and converted to either LAS or changed and updated in the original file. - /// - public struct PotreePoint - { - /// - /// The position of a point. - /// - public float3 Position; - - /// - /// The color (r,g,b,a) of a point. - /// - public float4 Color; - - - } } \ No newline at end of file diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 116c3a000..6af76c57f 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -476,6 +476,8 @@ public void Write() _fileStream.Write(tmpArray[..15]); // pos(12) + intensity (2) + returnStuff (1) _fileStream.Write(tmpArray[16..Metadata.PointSize]); // skip array pos [15], byte 16 } + + inputStream.Close(); } /// From e021b2f3156557b9e7712235ca296103bfbf5fb3 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 15 May 2023 14:46:22 +0200 Subject: [PATCH 198/294] FilePicker hotfix: Do not crash when filename is empty or whitespace --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 568232462..0adf06e03 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -397,7 +397,8 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - SelectedFile = selectedFile; + if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0])) + SelectedFile = new FileInfo(selectedFile); } if (_sizeOfInputText == Vector2.Zero) From c1874f7f18fbc97ad3e42c60f2705ee8141d9137 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 15 May 2023 14:46:41 +0200 Subject: [PATCH 199/294] FilePicker hotfix: Do not crash when filename is empty or whitespace --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index ec8d3a796..305d05d89 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -378,7 +378,8 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - SelectedFile = new FileInfo(selectedFile); + if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0])) + SelectedFile = new FileInfo(selectedFile); } if (_sizeOfInputText == Vector2.Zero) From 8260e3a53b2783f683814a9568456d2346041918 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 15 May 2023 15:34:14 +0200 Subject: [PATCH 200/294] FilePicker hotfix: Add fullpath to manually selected file --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 4 ++-- src/PointCloud/Potree/V2/Potree2Reader.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 305d05d89..eecc577e3 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -378,8 +378,8 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0])) - SelectedFile = new FileInfo(selectedFile); + if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0]) && CurrentOpenFolder != null) + SelectedFile = new FileInfo(Path.Combine(CurrentOpenFolder.FullName, selectedFile)); } if (_sizeOfInputText == Vector2.Zero) diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 65370319d..1468ef84d 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -299,8 +299,6 @@ public void ReadFile(PotreeData potreeData) CalculateAttributeOffsets(ref Metadata); - // TODO(mr): Add isExtraBytes, adapt in LASReader - Hierarchy.Root.Aabb = new AABBd(Metadata.BoundingBox.Min, Metadata.BoundingBox.Max); var data = File.ReadAllBytes(hierarchyFilePath); @@ -326,7 +324,8 @@ private static PotreeMetadata LoadPotreeMetadata(string metadataFilepath) { var settings = new JsonSerializerSettings(); settings.Converters.Add(new ConvertIPointWriterHierarchy()); - var potreeData = JsonConvert.DeserializeObject(File.ReadAllText(metadataFilepath), settings); + var metaData = File.ReadAllText(metadataFilepath); + var potreeData = JsonConvert.DeserializeObject(metaData, settings); Guard.IsNotNull(potreeData, nameof(potreeData)); return potreeData; From d19810e8acad30a3e073fdd1d337f0b81ce6f02b Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 15 May 2023 15:34:38 +0200 Subject: [PATCH 201/294] FilePicker hotfix: Add fullpath to manually selected file --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 0adf06e03..742282157 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -397,8 +397,8 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0])) - SelectedFile = new FileInfo(selectedFile); + if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0]) && CurrentOpenFolder != null) + SelectedFile = new FileInfo(Path.Combine(CurrentOpenFolder.FullName, selectedFile)); } if (_sizeOfInputText == Vector2.Zero) From 23e9ea40b293c9aca24a37504d4f3095d74b0f1e Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 15 May 2023 16:02:25 +0200 Subject: [PATCH 202/294] PointCloudDataHandler GetGpuData: return null if update isn't possible --- src/PointCloud/Common/PointRenderEnums.cs | 2 +- src/PointCloud/Core/PointCloudDataHandler.cs | 21 ++++++++++++++------ src/PointCloud/Potree/Potree2CloudDynamic.cs | 10 +++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/PointCloud/Common/PointRenderEnums.cs b/src/PointCloud/Common/PointRenderEnums.cs index b21ab691f..17e36013c 100644 --- a/src/PointCloud/Common/PointRenderEnums.cs +++ b/src/PointCloud/Common/PointRenderEnums.cs @@ -7,7 +7,7 @@ public enum GpuDataState { /// - /// Default, gpu data wasn't created yet. + /// Default, gpu data wasn't created yet and or points havent been loaded yet. /// None = -1, diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 4388c2777..02d80046c 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -123,7 +123,7 @@ public PointCloudDataHandler(CreateGpuData createMeshHandler, LoadPoin } - private void DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuData) + private GpuDataState DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuData) { if (_pointCache.TryGetValue(octantId, out var points)) { @@ -145,7 +145,13 @@ private void DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuDat { InvalidateCacheToken.IsDirty = false; } + + return GpuDataState.Changed; } + + //No points in cache - cannot update (point loading is triggered in VisibilityTester) + return GpuDataState.None; + } /// @@ -164,8 +170,10 @@ private void DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuDat var doUpdate = doUpdateIf != null ? doUpdateIf.Invoke() : false; if (_meshesToUpdate.Contains(octantId) || doUpdate) { - DoUpdateGpuData(octantId, ref gpuData); - gpuDataState = GpuDataState.Changed; + gpuDataState = DoUpdateGpuData(octantId, ref gpuData); + if (gpuDataState != GpuDataState.None) + return gpuData; + return null; } else gpuDataState = GpuDataState.Unchanged; @@ -178,9 +186,10 @@ private void DoUpdateGpuData(OctantId octantId, ref IEnumerable gpuDat { DisposeQueue.Remove(octantId); } - DoUpdateGpuData(octantId, ref gpuData); - gpuDataState = GpuDataState.Changed; - return gpuData; + gpuDataState = DoUpdateGpuData(octantId, ref gpuData); + if (gpuDataState != GpuDataState.None) + return gpuData; + return null; } else if (_pointCache.TryGetValue(octantId, out var points)) { diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index 36100c2b7..dc46e3bad 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -1,9 +1,9 @@ -using CommunityToolkit.HighPerformance.Buffers; +using CommunityToolkit.Diagnostics; +using CommunityToolkit.HighPerformance.Buffers; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core; -using SixLabors.ImageSharp.ColorSpaces; using System; using System.Collections.Generic; @@ -194,7 +194,7 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 //Octants that are now visible and the meshes are newly created. //They we have to call "NewMeshAction" when they are loaded. case GpuDataState.New: - if (guidMeshes == null) continue; + Guard.IsNotNull(guidMeshes); //If this is null we have an internal error in DataHandler.GetMeshes/DoUpdate foreach (var mesh in guidMeshes) { NewMeshAction?.Invoke(mesh); @@ -202,14 +202,14 @@ public void Update(float fov, int viewportHeight, FrustumF renderFrustum, float3 break; //Octants that are now visible and the existing meshes where updated. case GpuDataState.Changed: - if (guidMeshes == null) continue; + Guard.IsNotNull(guidMeshes); //If this is null we have an internal error in DataHandler.GetMeshes/DoUpdate foreach (var mesh in guidMeshes) { UpdatedMeshAction?.Invoke(mesh); } break; case GpuDataState.Unchanged: - if (guidMeshes == null) continue; + Guard.IsNotNull(guidMeshes); //If this is null we have an internal error in DataHandler.GetMeshes/DoUpdate break; default: throw new ArgumentException($"Invalid mesh status {meshStatus}."); From 825a7c998509e7060476f39235b8c13cd7ceacbc Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Mon, 15 May 2023 16:46:09 +0200 Subject: [PATCH 203/294] PointCloudDataHandler: remove GpuData from cache if it is invalid --- src/Base/Core/MemoryCache.cs | 9 ++++++ src/PointCloud/Core/PointCloudDataHandler.cs | 32 ++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Base/Core/MemoryCache.cs b/src/Base/Core/MemoryCache.cs index f1171052a..2d3463583 100644 --- a/src/Base/Core/MemoryCache.cs +++ b/src/Base/Core/MemoryCache.cs @@ -151,6 +151,15 @@ public void AddOrUpdate(TKey key, TItem cacheEntry) _cache.Set(key, cacheEntry, cacheEntryOptions); } + /// + /// Removes the object associated with the given key. + /// + /// The key. + public void Remove(TKey key) + { + _cache.Remove(key); + } + /// /// Implement IDisposable. /// Do not make this method virtual. diff --git a/src/PointCloud/Core/PointCloudDataHandler.cs b/src/PointCloud/Core/PointCloudDataHandler.cs index 02d80046c..bd62d8d6e 100644 --- a/src/PointCloud/Core/PointCloudDataHandler.cs +++ b/src/PointCloud/Core/PointCloudDataHandler.cs @@ -138,14 +138,6 @@ private GpuDataState DoUpdateGpuData(OctantId octantId, ref IEnumerable Date: Mon, 15 May 2023 17:41:56 +0200 Subject: [PATCH 204/294] Adapt and fix Potree Metadata AABB, --- src/PointCloud/Potree/Potree2LAS.cs | 8 ++++---- src/PointCloud/Potree/V2/Potree2Reader.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 6af76c57f..05aa25c89 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -259,11 +259,11 @@ private void ParseAndFillHeader() MinY = Metadata.AABB.min.z, MinZ = Metadata.AABB.min.y, OffsetX = Metadata.Offset.x, - OffsetY = Metadata.Offset.y, - OffsetZ = Metadata.Offset.z, + OffsetY = Metadata.Offset.z, + OffsetZ = Metadata.Offset.y, ScaleFactorX = Metadata.Scale.x, - ScaleFactorY = Metadata.Scale.y, - ScaleFactorZ = Metadata.Scale.z, + ScaleFactorY = Metadata.Scale.z, + ScaleFactorZ = Metadata.Scale.y, NumberOfPtRecords = (ulong)Metadata.PointCount, LegacyNbrOfPoints = (uint)Metadata.PointCount, PointDataRecordFormat = (byte)_type diff --git a/src/PointCloud/Potree/V2/Potree2Reader.cs b/src/PointCloud/Potree/V2/Potree2Reader.cs index 1468ef84d..8a5ce3d1b 100644 --- a/src/PointCloud/Potree/V2/Potree2Reader.cs +++ b/src/PointCloud/Potree/V2/Potree2Reader.cs @@ -312,9 +312,9 @@ public void ReadFile(PotreeData potreeData) FlipYZAxis(Metadata, Hierarchy); - // do not adapt the global AABB after conversion, keep original for LAS writing - //Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x, Hierarchy.Root.Aabb.min.y, Hierarchy.Root.Aabb.min.z }; - //Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x, Hierarchy.Root.Aabb.max.y, Hierarchy.Root.Aabb.max.z }; + // adapt the global AABB after conversion, this works with the current LAS writer + Metadata.BoundingBox.MinList = new List(3) { Hierarchy.Root.Aabb.min.x + Metadata.Offset.x, Hierarchy.Root.Aabb.min.z + Metadata.Offset.z, Hierarchy.Root.Aabb.min.y + Metadata.Offset.y }; + Metadata.BoundingBox.MaxList = new List(3) { Hierarchy.Root.Aabb.max.x + Metadata.Offset.x, Hierarchy.Root.Aabb.max.z + Metadata.Offset.z, Hierarchy.Root.Aabb.max.y + Metadata.Offset.y }; return (Metadata, Hierarchy); } From c9f27d1c484343f32d6234b0af2a44e88adb49a6 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 16 May 2023 11:59:57 +0200 Subject: [PATCH 205/294] IPickerModule returns a list of PickResults --- src/Engine/Core/IPickerModule.cs | 3 +- src/Engine/Core/ScenePicker.cs | 7 +++- .../Core/Scene/PointCloudPickerModule.cs | 39 ++++++++----------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/Engine/Core/IPickerModule.cs b/src/Engine/Core/IPickerModule.cs index 9f452f683..692d41726 100644 --- a/src/Engine/Core/IPickerModule.cs +++ b/src/Engine/Core/IPickerModule.cs @@ -1,4 +1,5 @@ using Fusee.Engine.Common; +using System.Collections.Generic; using static Fusee.Engine.Core.ScenePicker; namespace Fusee.Engine.Core @@ -11,6 +12,6 @@ public interface IPickerModule : IVisitorModule /// The state to set. public void SetState(PickerState state); - public PickResult PickResult { get; set; } + public List PickResults { get; set; } } } \ No newline at end of file diff --git a/src/Engine/Core/ScenePicker.cs b/src/Engine/Core/ScenePicker.cs index 8fc88a964..f2c95ebde 100644 --- a/src/Engine/Core/ScenePicker.cs +++ b/src/Engine/Core/ScenePicker.cs @@ -294,12 +294,15 @@ protected override void InitState() private IEnumerable CheckVisitorModuleResults() { + var res = new List(); foreach (var module in VisitorModules) { var m = (IPickerModule)module; - if (m.PickResult != null) - yield return m.PickResult; + if (m.PickResults != null) + res.AddRange(m.PickResults); } + + return res; } /// diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 6f9fb76aa..8bb4bd8e2 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -48,7 +48,7 @@ public class PointCloudPickerModule : IPickerModule /// The pick result after picking. /// #pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). - public PickResult? PickResult { get; set; } + public List? PickResults { get; set; } #pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). internal struct MinPickValue @@ -66,7 +66,7 @@ internal struct MinPickValue [VisitMethod] public void RenderPointCloud(PointCloudComponent pointCloud) { - PickResult = null; + PickResults = new List(); if (!pointCloud.Active) return; Guard.IsNotNull(_pcImp); @@ -118,33 +118,26 @@ public void RenderPointCloud(PointCloudComponent pointCloud) }); if (currentRes == null || currentRes.Count == 0) return; + - var minElement = currentRes.First(); + var mvp = proj * view * _state.Model; foreach (var r in currentRes) { - if (r.Distance.x < minElement.Distance.x && r.Distance.y < minElement.Distance.y) + var pickRes = new PointCloudPickResult { - // TODO: Test if a offset > e. g. 0.1 is necessary that we do not spawn a box inside the cull / near clipping plane :) - minElement = r; - } + Node = null, + Projection = proj, + View = view, + Model = _state.Model, + ClipPos = float4x4.TransformPerspective(mvp, r.Mesh.Vertices[r.VertIdx]), + Mesh = r.Mesh, + VertIdx = r.VertIdx, + OctantId = r.OctantId + }; + + PickResults.Add(pickRes); } - - Guard.IsNotNull(minElement.Mesh.Vertices); - - var mvp = proj * view * _state.Model; - PickResult = new PointCloudPickResult - { - Node = null, - Projection = proj, - View = view, - Model = _state.Model, - ClipPos = float4x4.TransformPerspective(mvp, minElement.Mesh.Vertices[minElement.VertIdx]), - Mesh = minElement.Mesh, - VertIdx = minElement.VertIdx, - OctantId = minElement.OctantId - }; - } From 2aab13cc9e002d64cf1df179b4d17f089ddf551d Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 17 May 2023 14:25:05 +0200 Subject: [PATCH 206/294] Fixed LAS export --- src/PointCloud/Common/IPointWriter.cs | 1 + src/PointCloud/Potree/Potree2LAS.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index ba4523f50..ca31ddb8c 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -80,6 +80,7 @@ public interface IPointWriterMetadata /// /// Global of the point cloud + /// These values are not yet y/z flipped /// public AABBd AABB { get; } diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 05aa25c89..61578ba74 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -253,14 +253,14 @@ private void ParseAndFillHeader() FileCreationDayOfYear = (ushort)doy, FileCreationYear = (ushort)year, MaxX = Metadata.AABB.max.x, - MaxY = Metadata.AABB.max.z, - MaxZ = Metadata.AABB.max.y, + MaxY = Metadata.AABB.max.y, + MaxZ = Metadata.AABB.max.z, MinX = Metadata.AABB.min.x, - MinY = Metadata.AABB.min.z, - MinZ = Metadata.AABB.min.y, + MinY = Metadata.AABB.min.y, + MinZ = Metadata.AABB.min.z, OffsetX = Metadata.Offset.x, - OffsetY = Metadata.Offset.z, - OffsetZ = Metadata.Offset.y, + OffsetY = Metadata.Offset.y, + OffsetZ = Metadata.Offset.z, ScaleFactorX = Metadata.Scale.x, ScaleFactorY = Metadata.Scale.z, ScaleFactorZ = Metadata.Scale.y, From 9adc3f632539dd04890cbcffedea8faf239d7673 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Wed, 17 May 2023 14:28:53 +0200 Subject: [PATCH 207/294] Fixed LAS export y/z flip --- src/PointCloud/Potree/Potree2LAS.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PointCloud/Potree/Potree2LAS.cs b/src/PointCloud/Potree/Potree2LAS.cs index 61578ba74..b545c2bd0 100644 --- a/src/PointCloud/Potree/Potree2LAS.cs +++ b/src/PointCloud/Potree/Potree2LAS.cs @@ -247,6 +247,7 @@ private void ParseAndFillHeader() _ => throw new NotImplementedException(), }); + // Note: AABB is not y/z flipped, offset and scale is. _header = new LASHeader { PointDataRecordLength = (ushort)size, @@ -259,8 +260,8 @@ private void ParseAndFillHeader() MinY = Metadata.AABB.min.y, MinZ = Metadata.AABB.min.z, OffsetX = Metadata.Offset.x, - OffsetY = Metadata.Offset.y, - OffsetZ = Metadata.Offset.z, + OffsetY = Metadata.Offset.z, + OffsetZ = Metadata.Offset.y, ScaleFactorX = Metadata.Scale.x, ScaleFactorY = Metadata.Scale.z, ScaleFactorZ = Metadata.Scale.y, From 59726de495f7208d8ecf08b8e70f863df697ba0f Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 22 May 2023 13:32:20 +0200 Subject: [PATCH 208/294] Update mesh indices before rendering, discard dirtyIndex values before rendering --- src/Engine/Core/RenderCanvas.cs | 4 +++- src/Engine/Core/RenderContext.cs | 8 ++++++++ src/Engine/Core/Scene/Mesh.cs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Engine/Core/RenderCanvas.cs b/src/Engine/Core/RenderCanvas.cs index d655673b6..e33572602 100644 --- a/src/Engine/Core/RenderCanvas.cs +++ b/src/Engine/Core/RenderCanvas.cs @@ -222,11 +222,13 @@ public void InitApp() // pre-rendering Time.Instance.DeltaTimeIncrement = CanvasImplementor.DeltaTime; + // update all meshes (changed values like position, normals, etc.) before rendering them + RC.UpdateAllMeshes(); + // rendering if (Width != 0 || Height != 0) RenderAFrame(); - RC.UpdateAllMeshes(); RC.CleanupResourceManagers(); EndOfFrame?.Invoke(this, EventArgs.Empty); diff --git a/src/Engine/Core/RenderContext.cs b/src/Engine/Core/RenderContext.cs index 40f69e979..865487090 100644 --- a/src/Engine/Core/RenderContext.cs +++ b/src/Engine/Core/RenderContext.cs @@ -2021,6 +2021,14 @@ public void Render(Mesh mesh, InstanceData instanceData = null, bool doRenderFor UpdateAllActiveFxParams(cFx); var meshImp = _meshManager.GetImpFromMesh(mesh); + + // The dirty index functionality works after the initial call to the MeshManager + // This is therefore the first possible place to catch und discard (pointcloud)-meshes with a dirty index + if (mesh != null && mesh.Flags != null && mesh.Flags.DirtyIndex) + { + return; + } + if (instanceData != null) { var instanceDataImp = _meshManager.GetImpFromInstanceData(mesh, instanceData); diff --git a/src/Engine/Core/Scene/Mesh.cs b/src/Engine/Core/Scene/Mesh.cs index 00d82eef6..74b650ba9 100644 --- a/src/Engine/Core/Scene/Mesh.cs +++ b/src/Engine/Core/Scene/Mesh.cs @@ -175,7 +175,7 @@ public class Mesh : SceneComponent, IManagedMesh public Suid SessionUniqueIdentifier { get; } = Suid.GenerateSuid(); /// - /// Update all changed data after each frame? + /// Update all changed data before each frame? /// public bool UpdatePerFrame { set; get; } = true; From 225974448a6257c9f90c166ba31d6e178bf3fe93 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Mon, 22 May 2023 13:58:33 +0200 Subject: [PATCH 209/294] Wrap discarding of dirtyIndex-meshes into property with convient access --- src/Engine/Core/RenderContext.cs | 11 ++++- src/Engine/Core/Scene/Mesh.cs | 42 +++++++++++++++++++ .../Core/Scene/PointCloudRenderModule.cs | 3 ++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/Engine/Core/RenderContext.cs b/src/Engine/Core/RenderContext.cs index 865487090..f946fb411 100644 --- a/src/Engine/Core/RenderContext.cs +++ b/src/Engine/Core/RenderContext.cs @@ -770,6 +770,15 @@ public float4x4 InvTransModelViewProjection /// public LightResult[] ForwardLights = new LightResult[ModuleExtensionPoint.NumberOfLightsForward]; + /// + /// Render meshes even if is . + /// If all meshes are guaranted to have valid and up-to-date values each frame. + /// Usually this is not necessary, however if there is caching and/or visibility testing on a mesh level involved + /// we want to ensure the data validity of each frame. + /// + public bool AllowDirtyMeshs { get; set; } = true; + + /// /// Initializes a new instance of the class. /// @@ -2024,7 +2033,7 @@ public void Render(Mesh mesh, InstanceData instanceData = null, bool doRenderFor // The dirty index functionality works after the initial call to the MeshManager // This is therefore the first possible place to catch und discard (pointcloud)-meshes with a dirty index - if (mesh != null && mesh.Flags != null && mesh.Flags.DirtyIndex) + if (!AllowDirtyMeshs && mesh != null && mesh.HasDirtyIndices) { return; } diff --git a/src/Engine/Core/Scene/Mesh.cs b/src/Engine/Core/Scene/Mesh.cs index 74b650ba9..a984faa7f 100644 --- a/src/Engine/Core/Scene/Mesh.cs +++ b/src/Engine/Core/Scene/Mesh.cs @@ -179,11 +179,51 @@ public class Mesh : SceneComponent, IManagedMesh /// public bool UpdatePerFrame { set; get; } = true; + /// + /// Gather all values in one property. + /// + /// if any of the is true. + public bool HasDirtyIndices + { +#pragma warning disable CS8602 // Dereference of a possibly null reference. + get + { + var returnVal = Vertices.DirtyIndex && Triangles.DirtyIndex; + + if (NormalsSet) + returnVal &= Normals.DirtyIndex; + if (UVsSet) + returnVal &= UVs.DirtyIndex; + if (TangentsSet) + returnVal &= Tangents.DirtyIndex; + if (BiTangentsSet) + returnVal &= BiTangents.DirtyIndex; + if (BoneIndicesSet) + returnVal &= BoneIndices.DirtyIndex; + if (BoneWeightsSet) + returnVal &= BoneWeights.DirtyIndex; + if (Colors0Set) + returnVal &= Colors0.DirtyIndex; + if (Colors1Set) + returnVal &= Colors1.DirtyIndex; + if (Colors2Set) + returnVal &= Colors2.DirtyIndex; + if (FlagsSet) + returnVal &= Flags.DirtyIndex; + + return returnVal; + } +#pragma warning restore CS8602 // Dereference of a possibly null reference. + + } + /// /// Reset all dirty flags /// public void ResetIndexLists() { +#pragma warning disable CS8602 // Dereference of a possibly null reference. + Vertices.DirtyIndex = false; Triangles.DirtyIndex = false; @@ -223,6 +263,8 @@ public void ResetIndexLists() //Colors0?.DirtyIndices.ResetList(); //Colors1?.DirtyIndices.ResetList(); //Colors2?.DirtyIndices.ResetList(); + +#pragma warning restore CS8602 // Dereference of a possibly null reference. } #endregion diff --git a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs index 65252a6f6..f319cd3fa 100644 --- a/src/PointCloud/Core/Scene/PointCloudRenderModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudRenderModule.cs @@ -44,6 +44,9 @@ public void UpdateContext(RenderContext rc) throw new ArgumentNullException(nameof(rc)); _rc = rc; + + // prevent rendering with + _rc.AllowDirtyMeshs = false; } /// From 1e9137f5b24422685bb0692dacae7dc48e7b5a86 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 22 May 2023 18:01:35 +0200 Subject: [PATCH 210/294] Unifying Android projects --- .../Fusee.Examples.AdvancedUI.Android.csproj | 15 ++--- .../Android/Properties/AndroidManifest.xml | 2 +- .../Fusee.Examples.Camera.Android.csproj | 15 ++--- .../Android/Properties/AndroidManifest.xml | 4 +- .../Fusee.Examples.Deferred.Android.csproj | 23 ++------ .../Android/Properties/AndroidManifest.xml | 9 +-- ...ee.Examples.GeometryEditing.Android.csproj | 11 ++-- .../Android/Properties/AndroidManifest.xml | 4 +- .../Fusee.Examples.Materials.Android.csproj | 22 ++----- .../Android/Properties/AndroidManifest.xml | 4 +- ...usee.Examples.MeshingAround.Android.csproj | 11 ++-- .../Android/Properties/AndroidManifest.xml | 4 +- .../Fusee.Examples.Picking.Android.csproj | 11 ++-- .../Android/Properties/AndroidManifest.xml | 4 +- ...see.Examples.PickingRayCast.Android.csproj | 20 +++---- .../Android/Properties/AndroidManifest.xml | 6 +- ....Examples.RenderContextOnly.Android.csproj | 12 ++-- .../Android/Properties/AndroidManifest.xml | 6 +- .../Fusee.Examples.RenderLayer.Android.csproj | 11 ++-- .../Android/Properties/AndroidManifest.xml | 6 +- .../Fusee.Examples.Simple.Android.csproj | 57 +++++++++---------- .../Android/Properties/AndroidManifest.xml | 4 +- .../Fusee.Examples.ThreeDFont.Android.csproj | 11 ++-- .../Android/Properties/AndroidManifest.xml | 4 +- .../Android/Fusee.Examples.UI.Android.csproj | 11 ++-- .../UI/Android/Properties/AndroidManifest.xml | 4 +- .../Fusee.Engine.Player.Android.csproj | 5 +- .../Android/Properties/AndroidManifest.xml | 5 +- 28 files changed, 115 insertions(+), 186 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj index 4ad6dc1dd..cf29c0418 100644 --- a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj +++ b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.AdvancedUI.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,30 +28,27 @@ 4 None None - true + True True False - false + False False - armeabi-v7a;x86_64 Xamarin False True - false - false - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/AdvancedUI/Android/Properties/AndroidManifest.xml b/Examples/Complete/AdvancedUI/Android/Properties/AndroidManifest.xml index 760b68c26..0ca26b228 100644 --- a/Examples/Complete/AdvancedUI/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/AdvancedUI/Android/Properties/AndroidManifest.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj index cad802c13..6cc0407a6 100644 --- a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj +++ b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj @@ -1,14 +1,10 @@ - + Fusee.Examples.Camera.Android Fusee.Examples.Camera.Android Debug AnyCPU - - $(FuseeEngineRoot)\bin\$(Configuration) - - ..\..\..\..\bin\$(Configuration) $(BaseOutputPath)\Examples\Camera\Android 8.0.30703 @@ -20,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -32,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin @@ -46,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/Camera/Android/Properties/AndroidManifest.xml b/Examples/Complete/Camera/Android/Properties/AndroidManifest.xml index 87f7791a6..776cdb91c 100644 --- a/Examples/Complete/Camera/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/Camera/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj index de55dd0fc..f566c3cb9 100644 --- a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj +++ b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.Deferred.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,38 +28,27 @@ 4 None None - true + True True False - false + False False - armeabi-v7a;x86_64 Xamarin False True - false - false - false - false - ..\..\..\..\bin\Debug\Examples\Deferred\Android\ - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true - false - false - false - false - ..\..\..\..\bin\Release\Examples\Deferred\Android\ + True diff --git a/Examples/Complete/Deferred/Android/Properties/AndroidManifest.xml b/Examples/Complete/Deferred/Android/Properties/AndroidManifest.xml index 2344cdcad..5a37c538d 100644 --- a/Examples/Complete/Deferred/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/Deferred/Android/Properties/AndroidManifest.xml @@ -1,11 +1,6 @@  - - - - - - + - + \ No newline at end of file diff --git a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj index 7bcb8c717..5724dfe18 100644 --- a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj +++ b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.GeometryEditing.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a,x86 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/GeometryEditing/Android/Properties/AndroidManifest.xml b/Examples/Complete/GeometryEditing/Android/Properties/AndroidManifest.xml index 7b2d0a098..d1be16535 100644 --- a/Examples/Complete/GeometryEditing/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/GeometryEditing/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj index b97bda64b..43c53d92c 100644 --- a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj +++ b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.Materials.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,37 +28,27 @@ 4 None None - true + True True False - false + False False - armeabi-v7a;x86_64 Xamarin False True - false - false - false - ..\..\..\..\bin\Debug\Examples\Materials\Android\ - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true - false - false - false - false - ..\..\..\..\bin\Release\Examples\Materials\Android\ + True diff --git a/Examples/Complete/Materials/Android/Properties/AndroidManifest.xml b/Examples/Complete/Materials/Android/Properties/AndroidManifest.xml index f1081c3ee..1a28431ec 100644 --- a/Examples/Complete/Materials/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/Materials/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj index 948920ac7..fe900ae32 100644 --- a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj +++ b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.MeshingAround.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a,x86 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/MeshingAround/Android/Properties/AndroidManifest.xml b/Examples/Complete/MeshingAround/Android/Properties/AndroidManifest.xml index e6f4950de..34a296837 100644 --- a/Examples/Complete/MeshingAround/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/MeshingAround/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj index c6209f590..2be8ea064 100644 --- a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj +++ b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.Picking.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/Picking/Android/Properties/AndroidManifest.xml b/Examples/Complete/Picking/Android/Properties/AndroidManifest.xml index fe5990574..26a96b96b 100644 --- a/Examples/Complete/Picking/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/Picking/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj index fbb2d6125..4202f8bb7 100644 --- a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj +++ b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj @@ -1,12 +1,12 @@ - + - Fusee.Examples.Picking.Android - Fusee.Examples.Picking.Android + Fusee.Examples.PickingRayCast.Android + Fusee.Examples.PickingRayCast.Android Debug AnyCPU ..\..\..\..\bin\$(Configuration) - $(BaseOutputPath)\Examples\Picking\Android + $(BaseOutputPath)\Examples\PickingRayCast\Android 8.0.30703 2.0 {027608D0-ACAF-488E-AA8B-F2BA547CBC71} @@ -16,41 +16,39 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 True - full + portable False DEBUG;TRACE;PLATFORM_ANDROID prompt 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin False True - ..\..\..\..\bin\Debug\Examples\PickingRayCast\Android\ - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/PickingRayCast/Android/Properties/AndroidManifest.xml b/Examples/Complete/PickingRayCast/Android/Properties/AndroidManifest.xml index 01463fb50..c95f65ca7 100644 --- a/Examples/Complete/PickingRayCast/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/PickingRayCast/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + - + \ No newline at end of file diff --git a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj index 04e85d918..63757f7b8 100644 --- a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj +++ b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.RenderContextOnly.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,29 +28,27 @@ 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin False True - ..\..\..\..\bin\Debug\Libraries\Fusee.Examples.RenderContextOnly.Android\ - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/RenderContextOnly/Android/Properties/AndroidManifest.xml b/Examples/Complete/RenderContextOnly/Android/Properties/AndroidManifest.xml index a466b7b93..8d9697f7a 100644 --- a/Examples/Complete/RenderContextOnly/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/RenderContextOnly/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + - + \ No newline at end of file diff --git a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj index 4edfa13bd..bd04e7a5c 100644 --- a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj +++ b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.RenderLayer.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/RenderLayer/Android/Properties/AndroidManifest.xml b/Examples/Complete/RenderLayer/Android/Properties/AndroidManifest.xml index a466b7b93..e32b6f66e 100644 --- a/Examples/Complete/RenderLayer/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/RenderLayer/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + - + \ No newline at end of file diff --git a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj index 4234dc47b..8b99d5127 100644 --- a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj +++ b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj @@ -1,10 +1,10 @@ - + Fusee.Examples.Simple.Android Fusee.Examples.Simple.Android - Debug - AnyCPU + Debug + AnyCPU ..\..\..\..\bin\$(Configuration) $(BaseOutputPath)\Examples\Simple\Android 8.0.30703 @@ -14,42 +14,41 @@ Library Properties 512 + True + True + Resources\Resource.designer.cs + Resource + Off + false + v13.0 Properties\AndroidManifest.xml - Resources\Resource.Designer.cs - true - v11.0 + Resources + Assets + true + true + Xamarin.Android.Net.AndroidClientHandler True portable False DEBUG;TRACE;PLATFORM_ANDROID - prompt - 4 - None - None - true - True - - False - False - False - armeabi-v7a;x86_64 - - - Xamarin - False - True + prompt + 4 + None + False - pdbonly - True + True + portable + True TRACE;PLATFORM_ANDROID - prompt - 4 - False - Full - true + prompt + 4 + aab + true + SdkOnly + True diff --git a/Examples/Complete/Simple/Android/Properties/AndroidManifest.xml b/Examples/Complete/Simple/Android/Properties/AndroidManifest.xml index a466b7b93..3811d918d 100644 --- a/Examples/Complete/Simple/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/Simple/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj index 770f87987..0e4a01f70 100644 --- a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj +++ b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.ThreeDFont.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a,x86 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/ThreeDFont/Android/Properties/AndroidManifest.xml b/Examples/Complete/ThreeDFont/Android/Properties/AndroidManifest.xml index 5f049f3b5..9ec8cfc81 100644 --- a/Examples/Complete/ThreeDFont/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/ThreeDFont/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj index 0a2aef980..c4c5194da 100644 --- a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj +++ b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Examples.UI.Android @@ -16,7 +16,7 @@ 512 Properties\AndroidManifest.xml Resources\Resource.Designer.cs - true + True v11.0 @@ -28,13 +28,12 @@ 4 None None - true + True True False False False - armeabi-v7a;x86_64 Xamarin @@ -42,14 +41,14 @@ True - pdbonly + portable True TRACE;PLATFORM_ANDROID prompt 4 False Full - true + True diff --git a/Examples/Complete/UI/Android/Properties/AndroidManifest.xml b/Examples/Complete/UI/Android/Properties/AndroidManifest.xml index a4357ce1d..b72fdf319 100644 --- a/Examples/Complete/UI/Android/Properties/AndroidManifest.xml +++ b/Examples/Complete/UI/Android/Properties/AndroidManifest.xml @@ -1,8 +1,6 @@  - - - + \ No newline at end of file diff --git a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj index a3291f7b2..69bb96a96 100644 --- a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj +++ b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj @@ -41,7 +41,7 @@ - pdbonly + portable True $(DefineConstants);PLATFORM_ANDROID prompt @@ -49,7 +49,6 @@ False Full true - armeabi-v7a;x86;x86_64;arm64-v8a @@ -88,7 +87,7 @@ {1228EB3F-8BCC-453F-8A2E-D9246495A118} Fusee.Engine.Core - + {26808b4a-9f15-47f0-b147-e744241b79d2} Fusee.Engine.Gui diff --git a/src/Engine/Player/Android/Properties/AndroidManifest.xml b/src/Engine/Player/Android/Properties/AndroidManifest.xml index 6b8422464..727d568a7 100644 --- a/src/Engine/Player/Android/Properties/AndroidManifest.xml +++ b/src/Engine/Player/Android/Properties/AndroidManifest.xml @@ -1,9 +1,6 @@  - - - - + \ No newline at end of file From 257b0a32bbccf71898f46c912d6c4b8064839b28 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Mon, 22 May 2023 18:19:54 +0200 Subject: [PATCH 211/294] CI: only lint style --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2283f69d2..207783d77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - name: dotnet restore solution run: dotnet restore Fusee.sln - name: dotnet format solution - run: dotnet format Fusee.sln + run: dotnet format style Fusee.sln - name: Commiting changes uses: stefanzweifel/git-auto-commit-action@v4 with: From ee5af38441ad93a9a074b86d66fe22d4ae050b2d Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 23 May 2023 08:09:34 +0200 Subject: [PATCH 212/294] Automatic code cleanup --- .../PointCloudPotree2/Core/PointCloudPotree2Core.cs | 2 -- src/Base/Core/MemoryCache.cs | 6 +++--- .../Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 2 +- src/PointCloud/Common/IPointWriter.cs | 2 -- src/PointCloud/Core/Scene/PointCloudPickerModule.cs | 4 ++-- src/PointCloud/Potree/Potree2CloudDynamic.cs | 2 +- src/PointCloud/Potree/V2/Potree2Reader.cs | 2 -- 7 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index 8412f08db..daac21f6d 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -5,12 +5,10 @@ using Fusee.Math.Core; using Fusee.PointCloud.Common; using Fusee.PointCloud.Core.Scene; -using Fusee.PointCloud.Potree; using Fusee.PointCloud.Potree.V2; using Fusee.PointCloud.Potree.V2.Data; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; diff --git a/src/Base/Core/MemoryCache.cs b/src/Base/Core/MemoryCache.cs index 2d3463583..a5d40387e 100644 --- a/src/Base/Core/MemoryCache.cs +++ b/src/Base/Core/MemoryCache.cs @@ -1,10 +1,10 @@ using Microsoft.Extensions.Caching.Memory; using System; -using System.Collections.Generic; -using System.Reflection.Emit; -using System.Reflection; using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; namespace Fusee.Base.Core { diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index eecc577e3..7f98a009d 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -378,7 +378,7 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; })) { - if(!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0]) && CurrentOpenFolder != null) + if (!string.IsNullOrEmpty(selectedFile) && !char.IsWhiteSpace(selectedFile[0]) && CurrentOpenFolder != null) SelectedFile = new FileInfo(Path.Combine(CurrentOpenFolder.FullName, selectedFile)); } diff --git a/src/PointCloud/Common/IPointWriter.cs b/src/PointCloud/Common/IPointWriter.cs index ca31ddb8c..0898f7c06 100644 --- a/src/PointCloud/Common/IPointWriter.cs +++ b/src/PointCloud/Common/IPointWriter.cs @@ -1,8 +1,6 @@ using Fusee.Math.Core; -using System; using System.IO; using System.Text; -using System.Threading.Tasks; namespace Fusee.PointCloud.Common { diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index 8bb4bd8e2..cb7491b1f 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -118,7 +118,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) }); if (currentRes == null || currentRes.Count == 0) return; - + var mvp = proj * view * _state.Model; @@ -135,7 +135,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) VertIdx = r.VertIdx, OctantId = r.OctantId }; - + PickResults.Add(pickRes); } } diff --git a/src/PointCloud/Potree/Potree2CloudDynamic.cs b/src/PointCloud/Potree/Potree2CloudDynamic.cs index dc46e3bad..74f28d238 100644 --- a/src/PointCloud/Potree/Potree2CloudDynamic.cs +++ b/src/PointCloud/Potree/Potree2CloudDynamic.cs @@ -131,7 +131,7 @@ public void UpdateGpuDataCache(ref IEnumerable meshes, MemoryOwner Date: Tue, 23 May 2023 11:44:24 +0200 Subject: [PATCH 213/294] Close: #763, fix FilePicker folder input --- .../Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs index 7f98a009d..c12f4f1e7 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFilePicker.cs @@ -259,7 +259,11 @@ public virtual unsafe void Draw(ref bool filePickerOpen) return 0; }); - if (!Directory.Exists(currentFolder)) + if (Directory.Exists(currentFolder)) + { + CurrentOpenFolder = new DirectoryInfo(currentFolder); + } + else { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); ImGui.BeginTooltip(); From 936577e34d2aee4d538987419a2b0065726dc01a Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 23 May 2023 13:40:56 +0200 Subject: [PATCH 214/294] Re-added netstandard2.1 for Pointcloud.Potree --- src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index eacb301c6..7300ceb4d 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -1,7 +1,7 @@ - net7.0 + netstandard2.1;net7.0 $(OutputPath)\$(RootNamespace).xml enable From 8727b3252f1ded2317ef9c39cf925a4fde943090 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 23 May 2023 15:52:53 +0200 Subject: [PATCH 215/294] Fixed PointPicking Added Distance to ray value Fixed precision (0.5f * spacing) --- .../Core/PointCloudPotree2Core.cs | 16 +++++++++++++--- .../Core/Scene/PointCloudPickerModule.cs | 8 +++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs index daac21f6d..479d52512 100644 --- a/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs +++ b/Examples/Complete/PointCloudPotree2/Core/PointCloudPotree2Core.cs @@ -287,10 +287,20 @@ public void Update(bool allowInput) { var size = RenderToTexture ? ExternalCanvasSize : new int2(_rc.ViewportWidth, _rc.ViewportHeight); var mousePos = RenderToTexture ? ExternalMousePosition : Input.Mouse.Position; - var result = _picker?.Pick(mousePos, size.x, size.y).ToList(); - if (result != null && result.Count > 0 && result[0] is PointCloudPickResult ppr) + var result = _picker?.Pick(mousePos, size.x, size.y).Where(x => x is PointCloudPickResult).Cast(); + if (result != null && result.Any()) { - _pickResultTransform.Translation = ppr.Mesh.Vertices[ppr.VertIdx]; + var minElement = result.FirstOrDefault(); + + // get min x/y distance point + foreach (var r in result) + { + if (r.DistanceToRay.x < minElement.DistanceToRay.x && r.DistanceToRay.y < minElement.DistanceToRay.y) + { + minElement = r; + } + } + _pickResultTransform.Translation = minElement.Mesh.Vertices[minElement.VertIdx]; } } diff --git a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs index cb7491b1f..a046c826e 100644 --- a/src/PointCloud/Core/Scene/PointCloudPickerModule.cs +++ b/src/PointCloud/Core/Scene/PointCloudPickerModule.cs @@ -32,6 +32,11 @@ public class PointCloudPickResult : PickResult /// The of the in which the found point lies. /// public OctantId OctantId; + + /// + /// The distance between ray (origin: mouse position) and hit result (point) + /// + public float2 DistanceToRay; } /// @@ -99,7 +104,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) for (var i = 0; i < mesh.Vertices.Length; i++) { - var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], _pointSpacing); + var dist = SphereRayIntersection((float3)rayD.Origin, (float3)rayD.Direction, mesh.Vertices[i], _pointSpacing * 0.5f); if (dist.x < 0 || dist.y < 0) continue; if (dist.x <= currentMin.Distance.x && dist.y <= currentMin.Distance.y) @@ -131,6 +136,7 @@ public void RenderPointCloud(PointCloudComponent pointCloud) View = view, Model = _state.Model, ClipPos = float4x4.TransformPerspective(mvp, r.Mesh.Vertices[r.VertIdx]), + DistanceToRay = r.Distance, Mesh = r.Mesh, VertIdx = r.VertIdx, OctantId = r.OctantId From 204024b67c005a33237074953ab32d626c46062e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 02:58:30 +0000 Subject: [PATCH 216/294] Bump CommunityToolkit.HighPerformance from 8.1.0 to 8.2.0 Bumps [CommunityToolkit.HighPerformance](https://github.com/CommunityToolkit/dotnet) from 8.1.0 to 8.2.0. - [Release notes](https://github.com/CommunityToolkit/dotnet/releases) - [Commits](https://github.com/CommunityToolkit/dotnet/compare/v8.1.0...v8.2.0) --- updated-dependencies: - dependency-name: CommunityToolkit.HighPerformance dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/Engine/Core/Fusee.Engine.Core.csproj | 4 ++-- src/PointCloud/Common/Fusee.PointCloud.Common.csproj | 4 ++-- src/PointCloud/Core/Fusee.PointCloud.Core.csproj | 4 ++-- src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 22d12099d..9660822c8 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -9,7 +9,7 @@ - + diff --git a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj index d67dfe2cf..a69ee5fef 100644 --- a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj +++ b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -16,7 +16,7 @@ - + diff --git a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj index d43a732d0..4841e4642 100644 --- a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj +++ b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -7,7 +7,7 @@ - + diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index 7300ceb4d..8f3d7e282 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -7,7 +7,7 @@ - + From cb595fe7582f4acbb90d22fb82e4702ca9a9eb24 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Wed, 24 May 2023 14:17:37 +0200 Subject: [PATCH 217/294] RenderContextImp Desktop: implemented SetCursor --- src/Engine/Common/RCEnums.cs | 2 ++ .../Imp/Graphics/Desktop/RenderCanvasImp.cs | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Engine/Common/RCEnums.cs b/src/Engine/Common/RCEnums.cs index 5b12d0fd0..7b7514287 100644 --- a/src/Engine/Common/RCEnums.cs +++ b/src/Engine/Common/RCEnums.cs @@ -491,6 +491,8 @@ public enum CursorType : int #pragma warning disable 1591 Standard, Hand, + HResize, + VResize #pragma warning restore 1591 } diff --git a/src/Engine/Imp/Graphics/Desktop/RenderCanvasImp.cs b/src/Engine/Imp/Graphics/Desktop/RenderCanvasImp.cs index 7564bcaa9..5d801aab5 100644 --- a/src/Engine/Imp/Graphics/Desktop/RenderCanvasImp.cs +++ b/src/Engine/Imp/Graphics/Desktop/RenderCanvasImp.cs @@ -461,7 +461,23 @@ public virtual void Present() /// The type of the cursor to set. public void SetCursor(CursorType cursorType) { - // Currently not supported by OpenTK... Too bad. + switch (cursorType) + { + case CursorType.Standard: + _gameWindow.Cursor = MouseCursor.Default; + break; + case CursorType.Hand: + _gameWindow.Cursor = MouseCursor.Hand; + break; + case CursorType.HResize: + _gameWindow.Cursor = MouseCursor.HResize; + break; + case CursorType.VResize: + _gameWindow.Cursor = MouseCursor.VResize; + break; + default: + break; + } } /// From 46294356802fd05cc542ba4355226e75af0e5415 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 02:58:11 +0000 Subject: [PATCH 218/294] Bump CommunityToolkit.Diagnostics from 8.1.0 to 8.2.0 Bumps [CommunityToolkit.Diagnostics](https://github.com/CommunityToolkit/dotnet) from 8.1.0 to 8.2.0. - [Release notes](https://github.com/CommunityToolkit/dotnet/releases) - [Commits](https://github.com/CommunityToolkit/dotnet/compare/v8.1.0...v8.2.0) --- updated-dependencies: - dependency-name: CommunityToolkit.Diagnostics dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/Engine/Core/Fusee.Engine.Core.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 22d12099d..892733226 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -10,7 +10,7 @@ - + From 88eb231fdc0b9661ee47b4770ec1e209f5eea430 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 02:58:18 +0000 Subject: [PATCH 219/294] Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .../Desktop/Fusee.Tests.AssetStorage.Desktop.csproj | 4 ++-- src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj | 4 ++-- src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj | 4 ++-- .../Scene/Components/Fusee.Tests.Scene.Components.csproj | 4 ++-- .../Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 4 ++-- src/Tests/Xene/Fusee.Tests.Xene.csproj | 4 ++-- src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj | 4 ++-- .../NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj b/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj index eec8b48bd..f5837265a 100644 --- a/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj +++ b/src/Tests/AssetStorage/Desktop/Fusee.Tests.AssetStorage.Desktop.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\AssetStorage\Desktop\ @@ -24,7 +24,7 @@ - + all diff --git a/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj b/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj index ab8275f5a..852fa6f7d 100644 --- a/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj +++ b/src/Tests/Math/Core/Fusee.Tests.Math.Core.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Math @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj index 78e2db8c8..8de380068 100644 --- a/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj +++ b/src/Tests/Render/Desktop/Fusee.Tests.Render.Desktop.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Render\Desktop\ @@ -26,7 +26,7 @@ - + diff --git a/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj b/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj index cbe6aafdd..6bd1f2b5a 100644 --- a/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj +++ b/src/Tests/Scene/Components/Fusee.Tests.Scene.Components.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -8,7 +8,7 @@ - + all diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index e00d0cf4d..9e2b83122 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Serialization\V1\ @@ -8,7 +8,7 @@ - + all diff --git a/src/Tests/Xene/Fusee.Tests.Xene.csproj b/src/Tests/Xene/Fusee.Tests.Xene.csproj index c98731b4b..7f412de9f 100644 --- a/src/Tests/Xene/Fusee.Tests.Xene.csproj +++ b/src/Tests/Xene/Fusee.Tests.Xene.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Xene\ @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj b/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj index 5ac3580d5..7ea93cbbb 100644 --- a/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj +++ b/src/Tests/Xirkit/Core/Fusee.Tests.Xirkit.Core.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Xirkit @@ -6,7 +6,7 @@ - + all diff --git a/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj b/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj index 559018ce8..cc837510d 100644 --- a/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj +++ b/src/Tests/Xirkit/NestedAccess/Fusee.Tests.Xirkit.NestedAccess.csproj @@ -1,4 +1,4 @@ - + net7.0 $(BaseOutputPath)\Tests\Xirkit\ @@ -6,7 +6,7 @@ - + all From 886cf7c667763b2bef46e723f2d35e2c360698e9 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Thu, 25 May 2023 11:54:52 +0200 Subject: [PATCH 220/294] Removed PotreeSettingsAttribute vector access --- src/PointCloud/Potree/V2/Data/PotreeMetadata.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs index aa9569b0d..af0f1e715 100644 --- a/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs +++ b/src/PointCloud/Potree/V2/Data/PotreeMetadata.cs @@ -38,13 +38,9 @@ public class PotreeSettingsAttribute [JsonProperty(PropertyName = "min")] public List MinList { get; set; } - [JsonIgnore] - public double3 Min => new(MinList[0], MinList[1], MinList[2]); [JsonProperty(PropertyName = "max")] public List MaxList { get; set; } - [JsonIgnore] - public double3 Max => new(MaxList[0], MaxList[1], MaxList[2]); [JsonIgnore] public int AttributeOffset { get; set; } From 88e02fed783da2d38d30c5285f805dc17ac34cc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 12:30:12 +0000 Subject: [PATCH 221/294] Bump CommunityToolkit.HighPerformance from 8.1.0 to 8.2.0 Bumps [CommunityToolkit.HighPerformance](https://github.com/CommunityToolkit/dotnet) from 8.1.0 to 8.2.0. - [Release notes](https://github.com/CommunityToolkit/dotnet/releases) - [Commits](https://github.com/CommunityToolkit/dotnet/compare/v8.1.0...v8.2.0) --- updated-dependencies: - dependency-name: CommunityToolkit.HighPerformance dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- src/Engine/Core/Fusee.Engine.Core.csproj | 2 +- src/PointCloud/Common/Fusee.PointCloud.Common.csproj | 4 ++-- src/PointCloud/Core/Fusee.PointCloud.Core.csproj | 4 ++-- src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Engine/Core/Fusee.Engine.Core.csproj b/src/Engine/Core/Fusee.Engine.Core.csproj index 892733226..5d148dd93 100644 --- a/src/Engine/Core/Fusee.Engine.Core.csproj +++ b/src/Engine/Core/Fusee.Engine.Core.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj index 7a43e2772..4b7fa29d5 100644 --- a/src/PointCloud/Common/Fusee.PointCloud.Common.csproj +++ b/src/PointCloud/Common/Fusee.PointCloud.Common.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -17,7 +17,7 @@ - + diff --git a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj index d43a732d0..4841e4642 100644 --- a/src/PointCloud/Core/Fusee.PointCloud.Core.csproj +++ b/src/PointCloud/Core/Fusee.PointCloud.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -7,7 +7,7 @@ - + diff --git a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj index 7300ceb4d..8f3d7e282 100644 --- a/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj +++ b/src/PointCloud/Potree/Fusee.PointCloud.Potree.csproj @@ -7,7 +7,7 @@ - + From 0409d11b939a04ad1bd283c8028ffda7cd348955 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 02:58:05 +0000 Subject: [PATCH 222/294] Bump protobuf-net from 3.2.16 to 3.2.26 Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) from 3.2.16 to 3.2.26. - [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md) - [Commits](https://github.com/protobuf-net/protobuf-net/commits) --- updated-dependencies: - dependency-name: protobuf-net dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Android/Fusee.Examples.AdvancedUI.Android.csproj | 2 +- .../Camera/Android/Fusee.Examples.Camera.Android.csproj | 2 +- .../Deferred/Android/Fusee.Examples.Deferred.Android.csproj | 2 +- .../Android/Fusee.Examples.GeometryEditing.Android.csproj | 2 +- .../Materials/Android/Fusee.Examples.Materials.Android.csproj | 2 +- .../Android/Fusee.Examples.MeshingAround.Android.csproj | 2 +- .../Picking/Android/Fusee.Examples.Picking.Android.csproj | 2 +- .../Android/Fusee.Examples.PickingRayCast.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderContextOnly.Android.csproj | 2 +- .../Android/Fusee.Examples.RenderLayer.Android.csproj | 2 +- .../Simple/Android/Fusee.Examples.Simple.Android.csproj | 2 +- .../Android/Fusee.Examples.ThreeDFont.Android.csproj | 2 +- Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj | 2 +- src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj | 4 ++-- src/Math/Core/Fusee.Math.Core.csproj | 4 ++-- src/Serialization/Fusee.Serialization.csproj | 4 ++-- .../Serialization/V1/Fusee.Tests.Serialization.V1.csproj | 2 +- 17 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj index cf29c0418..3f1ea1465 100644 --- a/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj +++ b/Examples/Complete/AdvancedUI/Android/Fusee.Examples.AdvancedUI.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj index 6cc0407a6..1c50b8b55 100644 --- a/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj +++ b/Examples/Complete/Camera/Android/Fusee.Examples.Camera.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj index f566c3cb9..aa488835c 100644 --- a/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj +++ b/Examples/Complete/Deferred/Android/Fusee.Examples.Deferred.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj index 5724dfe18..600563ba7 100644 --- a/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj +++ b/Examples/Complete/GeometryEditing/Android/Fusee.Examples.GeometryEditing.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj index 43c53d92c..95d41cb81 100644 --- a/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj +++ b/Examples/Complete/Materials/Android/Fusee.Examples.Materials.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj index fe900ae32..75dffe033 100644 --- a/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj +++ b/Examples/Complete/MeshingAround/Android/Fusee.Examples.MeshingAround.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj index 2be8ea064..4b6b0e47f 100644 --- a/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj +++ b/Examples/Complete/Picking/Android/Fusee.Examples.Picking.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj index 4202f8bb7..accde2a38 100644 --- a/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj +++ b/Examples/Complete/PickingRayCast/Android/Fusee.Examples.PickingRayCast.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj index 63757f7b8..32b1fef4b 100644 --- a/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj +++ b/Examples/Complete/RenderContextOnly/Android/Fusee.Examples.RenderContextOnly.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj index bd04e7a5c..b27b7f592 100644 --- a/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj +++ b/Examples/Complete/RenderLayer/Android/Fusee.Examples.RenderLayer.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj index 8b99d5127..3bd05f6f8 100644 --- a/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj +++ b/Examples/Complete/Simple/Android/Fusee.Examples.Simple.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj index 0e4a01f70..42a891732 100644 --- a/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj +++ b/Examples/Complete/ThreeDFont/Android/Fusee.Examples.ThreeDFont.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj index c4c5194da..c696e35b7 100644 --- a/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj +++ b/Examples/Complete/UI/Android/Fusee.Examples.UI.Android.csproj @@ -54,7 +54,7 @@ - + diff --git a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj index 69bb96a96..b9661dc3a 100644 --- a/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj +++ b/src/Engine/Player/Android/Fusee.Engine.Player.Android.csproj @@ -1,4 +1,4 @@ - + Fusee.Engine.Player.Android @@ -55,7 +55,7 @@ - + diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index d77c28b12..4f2d05555 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -13,7 +13,7 @@ - + \ No newline at end of file diff --git a/src/Serialization/Fusee.Serialization.csproj b/src/Serialization/Fusee.Serialization.csproj index 1c3e6fe49..9d5784b30 100644 --- a/src/Serialization/Fusee.Serialization.csproj +++ b/src/Serialization/Fusee.Serialization.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -19,7 +19,7 @@ analyzers - + \ No newline at end of file diff --git a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj index 9e2b83122..1305eae87 100644 --- a/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj +++ b/src/Tests/Serialization/V1/Fusee.Tests.Serialization.V1.csproj @@ -14,6 +14,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 9d7eb493287297d3abc03b214dfbf509827c0621 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Fri, 26 May 2023 13:19:03 +0200 Subject: [PATCH 223/294] Implemented folder creation --- .../Templates/ImGuiFolderPicker.cs | 145 +++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index a308d961b..e9898485a 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -56,6 +56,82 @@ public class ImGuiFolderPicker public string ParentFolderTxt = "Parent"; public string BackTxt = "Back"; + /// + /// Show a button which let's the user create a new folder at the current directory + /// + public bool ShowNewFolderButton + { + get => _showNewFolderButton; + set + { + if (value) + DriveSelectionWidth = 120; + else + DriveSelectionWidth = 100; + + _showNewFolderButton = value; + } + } + public string NewFolderButtonTxt = "\uf65e"; + + /// + /// Caption of the create new folder window + /// + public string CreateNewFolderTxt = "Create new folder"; + + /// + /// Caption of the create folder button + /// + public string CreateFolderTxt = "Create folder"; + + /// + /// Create new folder name hint txt + /// + public string CreateNewFolderHintTxt = "Insert folder name"; + + private bool _showNewFolderButton; + private bool _isNewFolderNameWindowOpen; + + // as we cannot use the property as ref, we need to check and set all variables every time + // so that, when we call if(IsNewFolderNameWindowOpen), the variables are being set properly, too even when the window itself was closed via 'x' + private bool IsNewFolderNameWindowOpen + { + get + { + if (_isNewFolderNameWindowOpen) + { + // push the folder window to the back + DoFocusPicker = false; + } + else + { + // reset text and reset windows + _createFolderException = null; + _newFolderName = ""; + DoFocusPicker = true; + } + return _isNewFolderNameWindowOpen; + } + set + { + _isNewFolderNameWindowOpen = value; + if (_isNewFolderNameWindowOpen) + { + // push the folder window to the back + DoFocusPicker = false; + } + else + { + // reset text and reset windows + _createFolderException = null; + _newFolderName = ""; + DoFocusPicker = true; + } + } + } + private string _newFolderName = ""; + private Exception? _createFolderException; + public DirectoryInfo? SelectedFolder { get; protected set; } public DirectoryInfo RootFolder { get; protected set; } @@ -69,7 +145,7 @@ public class ImGuiFolderPicker protected const float FolderTextInputWidth = 350; protected const float FileTextInputWidth = 300; - protected const float DriveSelectionWidth = 100; + protected static float DriveSelectionWidth = 100; protected const float BrowserHeight = 200; protected readonly Vector2 WindowPadding = new(15, 15); protected readonly Vector2 BottomButtonSize = new(55, 26); @@ -159,6 +235,8 @@ public ImGuiFolderPicker(DirectoryInfo? startingPath = null) CurrentOpenFolder = startingPath; StartingFolder = startingPath; SelectedFolder = startingPath; + + ShowNewFolderButton = true; } public virtual unsafe void Draw(ref bool filePickerOpen) @@ -211,6 +289,15 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.EndDisabled(); } + if (ShowNewFolderButton) + { + ImGui.SameLine(); + if (ImGui.Button($"{NewFolderButtonTxt}##{_folderPickerCount}", TopButtonSize)) + { + _isNewFolderNameWindowOpen = true; + } + } + if ((IntPtr)SymbolsFontPtr.NativePtr != IntPtr.Zero) ImGui.PopFont(); @@ -353,12 +440,68 @@ public virtual unsafe void Draw(ref bool filePickerOpen) ImGui.End(); + if (ShowNewFolderButton && IsNewFolderNameWindowOpen) + { + ImGui.SetNextWindowFocus(); + ImGui.Begin($"{CreateNewFolderTxt}##{_folderPickerCount}", ref _isNewFolderNameWindowOpen, ImGuiWindowFlags.Modal | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDocking); + ImGui.InputTextWithHint($"", $"{CreateNewFolderHintTxt}", ref _newFolderName, 400, ImGuiInputTextFlags.AutoSelectAll | ImGuiInputTextFlags.CallbackAlways, (x) => + { + var arr = _newFolderName.ToCharArray(); + + if (x->SelectionStart < x->SelectionEnd && x->SelectionStart >= 0 && x->SelectionEnd <= arr.Length) + { + var selectedText = arr[x->SelectionStart..x->SelectionEnd]; + if (selectedText != null) + ImGuiInputImp.CurrentlySelectedText = new string(selectedText); + } + + return 0; + }); + ImGui.SameLine(); + + if (ImGui.Button($"{CreateFolderTxt}")) + { + if (!string.IsNullOrEmpty(_newFolderName)) + { + try + { + Directory.CreateDirectory(Path.Combine(currentFolder, _newFolderName)); + } + catch (Exception ex) + { + _createFolderException = ex; + return; + + } + } + IsNewFolderNameWindowOpen = false; + } + + // display a possible exception during folder creation as a tooltip text + if (_createFolderException != null) + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(5, 5)); + var size = ImGui.CalcTextSize(_createFolderException?.Message); + ImGui.SetNextWindowSize(new Vector2(size.X / 4, -1)); + ImGui.BeginTooltip(); + ImGui.PushStyleColor(ImGuiCol.Text, WarningTextColor); + ImGui.TextWrapped(_createFolderException?.Message); + ImGui.PopStyleColor(); + ImGui.EndTooltip(); + ImGui.PopStyleVar(); + } + + ImGui.End(); + } + ImGui.PopStyleVar(2); ImGui.PopStyleColor(); return; } + + /// /// We differentiate between files and folders, as we want to print the folders first /// If we collect everything in one list all files and folders are being sorted alphabetically From 63980483b8a00a071225663d9250569b5dafaef3 Mon Sep 17 00:00:00 2001 From: Moritz Roetner Date: Tue, 30 May 2023 10:58:22 +0200 Subject: [PATCH 224/294] By default, disable folder creation button --- .../Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs index e9898485a..9cf13df5f 100644 --- a/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs +++ b/src/ImGui/Desktop/Fusee.ImGui.Desktop/Templates/ImGuiFolderPicker.cs @@ -235,8 +235,6 @@ public ImGuiFolderPicker(DirectoryInfo? startingPath = null) CurrentOpenFolder = startingPath; StartingFolder = startingPath; SelectedFolder = startingPath; - - ShowNewFolderButton = true; } public virtual unsafe void Draw(ref bool filePickerOpen) From a3b7b008854c4ed8e442d6380464ea3048910b16 Mon Sep 17 00:00:00 2001 From: Sarah Busert Date: Tue, 30 May 2023 14:29:49 +0200 Subject: [PATCH 225/294] Primitives Sphere: fixed rad --- src/Engine/Core/Primitives/Sphere.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Engine/Core/Primitives/Sphere.cs b/src/Engine/Core/Primitives/Sphere.cs index c3ae29624..22acf4ffe 100644 --- a/src/Engine/Core/Primitives/Sphere.cs +++ b/src/Engine/Core/Primitives/Sphere.cs @@ -1,4 +1,4 @@ -using CommunityToolkit.Diagnostics; +using CommunityToolkit.Diagnostics; using Fusee.Engine.Core.Scene; using Fusee.Math.Core; @@ -22,7 +22,7 @@ public Sphere(int segments, int rings) private void BuildSphere(int segments, int rings)// segments: Longitude ||| - rings: Latitude --- { - const float radius = 1f; + const float radius = 0.5f; const double pi = System.Math.PI; const double twoPi = pi * 2f; From b15e111028bb65aa346ae7e3ae1cfd0b23fccf24 Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 31 May 2023 14:43:20 +0200 Subject: [PATCH 226/294] Updated .editorconfig (defaults) --- .editorconfig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.editorconfig b/.editorconfig index ed3ef24af..a04fb084e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -199,3 +199,22 @@ dotnet_naming_style.begins_with_i.required_prefix = I dotnet_naming_style.begins_with_i.required_suffix = dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion \ No newline at end of file From c04ac6979308ed4852c683c2e0171bd79a1548ba Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 31 May 2023 14:44:05 +0200 Subject: [PATCH 227/294] Changed default Dependabot branch to housekeeping --- .github/dependabot.yml | 2 +- Fusee.sln | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 759d4a1dd..59ff30570 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,6 @@ updates: directory: "/" # Location of package manifests schedule: interval: "daily" - target-branch: "develop" + target-branch: "housekeeping" ignore: - dependency-name: "Google.Protobuf.Tools" diff --git a/Fusee.sln b/Fusee.sln index 62665bb4d..4b4fb1da9 100644 --- a/Fusee.sln +++ b/Fusee.sln @@ -65,6 +65,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore BuildNuget.cmd = BuildNuget.cmd .github\workflows\ci.yml = .github\workflows\ci.yml + .github\dependabot.yml = .github\dependabot.yml Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets NuGet.config = NuGet.config From be8e3e4043184d143ba63d97c93cee86960d224d Mon Sep 17 00:00:00 2001 From: Alexander Scheurer Date: Wed, 31 May 2023 14:44:29 +0200 Subject: [PATCH 228/294] Added ruleset default configuration --- Directory.Build.props | 2 ++ Fusee.ruleset | 2 ++ Fusee.sln | 1 + 3 files changed, 5 insertions(+) create mode 100644 Fusee.ruleset diff --git a/Directory.Build.props b/Directory.Build.props index 7748f48f0..dc943590b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,6 +33,8 @@ $(FuseeEngineRoot)\bin\Release\nuget + $(FuseeEngineRoot)\Fusee.ruleset + + + + + + + \ No newline at end of file diff --git a/Fusee.sln b/Fusee.sln index 72e2fd48b..7b6e236c8 100644 --- a/Fusee.sln +++ b/Fusee.sln @@ -69,6 +69,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Fusee.ruleset = Fusee.ruleset + LICENSE = LICENSE + LICENSE-THIRD-PARTY = LICENSE-THIRD-PARTY NuGet.config = NuGet.config EndProjectSection EndProject diff --git a/LICENSE-THIRD-PARTY b/LICENSE-THIRD-PARTY index 89602ef2a..daf2af8e2 100644 --- a/LICENSE-THIRD-PARTY +++ b/LICENSE-THIRD-PARTY @@ -8,6 +8,7 @@ components are released under: - Json.NET - https://github.com/JamesNK/Newtonsoft.Json - MIT - LASlib - https://github.com/LAStools/LAStools/tree/master/LASlib - LGPL - Lato - https://fonts.google.com/specimen/Lato - SIL Open Font License 1.1 +- Math.NET - https://github.com/mathnet/mathnet-numerics - MIT - OpenTK - http://www.opentk.com/ - MIT - Potree - https://github.com/potree/potree - BSD 2-Clause "Simplified" License - Protobuf - https://code.google.com/p/protobuf/ - BSD New/Simplified diff --git a/src/Math/Core/Fusee.Math.Core.csproj b/src/Math/Core/Fusee.Math.Core.csproj index 4f2d05555..620390a2f 100644 --- a/src/Math/Core/Fusee.Math.Core.csproj +++ b/src/Math/Core/Fusee.Math.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.1;net7.0 @@ -16,4 +16,8 @@ + + + + \ No newline at end of file diff --git a/src/Math/Core/MathNetExtensions.cs b/src/Math/Core/MathNetExtensions.cs new file mode 100644 index 000000000..b86862e84 --- /dev/null +++ b/src/Math/Core/MathNetExtensions.cs @@ -0,0 +1,240 @@ +#if MathNet + +using System; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Fusee.Math.Core +{ + /// + /// Vector extensions for MathNet compatibility + /// + public static class MathNetVectorExtension + { + #region FuseeToMathNet + + public static MathNet.Numerics.LinearAlgebra.Single.DenseVector ToMathNetSingleVector(this float3 f3) + + { + float[] f = new float[] { f3.x, f3.y, f3.z }; + return new MathNet.Numerics.LinearAlgebra.Single.DenseVector(f); + } + + public static MathNet.Numerics.LinearAlgebra.Single.DenseVector ToMathNetSingleVector(this double3 d3) + { + return ToMathNetSingleVector((float3)d3); + } + + public static MathNet.Numerics.LinearAlgebra.Double.DenseVector ToMathNetDoubleVector(this double3 d3) + { + double[] d = new double[] { d3.x, d3.y, d3.z }; + + return new MathNet.Numerics.LinearAlgebra.Double.DenseVector(d); + } + + public static MathNet.Numerics.LinearAlgebra.Double.DenseVector ToMathNetDoubleVector(this float3 f3) + { + return ToMathNetDoubleVector(f3); + } + + #endregion FuseeToMathNet + + #region MathNetToFusee + + public static float3 ToFuseeSingleVector(this MathNet.Numerics.LinearAlgebra.Single.DenseVector sdv) + { + if (sdv.Values.Length != 3) + throw new ArgumentException(); + + var f = sdv.Storage.AsArray(); + + return new float3(f[0], f[1], f[2]); + } + + public static float3 ToFuseeSingleVector(this MathNet.Numerics.LinearAlgebra.Double.DenseVector ddv) + { + if (ddv.Values.Length != 3) + throw new ArgumentException(); + + var d = ddv.Storage.AsArray(); + + return new float3((float)d[0], (float)d[1], (float)d[2]); + } + + public static double3 ToFuseeDoubleVector(this MathNet.Numerics.LinearAlgebra.Single.DenseVector sdv) + { + if (sdv.Values.Length != 3) + throw new ArgumentException(); + + var f = sdv.Storage.AsArray(); + + return new double3(f[0], f[1], f[2]); + } + + public static double3 ToFuseeDoubleVector(this MathNet.Numerics.LinearAlgebra.Double.DenseVector ddv) + { + if (ddv.Values.Length != 3) + throw new ArgumentException(); + + var d = ddv.Storage.AsArray(); + + return new double3(d[0], d[1], d[2]); + } + + #endregion MathNetToFusee + } + + /// + /// Matrix extensions for MathNet compatibility + /// + public static class MathNetMatrixExtension + { + #region FuseeToMathNet + + public static MathNet.Numerics.LinearAlgebra.Single.DenseMatrix ToMathNetSingleMatrix(this float4x4 f4x4) + { + float[] f = new float[] { f4x4.M11, f4x4.M21, f4x4.M31, f4x4.M41, + f4x4.M12, f4x4.M22, f4x4.M32, f4x4.M42, + f4x4.M13, f4x4.M23, f4x4.M33, f4x4.M43, + f4x4.M14, f4x4.M24, f4x4.M34, f4x4.M44 + }; + + return new MathNet.Numerics.LinearAlgebra.Single.DenseMatrix(4, 4, f); + } + + public static MathNet.Numerics.LinearAlgebra.Single.DenseMatrix ToMathNetSingleMatrix(this double4x4 d4x4) + { + return ToMathNetSingleMatrix((float4x4)d4x4); + } + + public static MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ToMathNetDoubleMatrix(this double4x4 d4x4) + { + double[] f = new double[] { d4x4.M11, d4x4.M21, d4x4.M31, d4x4.M41, + d4x4.M12, d4x4.M22, d4x4.M32, d4x4.M42, + d4x4.M13, d4x4.M23, d4x4.M33, d4x4.M43, + d4x4.M14, d4x4.M24, d4x4.M34, d4x4.M44 + }; + + return new MathNet.Numerics.LinearAlgebra.Double.DenseMatrix(4, 4, f); + } + + public static MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ToMathNetDoubleMatrix(this float4x4 f4x4) + { + return ToMathNetDoubleMatrix((double4x4)f4x4); + } + + #endregion FuseeToMathNet + + #region MathNetToFusee + + public static float4x4 ToFuseeSingleMatrix(this MathNet.Numerics.LinearAlgebra.Single.DenseMatrix sdm) + { + if (sdm.Values.Length != 16 || sdm.ColumnCount != 4 || sdm.RowCount != 4) + throw new ArgumentException(); + + return new float4x4 + { + M11 = sdm.Values[0], + M21 = sdm.Values[1], + M31 = sdm.Values[2], + M41 = sdm.Values[3], + M12 = sdm.Values[4], + M22 = sdm.Values[5], + M32 = sdm.Values[6], + M42 = sdm.Values[7], + M13 = sdm.Values[8], + M23 = sdm.Values[9], + M33 = sdm.Values[10], + M43 = sdm.Values[11], + M14 = sdm.Values[12], + M24 = sdm.Values[13], + M34 = sdm.Values[14], + M44 = sdm.Values[15] + }; + } + + public static float4x4 ToFuseeSingleMatrix(this MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ddm) + { + if (ddm.Values.Length != 16 || ddm.ColumnCount != 4 || ddm.RowCount != 4) + throw new ArgumentException(); + + return new float4x4 + { + M11 = (float)ddm.Values[0], + M21 = (float)ddm.Values[1], + M31 = (float)ddm.Values[2], + M41 = (float)ddm.Values[3], + M12 = (float)ddm.Values[4], + M22 = (float)ddm.Values[5], + M32 = (float)ddm.Values[6], + M42 = (float)ddm.Values[7], + M13 = (float)ddm.Values[8], + M23 = (float)ddm.Values[9], + M33 = (float)ddm.Values[10], + M43 = (float)ddm.Values[11], + M14 = (float)ddm.Values[12], + M24 = (float)ddm.Values[13], + M34 = (float)ddm.Values[14], + M44 = (float)ddm.Values[15] + }; + } + + public static double4x4 ToFuseeDoubleMatrix(this MathNet.Numerics.LinearAlgebra.Single.DenseMatrix sdm) + { + if (sdm.Values.Length != 16 || sdm.ColumnCount != 4 || sdm.RowCount != 4) + throw new ArgumentException(); + + return new double4x4 + { + M11 = sdm.Values[0], + M21 = sdm.Values[1], + M31 = sdm.Values[2], + M41 = sdm.Values[3], + M12 = sdm.Values[4], + M22 = sdm.Values[5], + M32 = sdm.Values[6], + M42 = sdm.Values[7], + M13 = sdm.Values[8], + M23 = sdm.Values[9], + M33 = sdm.Values[10], + M43 = sdm.Values[11], + M14 = sdm.Values[12], + M24 = sdm.Values[13], + M34 = sdm.Values[14], + M44 = sdm.Values[15] + }; + } + + public static double4x4 ToFuseeDoubleMatrix(this MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ddm) + { + if (ddm.Values.Length != 16 || ddm.ColumnCount != 4 || ddm.RowCount != 4) + throw new ArgumentException(); + + return new double4x4 + { + M11 = ddm.Values[0], + M21 = ddm.Values[1], + M31 = ddm.Values[2], + M41 = ddm.Values[3], + M12 = ddm.Values[4], + M22 = ddm.Values[5], + M32 = ddm.Values[6], + M42 = ddm.Values[7], + M13 = ddm.Values[8], + M23 = ddm.Values[9], + M33 = ddm.Values[10], + M43 = ddm.Values[11], + M14 = ddm.Values[12], + M24 = ddm.Values[13], + M34 = ddm.Values[14], + M44 = ddm.Values[15] + }; + } + + #endregion MathNetToFusee + } +} + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member + +#endif \ No newline at end of file diff --git a/src/Math/Core/double3.cs b/src/Math/Core/double3.cs index 2da6bbbc9..c20988bf7 100644 --- a/src/Math/Core/double3.cs +++ b/src/Math/Core/double3.cs @@ -1160,6 +1160,46 @@ public static explicit operator double3(float3 d3) return new double3(d3); } +#if MathNet + + /// + /// Explicit cast operator to cast a MathNet Single DenseVector into a double3 value. + /// + /// + public static explicit operator double3(MathNet.Numerics.LinearAlgebra.Single.DenseVector sdv) + { + return sdv.ToFuseeDoubleVector(); + } + + /// + /// Explicit cast operator to cast a MathNet Double DenseVector into a double3 value. + /// + /// + public static explicit operator double3(MathNet.Numerics.LinearAlgebra.Double.DenseVector ddv) + { + return ddv.ToFuseeDoubleVector(); + } + + /// + /// Explicit cast operator to cast a double3 into a MathNet Single DenseVector value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Single.DenseVector(double3 d3) + { + return d3.ToMathNetSingleVector(); + } + + /// + /// Explicit cast operator to cast a double3 into a MathNet Double DenseVector value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Double.DenseVector(double3 d3) + { + return d3.ToMathNetDoubleVector(); + } + +#endif + #endregion Operators #region Overrides diff --git a/src/Math/Core/double4x4.cs b/src/Math/Core/double4x4.cs index 027db142f..2c60356a3 100644 --- a/src/Math/Core/double4x4.cs +++ b/src/Math/Core/double4x4.cs @@ -2336,6 +2336,46 @@ public static explicit operator double4x4(float4x4 d4x4) return new double4x4(d4x4); } +#if MathNet + + /// + /// Explicit cast operator to cast a MathNet Single DenseMatrix into a double4x4 value. + /// + /// + public static explicit operator double4x4(MathNet.Numerics.LinearAlgebra.Single.DenseMatrix sdm) + { + return sdm.ToFuseeDoubleMatrix(); + } + + /// + /// Explicit cast operator to cast a MathNet Double DenseMatrix into a double4x4 value. + /// + /// + public static explicit operator double4x4(MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ddm) + { + return ddm.ToFuseeDoubleMatrix(); + } + + /// + /// Explicit cast operator to cast a double4x4 into a MathNet Single DenseMatrix value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Single.DenseMatrix(double4x4 d4x4) + { + return d4x4.ToMathNetSingleMatrix(); + } + + /// + /// Explicit cast operator to cast a double4x4 into a MathNet Double DenseMatrix value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Double.DenseMatrix(double4x4 d4x4) + { + return d4x4.ToMathNetDoubleMatrix(); + } + +#endif + #endregion Operators #region Overrides diff --git a/src/Math/Core/float3.cs b/src/Math/Core/float3.cs index a0f49b238..8f242b4eb 100644 --- a/src/Math/Core/float3.cs +++ b/src/Math/Core/float3.cs @@ -1160,6 +1160,46 @@ public static explicit operator float3(double3 d3) return new float3(d3); } +#if MathNet + + /// + /// Explicit cast operator to cast a MathNet Single DenseVector into a float3 value. + /// + /// + public static explicit operator float3(MathNet.Numerics.LinearAlgebra.Single.DenseVector sdv) + { + return sdv.ToFuseeSingleVector(); + } + + /// + /// Explicit cast operator to cast a MathNet Double DenseVector into a float3 value. + /// + /// + public static explicit operator float3(MathNet.Numerics.LinearAlgebra.Double.DenseVector ddv) + { + return ddv.ToFuseeSingleVector(); + } + + /// + /// Explicit cast operator to cast a float3 into a MathNet Single DenseVector value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Single.DenseVector(float3 f3) + { + return f3.ToMathNetSingleVector(); + } + + /// + /// Explicit cast operator to cast a float3 into a MathNet Double DenseVector value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Double.DenseVector(float3 f3) + { + return f3.ToMathNetDoubleVector(); + } + +#endif + #endregion Operators #region Overrides diff --git a/src/Math/Core/float4x4.cs b/src/Math/Core/float4x4.cs index e66dd2abe..3ac39bd9f 100644 --- a/src/Math/Core/float4x4.cs +++ b/src/Math/Core/float4x4.cs @@ -2341,6 +2341,46 @@ public static explicit operator float4x4(double4x4 d4x4) return new float4x4(d4x4); } +#if MathNet + + /// + /// Explicit cast operator to cast a MathNet Single DenseMatrix into a float4x4 value. + /// + /// + public static explicit operator float4x4(MathNet.Numerics.LinearAlgebra.Single.DenseMatrix sdv) + { + return sdv.ToFuseeSingleMatrix(); + } + + /// + /// Explicit cast operator to cast a MathNet Double DenseMatrix into a float4x4 value. + /// + /// + public static explicit operator float4x4(MathNet.Numerics.LinearAlgebra.Double.DenseMatrix ddv) + { + return ddv.ToFuseeSingleMatrix(); + } + + /// + /// Explicit cast operator to cast a float4x4 into a MathNet Single DenseMatrix value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Single.DenseMatrix(float4x4 f3) + { + return f3.ToMathNetSingleMatrix(); + } + + /// + /// Explicit cast operator to cast a float4x4 into a MathNet Double DenseMatrix value. + /// + /// + public static explicit operator MathNet.Numerics.LinearAlgebra.Double.DenseMatrix(float4x4 f3) + { + return f3.ToMathNetDoubleMatrix(); + } + +#endif + #endregion Operators #region Overrides