From bc2a8450b6645f2be12ad1abc31ce7b48e2a0129 Mon Sep 17 00:00:00 2001 From: miks1965 Date: Tue, 22 Aug 2017 15:07:53 +0300 Subject: [PATCH] =?UTF-8?q?Graph3D=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/Lib/Graph3D.pas | 998 ++++++++++++++++++++++++++++++++++++++++++++ bin/TestRunner.exe | Bin 28160 -> 30208 bytes 2 files changed, 998 insertions(+) create mode 100644 bin/Lib/Graph3D.pas diff --git a/bin/Lib/Graph3D.pas b/bin/Lib/Graph3D.pas new file mode 100644 index 000000000..eb4adeb50 --- /dev/null +++ b/bin/Lib/Graph3D.pas @@ -0,0 +1,998 @@ +unit Graph3D; + +{$reference 'PresentationFramework.dll'} +{$reference 'WindowsBase.dll'} +{$reference 'PresentationCore.dll'} +{$reference System.Xml.dll} + +{$reference HelixToolkit.Wpf.dll} +{reference Petzold.Media3D.dll} + +{$apptype windows} + +uses System.Windows; +uses System.Windows.Controls; +uses System.Windows.Shapes; +uses System.Windows.Media; +uses System.Windows.Media.Animation; +uses System.Windows.Media.Media3D; + +uses System.Windows.Markup; +uses System.XML; +uses System.IO; +uses System.Threading; +uses System.Windows.Input; + +uses HelixToolkit.Wpf; +//uses Petzold.Media3D; + +type + Key = System.Windows.Input.Key; + Colors = System.Windows.Media.Colors; + GColor = System.Windows.Media.Color; + GMaterial = System.Windows.Media.Media3D.Material; + GCamera = System.Windows.Media.Media3D.ProjectionCamera; + GRect = System.Windows.Rect; + GWindow = System.Windows.Window; + CameraMode = HelixToolkit.Wpf.CameraMode; + TupleInt3 = (integer,integer,integer); + TupleReal3 = (real,real,real); + +var + MainFormThread: Thread; + app: Application; + MainWindow: Window; + hvp: HelixViewport3D; + gvl: GridLinesVisual3D; + /// Событие нажатия на кнопку мыши. (x,y) - координаты курсора мыши в момент наступления события, mousebutton = 1, если нажата левая кнопка мыши, и 2, если нажата правая кнопка мыши + OnMouseDown: procedure(x, y: real; mousebutton: integer); + /// Событие отжатия кнопки мыши. (x,y) - координаты курсора мыши в момент наступления события, mousebutton = 1, если отжата левая кнопка мыши, и 2, если отжата правая кнопка мыши + OnMouseUp: procedure(x, y: real; mousebutton: integer); + /// Событие перемещения мыши. (x,y) - координаты курсора мыши в момент наступления события, mousebutton = 0, если кнопка мыши не нажата, 1, если нажата левая кнопка мыши, и 2, если нажата правая кнопка мыши + OnMouseMove: procedure(x, y: real; mousebutton: integer); + /// Событие нажатия клавиши + OnKeyDown: procedure(k: Key); + /// Событие отжатия клавиши + OnKeyUp: procedure(k: Key); + +function RGB(r,g,b: byte) := Color.Fromrgb(r, g, b); +function ARGB(a,r,g,b: byte) := Color.FromArgb(a, r, g, b); +function P3D(x,y,z: real) := new Point3D(x,y,z); +function V3D(x,y,z: real) := new Vector3D(x,y,z); +function Sz3D(x,y,z: real) := new Size3D(x,y,z); +function Pnt(x,y: real) := new Point(x,y); +function Rect(x,y,w,h: real) := new System.Windows.Rect(x,y,w,h); + +function ChangeAlpha(Self: GColor; value: integer); extensionmethod := ARGB(value,Self.R, Self.G, Self.B); + +function MoveX(Self: Point3D; dx: real); extensionmethod := P3D(Self.x + dx, Self.y, Self.z); +function MoveY(Self: Point3D; dy: real); extensionmethod := P3D(Self.x, Self.y + dy, Self.z); +function MoveZ(Self: Point3D; dz: real); extensionmethod := P3D(Self.x, Self.y, Self.z + dz); +function Move(Self: Point3D; dx,dy,dz: real); extensionmethod := P3D(Self.x + dx, Self.y + dy, Self.z + dz); + +function operator implicit(t: TupleInt3): Point3D; extensionmethod := new Point3D(t[0],t[1],t[2]); +function operator implicit(t: TupleReal3): Point3D; extensionmethod := new Point3D(t[0],t[1],t[2]); + +function operator implicit(ar: array of TupleInt3): Point3DCollection; extensionmethod := new Point3DCollection(ar.Select(t->new Point3D(t[0],t[1],t[2]))); + +function RandomColor := Color.Fromrgb(Random(256), Random(256), Random(256)); + +function RandomSolidBrush := new SolidColorBrush(RandomColor); + +procedure Invoke(d: System.Delegate; params args: array of object) := app.Dispatcher.Invoke(d,args); +procedure Invoke(d: ()->()) := app.Dispatcher.Invoke(d); +function Invoke(d: Func0) := app.Dispatcher.Invoke&(d); + +function wplus := SystemParameters.WindowResizeBorderThickness.Left + SystemParameters.WindowResizeBorderThickness.Right; +function hplus := SystemParameters.WindowCaptionHeight + SystemParameters.WindowResizeBorderThickness.Top + SystemParameters.WindowResizeBorderThickness.Bottom; + +type MHelper = auto class + fname: string; + opacity: real; + function ImageMaterial := MaterialHelper.CreateImageMaterial(fname,opacity); +end; + +function ImageMaterial(fname: string): GMaterial + := Invoke&(MHelper.Create(fname,1).ImageMaterial); + +/// --- SystemKeyEvents +procedure SystemOnKeyDown(sender: Object; e: System.Windows.Input.KeyEventArgs) := +begin + if OnKeyDown<>nil then + OnKeyDown(e.Key); +end; + +procedure SystemOnKeyUp(sender: Object; e: System.Windows.Input.KeyEventArgs) := + if OnKeyUp<>nil then + OnKeyUp(e.Key); + +/// --- SystemMouseEvents +procedure SystemOnMouseDown(sender: Object; e: System.Windows.Input.MouseButtonEventArgs); +begin + var mb := 0; + var p := e.GetPosition(hvp); + if e.LeftButton = MouseButtonState.Pressed then + mb := 1 + else if e.RightButton = MouseButtonState.Pressed then + mb := 2; + if OnMouseDown <> nil then + OnMouseDown(p.x, p.y, mb); +end; + +procedure SystemOnMouseUp(sender: Object; e: MouseButtonEventArgs); +begin + var mb := 0; + var p := e.GetPosition(hvp); + if e.LeftButton = MouseButtonState.Pressed then + mb := 1 + else if e.RightButton = MouseButtonState.Pressed then + mb := 2; + if OnMouseUp <> nil then + OnMouseUp(p.x, p.y, mb); +end; + +procedure SystemOnMouseMove(sender: Object; e: MouseEventArgs); +begin + var mb := 0; + var p := e.GetPosition(hvp); + if e.LeftButton = MouseButtonState.Pressed then + mb := 1 + else if e.RightButton = MouseButtonState.Pressed then + mb := 2; + if OnMouseMove <> nil then + OnMouseMove(p.x, p.y, mb); +end; + +type +///!# + View3DT = class + private + procedure SetSCSP(v: boolean) := hvp.ShowCoordinateSystem := v; + procedure SetSCS(v: boolean) := Invoke(SetSCSP,v); + function GetSCS: boolean := Invoke&(()->hvp.ShowCoordinateSystem); + + procedure SetSGLP(v: boolean) := gvl.Visible := v; + procedure SetSGL(v: boolean) := Invoke(SetSGLP,v); + function GetSGL: boolean := Invoke&(()->gvl.Visible); + + procedure SetSCIP(v: boolean) := hvp.ShowCameraInfo := v; + procedure SetSCI(v: boolean) := Invoke(SetSCIP,v); + function GetSCI: boolean := Invoke&(()->hvp.ShowCameraInfo); + + procedure SetSVCP(v: boolean) := hvp.ShowViewCube := v; + procedure SetSVC(v: boolean) := Invoke(SetSVCP,v); + function GetSVC: boolean := Invoke&(()->hvp.ShowViewCube); + + procedure SetTP(v: string) := hvp.Title := v; + procedure SetT(v: string) := Invoke(SetTP,v); + function GetT: string := Invoke&(()->hvp.Title); + + procedure SetSTP(v: string) := hvp.SubTitle := v; + procedure SetST(v: string) := Invoke(SetSTP,v); + function GetST: string := Invoke&(()->hvp.SubTitle); + + procedure SetCMP(v: HelixToolkit.Wpf.CameraMode) := hvp.CameraMode := v; + procedure SetCM(v: HelixToolkit.Wpf.CameraMode) := Invoke(SetCMP,v); + function GetCM: HelixToolkit.Wpf.CameraMode := Invoke&(()->hvp.CameraMode); + + procedure SetBCP(v: GColor) := hvp.Background := new SolidColorBrush(v); + procedure SetBC(v: GColor) := Invoke(SetBCP,v); + function GetBC: GColor := Invoke&(()->(hvp.Background as SolidColorBrush).Color); + procedure ExportP(fname: string) := hvp.Viewport.Export(fname,hvp.Background); + public + property ShowCoordinateSystem: boolean read GetSCS write SetSCS; + property ShowGridLines: boolean read GetSGL write SetSGL; + property ShowCameraInfo: boolean read GetSCI write SetSCI; + property ShowViewCube: boolean read GetSVC write SetSVC; + property Title: string read GetT write SetT; + property SubTitle: string read GetST write SetST; + property CameraMode: HelixToolkit.Wpf.CameraMode read GetCM write SetCM; + property BackgroundColor: GColor read GetBC write SetBC; + + procedure Save(fname: string) := Invoke(ExportP,fname); + end; + + ///!# + CameraType = class + private + function Cam: GCamera := hvp.Camera; + procedure SetPP(p: Point3D) := begin Cam.Position := p; Cam.LookDirection := Cam.Position.Multiply(-1).ToVector3D; end; + procedure SetP(p: Point3D) := Invoke(SetPP,p); + function GetP: Point3D := Invoke&(()->Cam.Position); + procedure SetLDP(v: Vector3D) := Cam.LookDirection := v; + procedure SetLD(v: Vector3D) := Invoke(SetLDP,v); + function GetLD: Vector3D := Invoke&(()->Cam.LookDirection); + procedure SetUDP(v: Vector3D) := Cam.UpDirection := v; + procedure SetUD(v: Vector3D) := Invoke(SetUDP,v); + function GetUD: Vector3D := Invoke&(()->Cam.UpDirection); + procedure SetDP(d: real); + begin + var dist := Position.DistanceTo(P3D(0,0,0)); + Position := Position.Multiply(d/dist); + end; + procedure SetD(d: real) := Invoke(SetDP,d); + function GetD: real := Invoke&(()->Cam.Position.DistanceTo(P3D(0,0,0))); + public + property Position: Point3D read GetP write SetP; + property LookDirection: Vector3D read GetLD write SetLD; + property UpDirection: Vector3D read GetUD write SetUD; + property Distanse: real read GetD write SetD; + end; + + ///!# + WindowType = class + private + procedure SetLeft(l: real); + function GetLeft: real; + procedure SetTop(t: real); + function GetTop: real; + procedure SetWidth(w: real); + function GetWidth: real; + procedure SetHeight(h: real); + function GetHeight: real; + procedure SetCaption(c: string); + function GetCaption: string; + public + /// Отступ графического окна от левого края экрана в пикселах + property Left: real read GetLeft write SetLeft; + /// Отступ графического окна от верхнего края экрана в пикселах + property Top: real read GetTop write SetTop; + /// Ширина клиентской части графического окна в пикселах + property Width: real read GetWidth write SetWidth; + /// Высота клиентской части графического окна в пикселах + property Height: real read GetHeight write SetHeight; + /// Заголовок графического окна + property Caption: string read GetCaption write SetCaption; + /// Заголовок графического окна + property Title: string read GetCaption write SetCaption; + /// Устанавливает размеры клиентской части графического окна в пикселах + procedure SetSize(w, h: real); + /// Устанавливает отступ графического окна от левого верхнего края экрана в пикселах + procedure SetPos(l, t: real); + /// Закрывает графическое окно и завершает приложение + procedure Close; + /// Сворачивает графическое окно + procedure Minimize; + /// Максимизирует графическое окно + procedure Maximize; + /// Возвращает графическое окно к нормальному размеру + procedure Normalize; + /// Центрирует графическое окно по центру экрана + procedure CenterOnScreen; + /// Возвращает центр графического окна + function Center: Point; + /// Возвращает прямоугольник клиентской области окна + function ClientRect: GRect; + end; + +var + View3D: View3DT; + Window: WindowType; + Camera: CameraType; + +type +///!# + Base0T = class + private + model: Visual3D; + transfgroup := new Transform3DGroup; + rotatetransform_anim := new MatrixTransform3D; + scaletransform := new ScaleTransform3D; + translatetransform: TranslateTransform3D; + + procedure CreateBase0(m: Visual3D; x,y,z: real); + begin + model := m; + translatetransform := new TranslateTransform3D(x,y,z); + transfgroup.Children.Add(new MatrixTransform3D); // ответственен за поворот. Не храним в отдельной переменной т.к. при повороте меняется сам объект, а не поля объекта!!! + transfgroup.Children.Add(rotatetransform_anim); + transfgroup.Children.Add(scaletransform); + transfgroup.Children.Add(translatetransform); + model.Transform := transfgroup; + hvp.Children.Add(model); + end; + procedure SetX(xx: real) := Invoke(()->begin translatetransform.OffsetX := xx; end); + function GetX: real := Invoke&(()->translatetransform.OffsetX); + procedure SetY(yy: real) := Invoke(()->begin translatetransform.OffsetY := yy; end); + function GetY: real := Invoke&(()->translatetransform.OffsetY); + procedure SetZ(zz: real) := Invoke(()->begin translatetransform.OffsetZ := zz; end); + function GetZ: real := Invoke&(()->translatetransform.OffsetZ); + function GetPos: Point3D := Invoke&(()->P3D(translatetransform.OffsetX,translatetransform.OffsetY,translatetransform.OffsetZ)); + public + property X: real read GetX write SetX; + property Y: real read GetY write SetY; + property Z: real read GetZ write SetZ; + + procedure MoveTo(xx,yy,zz: real) := + Invoke(()->begin + translatetransform.OffsetX := xx; + translatetransform.OffsetY := yy; + translatetransform.OffsetZ := zz; + end); + procedure MoveTo(p: Point3D) := MoveTo(p.X,p.y,p.z); + procedure MoveOn(dx,dy,dz: real) := MoveTo(x+dx,y+dy,z+dz); + procedure MoveOn(v: Vector3D) := MoveOn(v.X,v.Y,v.Z); + + property Position: Point3D read GetPos write MoveTo; + + procedure Scale(f: real) := + Invoke(()->begin + scaletransform.ScaleX *= f; + scaletransform.ScaleY *= f; + scaletransform.ScaleZ *= f; + end); + /// Поворот на угол angle вокруг оси axis + procedure Rotate(axis: Vector3D; angle: real) := + Invoke(()->begin + var m := transfgroup.Children[0].Value; + m.Rotate(new Quaternion(axis,angle)); + transfgroup.Children[0] := new MatrixTransform3D(m); + end); + /// Поворот на угол angle вокруг оси axis относительно точки center + procedure RotateAt(axis: Vector3D; angle: real; center: Point3D); + begin + Invoke(()->begin + var m := transfgroup.Children[0].Value; + m.RotateAt(new Quaternion(axis,angle),center); + transfgroup.Children[0] := new MatrixTransform3D(m); + end); + end; + end; + + BaseT = class(Base0T) + private + procedure CreateBase(m: MeshElement3D; x,y,z: real; c: Color); + begin + CreateBase0(m,x,y,z); + var ColorMaterial := MaterialHelper.CreateMaterial(c); + m.Material := ColorMaterial; + end; + procedure CreateBase(m: MeshElement3D; x,y,z: real; mat: GMaterial); + begin + CreateBase0(m,x,y,z); + m.Material := mat; + end; + procedure SetColorP(c: GColor) := (model as MeshElement3D).Material := MaterialHelper.CreateMaterial(c); + procedure SetColor(c: GColor) := Invoke(SetColorP,c); + procedure SetVP(v: boolean) := (model as MeshElement3D).Visible := v; + procedure SetV(v: boolean) := Invoke(SetVP,v); + function GetV: boolean := Invoke&(()->(model as MeshElement3D).Visible); + + procedure SetMP(mat: GMaterial) := (model as MeshElement3D).Material := mat; + procedure SetM(mat: GMaterial) := Invoke(SetMP,mat); + function GetM: GMaterial := Invoke&(()->(model as MeshElement3D).Material); + public + property Color: GColor write SetColor; + property Material: GMaterial read GetM write SetM; + property Visible: boolean read GetV write SetV; + end; + + SphereT = class(BaseT) + private + procedure SetR(r: real); + begin + var m := model as SphereVisual3D; + Invoke(()->begin m.Radius := r end); + end; + function GetR: real := Invoke&(()->(model as SphereVisual3D).Radius); + public + constructor(x,y,z,r: real; c: GColor); + begin + var sph := new SphereVisual3D; + sph.Center := P3D(0, 0, 0); + sph.Radius := r; + CreateBase(sph,x,y,z,c); + end; + property Radius: real read GetR write SetR; + (*procedure AnimProbaP; + begin + var sb := new StoryBoard; + var da := new DoubleAnimation(1,3,new Duration(System.TimeSpan.FromSeconds(3))); + da.FillBehavior := FillBehavior.HoldEnd; + //da.RepeatBehavior := RepeatBehavior.Forever; + //translatetransform.BeginAnimation(TranslateTransform3D.OffsetXProperty,da); + //translatetransform.BeginAnimation(TranslateTransform3D.OffsetYProperty,da); + //translatetransform.BeginAnimation(TranslateTransform3D.OffsetZProperty,da); + sb.Children.Add(da); + {StoryBoard.SetTarget(da,translatetransform); + var pi: System.Reflection.PropertyInfo := translatetransform.Gettype.GetProperty('OffsetX'); + StoryBoard.SetTargetProperty(da,new PropertyPath(SphereVisual3D.RadiusProperty));} + StoryBoard.SetTarget(da,translatetransform); + StoryBoard.SetTargetProperty(da,new PropertyPath(TranslateTransform3D.OffsetXProperty)); + + //StoryBoard.SetTarget(da,scaletransform); + //StoryBoard.SetTargetProperty(da,new PropertyPath(ScaleTransform3D.ScaleXProperty)); + + //StoryBoard.SetTarget(da,model); + //StoryBoard.SetTargetProperty(da,new PropertyPath(SphereVisual3D.RadiusProperty)); + sb.Completed += (o,e) -> Print('end!'); + sb.CurrentTimeInvalidated += (o,e) -> Print(1); + + var ex := new HelixToolkit.Wpf.XamlExporter('sph.xaml'); + ex.Export(model); + + sb.Begin; + end;*) + //procedure AnimProba := Invoke(AnimProbaP); + end; + + EllipsoidT = class(BaseT) + private + procedure SetRXP(r: real) := (model as EllipsoidVisual3D).RadiusX := r; + procedure SetRX(r: real) := Invoke(SetRXP,x); + function GetRX: real := Invoke&(()->(model as EllipsoidVisual3D).RadiusX); + procedure SetRYP(r: real) := (model as EllipsoidVisual3D).RadiusY := r; + procedure SetRY(r: real) := Invoke(SetRYP,x); + function GetRY: real := Invoke&(()->(model as EllipsoidVisual3D).RadiusY); + procedure SetRZP(r: real) := (model as EllipsoidVisual3D).RadiusZ := r; + procedure SetRZ(r: real) := Invoke(SetRZP,x); + function GetRZ: real := Invoke&(()->(model as EllipsoidVisual3D).RadiusZ); + public + constructor(x,y,z,rx,ry,rz: real; c: GColor); + begin + var ell := new EllipsoidVisual3D; + ell.Center := P3D(0, 0, 0); + ell.RadiusX := rx; + ell.RadiusY := ry; + ell.RadiusZ := rz; + CreateBase(ell,x,y,z,c); + end; + property RadiusX: real read GetRX write SetRX; + property RadiusY: real read GetRY write SetRY; + property RadiusZ: real read GetRZ write SetRZ; + end; + + BoxT = class(BaseT) + private + procedure SetWP(r: real) := (model as BoxVisual3D).Width := r; + procedure SetW(r: real) := Invoke(SetWP,r); + function GetW: real := Invoke&(()->(model as BoxVisual3D).Width); + + procedure SetHP(r: real) := (model as BoxVisual3D).Height := r; + procedure SetH(r: real) := Invoke(SetHP,r); + function GetH: real := Invoke&(()->(model as BoxVisual3D).Height); + + procedure SetLP(r: real) := (model as BoxVisual3D).Length := r; + procedure SetL(r: real) := Invoke(SetLP,r); + function GetL: real := Invoke&(()->(model as BoxVisual3D).Length); + + procedure SetSzP(r: Size3D); + begin + var mmm := model as BoxVisual3D; + (mmm.Length,mmm.Width,mmm.Height) := (r.X,r.Y,r.Z); + end; + procedure SetSz(r: Size3D) := Invoke(SetSzP,r); + function GetSz: Size3D := Invoke&(()->begin var mmm := model as BoxVisual3D; Result := Sz3D(mmm.Length,mmm.Width,mmm.Height) end); + public + constructor(x,y,z,l,w,h: real; c: GColor); + begin + var bx := new BoxVisual3D; + bx.Center := P3D(0,0,0); + (bx.Width,bx.Height,bx.Length) := (w,h,l); + CreateBase(bx,x,y,z,c); + end; + property Length: real read GetL write SetL; + property Width: real read GetW write SetW; + property Height: real read GetH write SetH; + property Size: Size3D read GetSz write SetSz; + end; + + ArrowT = class(BaseT) + private + procedure SetDP(r: real) := (model as ArrowVisual3D).Diameter := r; + procedure SetD(r: real) := Invoke(SetDP,r); + function GetD: real := Invoke&(()->(model as ArrowVisual3D).Diameter); + + procedure SetLP(r: real) := (model as ArrowVisual3D).HeadLength := r; + procedure SetL(r: real) := Invoke(SetLP,r); + function GetL: real := Invoke&(()->(model as ArrowVisual3D).HeadLength); + + procedure SetDirP(r: Vector3D) := (model as ArrowVisual3D).Direction := r; + procedure SetDir(r: Vector3D) := Invoke(SetDirP,r); + function GetDir: Vector3D := Invoke&(()->(model as ArrowVisual3D).Direction); + public + constructor(x,y,z,dx,dy,dz,d,hl: real; c: GColor); + begin + var a := new ArrowVisual3D; + a.HeadLength := hl; + a.Diameter := d; + a.Origin := P3D(0,0,0); + CreateBase(a,x,y,z,c); + a.Direction := V3D(dx,dy,dz); + end; + property HeadLength: real read GetL write SetL; + property Diameter: real read GetD write SetD; + property Direction: Vector3D read GetDir write SetDir; + end; + + TruncatedConeT = class(BaseT) + private + procedure SetHP(r: real) := (model as TruncatedConeVisual3D).Height := r; + procedure SetH(r: real) := Invoke(SetHP,r); + function GetH: real := Invoke&(()->(model as TruncatedConeVisual3D).Height); + + procedure SetBRP(r: real) := (model as TruncatedConeVisual3D).BaseRadius := r; + procedure SetBR(r: real) := Invoke(SetBRP,r); + function GetBR: real := Invoke&(()->(model as TruncatedConeVisual3D).BaseRadius); + + procedure SetTRP(r: real) := (model as TruncatedConeVisual3D).TopRadius := r; + procedure SetTR(r: real) := Invoke(SetTRP,r); + function GetTR: real := Invoke&(()->(model as TruncatedConeVisual3D).TopRadius); + + procedure SetTCP(r: boolean) := (model as TruncatedConeVisual3D).TopCap := r; + procedure SetTC(r: boolean) := Invoke(SetTCP,r); + function GetTC: boolean := Invoke&(()->(model as TruncatedConeVisual3D).TopCap); + + public + constructor(x,y,z,h,baser,topr: real; sides: integer; topcap: boolean; c: GColor); + begin + var a := new TruncatedConeVisual3D; + a.Origin := p3D(0,0,0); + a.BaseRadius := baser; + a.TopRadius := topr; + a.Height := h; + a.TopCap := topcap; + a.ThetaDiv := sides + 1; + CreateBase(a,x,y,z,c); + end; + property Height: real read GetH write SetH; + property BaseRadius: real read GetBR write SetBR; + property TopRadius: real read GetTR write SetTR; + property Topcap: boolean read GetTC write SetTC; + end; + + TeapotT = class(BaseT) + private + procedure SetVP(v: boolean) := (model as MeshElement3D).Visible := v; + procedure SetV(v: boolean) := Invoke(SetVP,v); + function GetV: boolean := Invoke&(()->(model as MeshElement3D).Visible); + public + constructor(x,y,z: real; c: GColor); + begin + var a := new Teapot; + CreateBase(a,x,y,z,c); + Rotate(V3D(1,0,0),90); + //a.Position := P3D(x,y,z); + end; + property Visible: boolean read GetV write SetV; + end; + + CoordinateSystemT = class(Base0T) + private + procedure SetALP(r: real) := (model as CoordinateSystemVisual3D).ArrowLengths := r; + procedure SetAL(r: real) := Invoke(SetALP,r); + function GetAL: real := Invoke&(()->(model as CoordinateSystemVisual3D).ArrowLengths); + public + constructor(x,y,z,arrlength,diameter: real); + begin + var a := new CoordinateSystemVisual3D; + CreateBase0(a,x,y,z); + a.ArrowLengths := arrlength; + (a.Children[0] as ArrowVisual3D).Diameter := diameter; + (a.Children[1] as ArrowVisual3D).Diameter := diameter; + (a.Children[2] as ArrowVisual3D).Diameter := diameter; + (a.Children[3] as CubeVisual3D).SideLength := diameter; + end; + property ArrowLengths: real read GetAL write SetAL; + end; + + BillboardTextT = class(Base0T) + private + procedure SetTP(r: string) := (model as BillboardTextVisual3D).Text := r; + procedure SetT(r: string) := Invoke(SetTP,r); + function GetT: string := Invoke&(()->(model as BillboardTextVisual3D).Text); + + procedure SetFSP(r: real) := (model as BillboardTextVisual3D).FontSize := r; + procedure SetFS(r: real) := Invoke(SetFS,r); + function GetFS: real := Invoke&(()->(model as BillboardTextVisual3D).FontSize); + public + constructor(x,y,z: real; text: string; fontsize: real); + begin + var a := new BillboardTextVisual3D; + CreateBase0(a,x,y,z); + a.Position := p3D(0,0,0); + a.Text := text; + a.FontSize := fontsize; + end; + property Text: string read GetT write SetT; + property FontSize: real read GetFS write SetFS; + end; + + TextT = class(Base0T) + private + procedure SetTP(r: string) := (model as TextVisual3D).Text := r; + procedure SetT(r: string) := Invoke(SetTP,r); + function GetT: string := Invoke&(()->(model as TextVisual3D).Text); + + procedure SetFSP(r: real) := (model as TextVisual3D).Height := r; + procedure SetFS(r: real) := Invoke(SetFS,r); + function GetFS: real := Invoke&(()->(model as TextVisual3D).Height); + + procedure SetNP(r: string) := (model as TextVisual3D).Text := r; + procedure SetN(r: string) := Invoke(SetTP,r); + function GetN: string := Invoke&(()->(model as TextVisual3D).Text); + + procedure SetColorP(c: GColor) := (model as TextVisual3D).Foreground := new SolidColorBrush(c); + procedure SetColor(c: GColor) := Invoke(SetColorP,c); + public + constructor(x,y,z: real; text: string; height: real; fontname: string; c: Color); + begin + var a := new TextVisual3D; + CreateBase0(a,x,y,z); + a.Position := p3D(0,0,0); + a.Text := text; + a.Height := height; + a.FontFamily := new FontFamily(fontname); + a.Foreground := new SolidColorBrush(c); + end; + property Text: string read GetT write SetT; + property Height: real read GetFS write SetFS; + property Name: string read GetN write SetN; + property Color: GColor write SetColor; + end; + + RectangleT = class(BaseT) + private + procedure SetWP(r: real) := (model as RectangleVisual3D).Width := r; + procedure SetW(r: real) := Invoke(SetWP,r); + function GetW: real := Invoke&(()->(model as RectangleVisual3D).Width); + + procedure SetLP(r: real) := (model as RectangleVisual3D).Length := r; + procedure SetL(r: real) := Invoke(SetLP,r); + function GetL: real := Invoke&(()->(model as RectangleVisual3D).Length); + + procedure SetLDP(r: Vector3D) := (model as RectangleVisual3D).LengthDirection := r; + procedure SetLD(r: Vector3D) := Invoke(SetLDP,r); + function GetLD: Vector3D := Invoke&(()->(model as RectangleVisual3D).LengthDirection); + + procedure SetNP(r: Vector3D) := (model as RectangleVisual3D).Normal := r; + procedure SetN(r: Vector3D) := Invoke(SetNP,r); + function GetN: Vector3D := Invoke&(()->(model as RectangleVisual3D).Normal); + public + constructor(x,y,z,l,w: real; normal,lendirection: Vector3D; c: GColor); + begin + var a := new RectangleVisual3D; + CreateBase(a,x,y,z,c); + a.Origin := P3D(0,0,0); + a.Width := w; + a.Length := l; + a.LengthDirection := lendirection; + a.Normal := normal; + end; + property Width: real read GetW write SetW; + property Length: real read GetL write SetL; + property LengthDirection: Vector3D read GetLD write SetLD; + property Normal: Vector3D read GetN write SetN; + end; + + FileModelT = class(Base0T) + private + procedure SetVP(v: boolean) := (model as MeshElement3D).Visible := v; + procedure SetV(v: boolean) := Invoke(SetVP,v); + function GetV: boolean := Invoke&(()->(model as MeshElement3D).Visible); + public + constructor(x,y,z: real; fname: string); + begin + var a := new FileModelVisual3D; + a.Source := fname; + CreateBase0(a,x,y,z); + end; + property Visible: boolean read GetV write SetV; + end; + + PipeT = class(BaseT) + private + procedure SetDP(r: real) := (model as PipeVisual3D).Diameter := r*2; + procedure SetD(r: real) := Invoke(SetDP,r); + function GetD: real := Invoke&(()->(model as PipeVisual3D).Diameter/2); + + procedure SetIDP(r: real) := (model as PipeVisual3D).InnerDiameter := r*2; + procedure SetID(r: real) := Invoke(SetIDP,r); + function GetID: real := Invoke&(()->(model as PipeVisual3D).InnerDiameter/2); + + procedure SetHP(r: real) := (model as PipeVisual3D).Point2 := P3D(0,0,r); + procedure SetH(r: real) := Invoke(SetHP,r); + function GetH: real := Invoke&(()->(model as PipeVisual3D).Point2.Z); + public + constructor(x,y,z,h,r,ir: real; c: GColor); + begin + var a := new PipeVisual3D; + a.Diameter := r*2; + a.InnerDiameter := ir*2; + a.Point1 := P3D(0,0,0); + a.Point2 := P3D(0,0,h); + CreateBase(a,x,y,z,c); + end; + property Radius: real read GetD write SetD; + property InnerRadius: real read GetID write SetID; + property Height: real read GetH write SetH; + end; + + + AnyT = class(BaseT) + constructor(x,y,z: real; c: GColor); + begin + //HelixToolkit.Wpf.LinesVisual3D + + var a := new HelixToolkit.Wpf.LinesVisual3D; + + var aa := 1; + var b := 80; + + var q := Partition(0,2*Pi*20,360*20*10).Select(t->P3D(5*cos(1*t),5*sin(1*t),t/5)); + var q1 := q.Interleave(q.Skip(1)); + + //a.Points := Lst(P3D(0,0,0),P3D(4,4,2),p3D(4,4,2),p3D(2,8,-1)); + a.Points := Lst(q1); + a.Color := Colors.Blue; + + a.Thickness := 1.5; + + {var a := new HelixToolkit.Wpf.PieSliceVisual3D; + a.StartAngle := 0; + a.EndAngle := 360; + a.ThetaDiv := 60;} + + {var a := new HelixToolkit.Wpf.TubeVisual3D; + var p := new Point3DCollection(Arr(P3D(1,2,0),P3D(2,1,0),P3D(3,1,0))); + a.Diameter := 0.05; + a.Path := p;} + + + + CreateBase0(a,x,y,z); + //a.Fill := Brushes.Aqua; + end; + end; + +{procedure ProbaP; +begin + //var ms := new TorusMesh(); + //ms.Radius := 4; + //var ms := new IcosahedronMesh(); + //ms.Geometry. + //var mg3d := ms.Geometry; + + var blueMaterial := MaterialHelper.CreateMaterial(Colors.Bisque); + + + var g3d := new GeometryModel3D(mg3d, blueMaterial); + var modelGroup := new Model3DGroup(); + modelGroup.Children.Add(g3d); + + var model := new ModelVisual3D(); + model.Content := modelGroup;| + hvp.Children.Add(model); +end; + +procedure Proba := Invoke(ProbaP);} + +function Sphere(x,y,z,r: real; c: Color): SphereT := Invoke&(()->SphereT.Create(x,y,z,r,c)); +function Sphere(center: Point3D; r: real; c: Color) := Sphere(center.x,center.y,center.z,r,c); + +function Ellipsoid(x,y,z,rx,ry,rz: real; c: Color): EllipsoidT := Invoke&(()->EllipsoidT.Create(x,y,z,rx,ry,rz,c)); +function Ellipsoid(center: Point3D; rx,ry,rz: real; c: Color): EllipsoidT := Ellipsoid(center.x,center.y,center.z,rx,ry,rz,c); + +function Box(x,y,z,l,w,h: real; c: Color): BoxT := Invoke&(()->BoxT.Create(x,y,z,l,w,h,c)); +function Box(center: Point3D; sz: Size3D; c: Color): BoxT := Invoke&(()->BoxT.Create(center.x,center.y,center.z,sz.X,sz.Y,sz.z,c)); +function Cube(x,y,z,w: real; c: Color) := Box(x,y,z,w,w,w,c); +function Cube(center: Point3D; w: real; c: Color) := Box(center.x,center.y,center.z,w,w,w,c); + +const + arhl = 3; + ard = 0.2; + +function Arrow(x,y,z,vx,vy,vz,diameter,hl: real; c: Color): ArrowT := Invoke&(()->ArrowT.Create(x,y,z,vx,vy,vz,diameter,hl,c)); +function Arrow(x,y,z,vx,vy,vz,diameter: real; c: Color) := Arrow(x,y,z,vx,vy,vz,diameter,arhl,c); +function Arrow(x,y,z,vx,vy,vz: real; c: Color): ArrowT := Arrow(x,y,z,vx,vy,vz,ard,arhl,c); +function Arrow(p: Point3D; v: Vector3D; diameter,hl: real; c: Color) := Arrow(p.x,p.y,p.z,v.X,v.Y,v.Z,diameter,hl,c); +function Arrow(p: Point3D; v: Vector3D; diameter: real; c: Color) := Arrow(p.x,p.y,p.z,v.X,v.Y,v.Z,diameter,arhl,c); +function Arrow(p: Point3D; v: Vector3D; c: Color) := Arrow(p.x,p.y,p.z,v.X,v.Y,v.Z,ard,arhl,c); + +function TruncatedConeAux(x,y,z,height,baseradius,topradius: real; sides: integer; topcap: boolean; c: GColor) := + Invoke&(()->TruncatedConeT.Create(x,y,z,height,baseradius,topradius,sides,topcap,c)); + +{function Pyramid(x,y,z,height,baseradius,topradius: real; sides: integer; topcap: boolean; c: GColor) + := TruncatedConeAux(x,y,z,height,baseradius,topradius,sides,topcap,c); +function Pyramid(x,y,z,height,baseradius,topradius: real; sides: integer; c: GColor) + := TruncatedConeAux(x,y,z,height,baseradius,topradius,sides,True,c); +function Pyramid(p: Point3D; height,baseradius,topradius: real; sides: integer; topcap: boolean; c: GColor) + := TruncatedConeAux(p.x,p.y,p.z,height,baseradius,topradius,sides,topcap,c); +function Pyramid(p: Point3D; height,baseradius,topradius: real; sides: integer; c: GColor) + := TruncatedConeAux(p.x,p.y,p.z,height,baseradius,topradius,sides,True,c); } + +const maxsides = 37; + +function TruncatedCone(x,y,z,height,baseradius,topradius: real; topcap: boolean; c: GColor) + := TruncatedConeAux(x,y,z,height,baseradius,topradius,maxsides,topcap,c); +function TruncatedCone(x,y,z,height,baseradius,topradius: real; c: GColor) + := TruncatedCone(x,y,z,height,baseradius,topradius,True,c); +function TruncatedCone(p: Point3D; height,baseradius,topradius: real; topcap: boolean; c: GColor) + := TruncatedCone(p.x,p.y,p.z,height,baseradius,topradius,topcap,c); +function TruncatedCone(p: Point3D; height,baseradius,topradius: real; c: GColor) + := TruncatedCone(p.x,p.y,p.z,height,baseradius,topradius,True,c); + +{function Prism(x,y,z,height,radius: real; sides: integer; topcap: boolean; c: GColor) + := TruncatedConeAux(x,y,z,height,radius,radius,sides,topcap,c); +function Prism(x,y,z,height,radius: real; sides: integer; c: GColor) + := TruncatedConeAux(x,y,z,height,radius,radius,sides,True,c); +function Prism(p: Point3D; height,radius: real; sides: integer; topcap: boolean; c: GColor) + := TruncatedConeAux(p.x,p.y,p.z,height,radius,radius,sides,topcap,c); +function Prism(p: Point3D; height,radius: real; sides: integer; c: GColor) + := TruncatedConeAux(p.x,p.y,p.z,height,radius,radius,sides,True,c);} + +function Cylinder(x,y,z,height,radius: real; topcap: boolean; c: GColor): TruncatedConeT := TruncatedCone(x,y,z,height,radius,radius,topcap,c); +function Cylinder(x,y,z,height,radius: real; c: GColor) := Cylinder(x,y,z,height,radius,True,c); +function Cylinder(p: Point3D; height,radius: real; topcap: boolean; c: GColor) := Cylinder(p.x,p.y,p.z,height,radius,topcap,c); +function Cylinder(p: Point3D; height,radius: real; c: GColor) := Cylinder(p.x,p.y,p.z,height,radius,True,c); + +function Tube(x,y,z,height,radius,innerradius: real; c: GColor): PipeT + := Invoke&(()->PipeT.Create(x,y,z,height,radius,innerradius,c)); +function Tube(p: Point3D; height,radius,innerradius: real; c: GColor) + := Tube(p.x,p.y,p.z,height,radius,innerradius,c); + +function Cone(x,y,z,height,radius: real; c: GColor): TruncatedConeT := TruncatedCone(x,y,z,height,radius,0,True,c); +function Cone(p: Point3D; height,radius: real; c: GColor) := TruncatedCone(p.x,p.y,p.z,height,radius,0,True,c); + +function Teapot(x,y,z: real; c: GColor): TeapotT := Invoke&(()->TeapotT.Create(x,y,z,c)); +function Teapot(p: Point3D; c: GColor) := Teapot(p.x,p.y,p.z,c); + +function BillboardText(x,y,z: real; text: string; fontsize: real): BillboardTextT := Invoke&(()->BillboardTextT.Create(x,y,z,text,fontsize)); +function BillboardText(p: Point3D; text: string; fontsize: real) := BillboardText(P.x,p.y,p.z,text,fontsize); +function BillboardText(p: Point3D; text: string) := BillboardText(P.x,p.y,p.z,text,12); +function BillboardText(x,y,z: real; text: string) := BillboardText(x,y,z,text,12); + +function CoordinateSystem(arrowslength,diameter: real): CoordinateSystemT + := Invoke&(()->CoordinateSystemT.Create(0,0,0,arrowslength,diameter)); +function CoordinateSystem(arrowslength: real) := CoordinateSystem(arrowslength,arrowslength/10); + +function Text3D(x,y,z: real; text: string; height: real; fontname: string; c: Color): TextT := Invoke&(()->TextT.Create(x,y,z,text,height,fontname,c)); +function Text3D(p: Point3D; text: string; height: real; fontname: string; c: Color) := Text3D(P.x,p.y,p.z,text,height,fontname,c); +function Text3D(x,y,z: real; text: string; height: real; c: Color) := Text3D(x,y,z,text,height,'Arial',c); +function Text3D(p: Point3D; text: string; height: real; c: Color) := Text3D(p.x,p.y,p.z,text,height,'Arial',c); +function Text3D(x,y,z: real; text: string; height: real) := Text3D(x,y,z,text,height,'Arial',Colors.Black); +function Text3D(p: Point3D; text: string; height: real) := Text3D(p.x,p.y,p.z,text,height,'Arial',Colors.Black); + +function Rectangle3D(x,y,z,l,w: real; normal,lendirection: Vector3D; c: GColor): RectangleT + := Invoke&(()->RectangleT.Create(x,y,z,l,w,normal,lendirection,c)); +function Rectangle3D(p: Point3D; l,w: real; normal,lendirection: Vector3D; c: GColor) + := Rectangle3D(p.x,p.y,p.z,l,w,normal,lendirection,c); +function Rectangle3D(x,y,z,l,w: real; normal: Vector3D; c: GColor) + := Rectangle3D(x,y,z,l,w,normal,v3d(1,0,0),c); +function Rectangle3D(x,y,z,l,w: real; c: GColor) + := Rectangle3D(x,y,z,l,w,v3d(0,0,1),v3d(1,0,0),c); +function Rectangle3D(p: Point3D; l,w: real; normal: Vector3D; c: GColor) + := Rectangle3D(p.x,p.y,p.z,l,w,normal,v3d(1,0,0),c); +function Rectangle3D(p: Point3D; l,w: real; c: GColor) + := Rectangle3D(p.x,p.y,p.z,l,w,v3d(0,0,1),v3d(1,0,0),c); + +/// Загружает модель из файла .obj, .3ds, .lwo, .objz, .stl, .off +function FileModel3D(x,y,z: real; fname: string): FileModelT + := Invoke&(()->FileModelT.Create(x,y,z,fname)); +function FileModel3D(p: Point3D; fname: string) := FileModel3D(p.x,p.y,p.z,fname); + + +function Any(x,y,z: real; c: Color): AnyT := Invoke&(()->AnyT.Create(x,y,z,c)); + +procedure WindowTypeSetLeftP(l: real) := MainWindow.Left := l; +procedure WindowType.SetLeft(l: real) := Invoke(WindowTypeSetLeftP,l); + +function WindowTypeGetLeftP := MainWindow.Left; +function WindowType.GetLeft := Invoke&(WindowTypeGetLeftP); + +procedure WindowTypeSetTopP(t: real) := MainWindow.Top := t; +procedure WindowType.SetTop(t: real) := Invoke(WindowTypeSetTopP,t); + +function WindowTypeGetTopP := MainWindow.Top; +function WindowType.GetTop := Invoke&(WindowTypeGetTopP); + +procedure WindowTypeSetWidthP(w: real) := MainWindow.Width := w + wplus; +procedure WindowType.SetWidth(w: real) := Invoke(WindowTypeSetWidthP,w); + +function WindowTypeGetWidthP := MainWindow.Width - wplus; +function WindowType.GetWidth := Invoke&(WindowTypeGetWidthP); + +procedure WindowTypeSetHeightP(h: real) := MainWindow.Height := h + hplus; +procedure WindowType.SetHeight(h: real) := Invoke(WindowTypeSetHeightP,h); + +function WindowTypeGetHeightP := MainWindow.Height - hplus; +function WindowType.GetHeight := Invoke&(WindowTypeGetHeightP); + +procedure WindowTypeSetCaptionP(c: string) := MainWindow.Title := c; +procedure WindowType.SetCaption(c: string) := Invoke(WindowTypeSetCaptionP,c); + +function WindowTypeGetCaptionP := MainWindow.Title; +function WindowType.GetCaption := Invoke&(WindowTypeGetCaptionP); + +procedure WindowTypeSetSizeP(w, h: real); +begin + WindowTypeSetWidthP(w); + WindowTypeSetHeightP(h); +end; +procedure WindowType.SetSize(w, h: real) := Invoke(WindowTypeSetSizeP,w,h); + +procedure WindowTypeSetPosP(l, t: real); +begin + WindowTypeSetLeftP(l); + WindowTypeSetTopP(t); +end; +procedure WindowType.SetPos(l, t: real) := Invoke(WindowTypeSetPosP,l,t); + +procedure WindowType.Close := Invoke(MainWindow.Close); + +procedure WindowTypeMinimizeP := MainWindow.WindowState := WindowState.Minimized; +procedure WindowType.Minimize := Invoke(WindowTypeMinimizeP); + +procedure WindowTypeMaximizeP := MainWindow.WindowState := WindowState.Maximized; +procedure WindowType.Maximize := Invoke(WindowTypeMaximizeP); + +procedure WindowTypeNormalizeP := MainWindow.WindowState := WindowState.Normal; +procedure WindowType.Normalize := Invoke(WindowTypeNormalizeP); + +procedure WindowTypeCenterOnScreenP := MainWindow.WindowStartupLocation := WindowStartupLocation.CenterScreen; +procedure WindowType.CenterOnScreen := Invoke(WindowTypeCenterOnScreenP); + +function WindowType.Center := new Point(Width/2,Height/2); + +function WindowType.ClientRect := Rect(0,0,Window.Width,Window.Height); + +var mre := new ManualResetEvent(false); + +procedure InitWPF0; +begin + app := new Application; + app.Dispatcher.UnhandledException += (o,e) -> Println(e); + MainWindow := new GWindow; + + Window := new WindowType; + Camera := new CameraType; + + var g := new Grid; + MainWindow.Content := g; + hvp := new HelixViewport3D(); + g.Children.Add(hvp); + //hvp.ShowTriangleCountInfo := true;; + //hvp.ShowCameraInfo := True; + hvp.ZoomExtentsWhenLoaded := True; + //hvp.Background := Brushes.Black; + + hvp.ShowCoordinateSystem := True; + + hvp.Children.Add(new DefaultLights); + + gvl := new GridLinesVisual3D(); + + //gvl.Fill := Brushes.Aqua; + //gvl.Visible := False; + gvl.Width := 12; gvl.Length := 12; + gvl.MinorDistance := 1; + gvl.MajorDistance := 1; + gvl.Thickness := 0.02; + hvp.Children.Add(gvl); + + MainWindow.Title := '3D графика'; + MainWindow.WindowStartupLocation := WindowStartupLocation.CenterScreen; + MainWindow.Width := 640; + MainWindow.Height := 480; + MainWindow.Closed += procedure(sender,e) -> begin Halt; end; + MainWindow.KeyDown += SystemOnKeyDown; + MainWindow.KeyUp += SystemOnKeyUp; + + View3D := new View3DT; + + //hvp.Camera.Position := P3D(12,16,20); + + mre.Set(); + + app.Run(MainWindow); +end; + +initialization + MainFormThread := new System.Threading.Thread(InitWPF0); + MainFormThread.SetApartmentState(ApartmentState.STA); + MainFormThread.Start; + + mre.WaitOne; // Основная программа не начнется пока не будут инициализированы все компоненты приложения + +finalization +end. \ No newline at end of file diff --git a/bin/TestRunner.exe b/bin/TestRunner.exe index 61a8dc4b2388f1389385cb9e8420eba8289e99f3..abd3aa0aaf2ed1f718837754683192d5a0a9e8aa 100644 GIT binary patch literal 30208 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 literal 28160 zcmeHw3w&JVb?3Qv?!4|iq#4;W#+L9H8#Bhn_=!j6VM~@|3qP^sR|1ho(%2q6nk(NK z*%n|UhopgoP-qeeCZRx}1@a<~Hi33i*raWoG{vN_0U8o$pi5hl=0OwKA8Yo1&Uf!T zWC=sJ`}^(wwnOI3_c-4<=R2?Oe0N69#`oPy9wIXEe*0~rZ{m|*s|CI@7{hdQ=3`O% zdhiFczNu~a!K~hW`BG}ww)WcDq0~UOShUKi{#?o)DW>wpRLAC?)Q~lpTNsI$a~#rJ zI*B%DI<@>oW{1n|Mbfp979zSDBf1*>#f|t(;e8L@EL{p8m}9y5<$luR=s0UUwEDwD zjq<${nz&aV6Jwr z=*cTZw0ohQD_8?c2H89Bt)S*zxSFWlZNJyglh_x(ST>!!RMJlOieY|}<_g-F2 zi*)j10oKO!G@yAtO+2QkVmq;>MXC)yth9N(JO2M}TNfV?T_lb)z)R0>T4d0aihjJ% zkDk7O^{l74gLxlq#ysnVjxNNK#3`*6u|Vm=jFw?l>DMx?BqrE3^=skchMAczQ%T{Y zr(rqs2v0cEKDL9GHUNqB{4QvVyX#7BMdT?{pDHvnY+_;Yl<9-aIz=(S`y@~L8pxRj zg4ugMfv1O>>gSFwfK=%K5OyfvsIEPF4yLRbAkZUglN1M0(27yIgwfWUK#EF1tTlBD zZsQC2XGTtGtRtv|lKz>d^!hSbM~T)5pwDlaSTI*q_s-}oKqrT=yG*w(1dXRcnJ3qjXf33QlW3X1lA!S~H zsv$b~8+2X;canJYB+^B&PA5U0luxra3Vq#^9%QLk0mz((=DTx~#dmrd6SA>>I1RZK zP00z0rs!1YOW&1qd|p|@EGv2COzQ)5E#!i(5W^O7iOF$jwt_#W7--J$SV#%x6bUdh zvzaG6>8rt{k%P^}fo5SIj!5_hB|Re$OPg}aUyok7fb`R>b+C z@?%$ZA9@Ls<`I@g`NrILg{ymxfGw7YKV9%{*T4*hZXAIMcq!J-r5|Cdh?O(QmOxrc zoibX#z>4wsZ%d8gvo9|nP|Pg!l)C(9zwc)3UvodL6t<_5JeJK|-It%a8Vk;?U69=A zE^%7p7NxK#HGa{w$|9#VZc!r^HI83&R%MaXs$X<=U;gazOQu(rIIa36)BEz%$1j;t zS>m+nmrU!+PaD6axw6D*)h|i*<@s=|(^^YqiPNfC;!o}jWiCI(5kF&5h>&Fn`6))? zY^Gx>3sy5N98hLB(*}%yg?wX%$S+42tHrL zdktjF+{=P`Mwq4&BAo_}n>oa-d1xh0NxgzWj(mm2lAVU8BSE-=;(Vv1nE?+IhpH=lDx7UR(-XR+@z7D#{Q&9l$QVm#X9EWD?&Sja384`)!+c(ln`+y{rO zR}_je^JYZY8CZ-*o1Dd}(^xEG7KqU^uo#avIg1}-6ZN}T%q$RwXJ9cNZE_Z)!lLH< z?vnwDj&NLef|i3Ija!}@uY8IoRft969+gaFiGwr&KTa8lRyMs8yGdLHkUyjJ5XO)Z zI8X7Zn?O!Pgd(4*zO{kR^UUvIM51`oEPFXH+$^j6t&LdLdXW&S&3~u`a7BI%cR2{Mu z93sGd=bA%@6fwdV=6Ki7q9ZyfHZw zImToIfrNE2W+v2NhR>WtlNYO*SaMgBKjv-n#e6aUDGtI`_7hxkDtZaTeB-E@f^5h$ zgP1S5v&onFKBN=P+k2r;$87su)pFRvf4JU&YqS42eNKqnSN?VSbBk@_%I z*VbydkwNu7O4|%G(?Fc$5~^EZ=u>SA2fPTt&un^uq4-~mvXp5_Nn|6}52p)2EVY6C zHDY1iWv5W0+GHqJyNKMh$WQP8KSAHQ6I*MPW_TYQd9a;pW44NTsXYcM%?z8>z7b75 z6xZyVS@I4nmhg9cG)ils2o%_O7SM?~s|x@Zij_ESC{Z11KBeuJCqT)SVVNmGOU_8^ z+Zdr_2~L2F74jvlbxf(7!Q}w8f1S5NY4D0%1S0JYu<@| z6K)&nEPGGtb?7R+n#q^008|c=2{0#*V$1OrpyL<;DxL|Z#`!Dm`;@GX@`2&p|J`gl z6&N)LJYQd`~qE$sedU0qWE}9la2>#B`;Bmg?L;mEhPCgR& zJs)rOtv0-2AId;X)LD|J?O#A?Dc;`n-7_0^Pw`B#kaf(t9y7B5NRp7v446_0_Vt;W z1UKP#Xd-t|X_qw7l2ipbGzg&dl^Ek(Q9{}@$B^BfX9SHj58P5Br-%^pt0yU(Vn{bL zVN8pP{Bi*c2WIT*WTUv-sr_VWcuUvgojD1hSMNDy=D%Wq^asv`IFVoqN&b3uVPa0f zyj+&RPF7WlR5Nq4S_O$Uc9vPZj)&9%!#Of$xQqEnfXGk+Gdho#cw^q`61ktr60f@i z(vU&{VOJuBa`s%QjNKI=f8j{xYkdOj$J($hHPoE#XU8-Q~T2rGk8dI)nNJsAmp z9BBjlz?m8I1}|ZE#C_W`~RkUVZ?3~nTjtMkI3#puqi%5#+MZwEjuu36BQDNpA8BP^Vm@d4RR zVA9>|6|@;v#=hPC=p!fz_`ci9&ZVc{2NLq!(9E0%%<-ub1p>-9)FkG~ORz4D3~r{c z1X#&YmT;zU8rDp?39%Mq)kGLd1nVlMiDMXna6dNh&|F=&q7^ZUsRwv8WH-z9(@H>Hb@i^OK;cW^6x1S6u( z6y$Td;XuI3g5JmZ(Ly3UEL%Gv*l=^fz6GJFp6W;R5gwH1Jxzw%ygDA{Kv1iHoLp+^ zGIwesUBe0V(I2-P(hB<#}f2U)Y{{ z`T$Jb6mX2V7xVTvnchCMnu4{q{b6O;j0L8o^UR7hn~VjltI=__!Ty$v1+l;S5mf(m zBf8_010sh6Y*R?SHEIgS!ZXuqzn+;(aA`nkN3pQ|T2)iO<&n@ytaV%-r-s@3B1SPh zvvKa+ zuB_43C87b>kcP~6U{(;0$D%Ib7+!Hjm}?&<9F4_}>r<>ENK8tZ7dez;pd8D5n;j-( zA=j8qp^VABu!ZpCJlx7BU<}Gq1G_BP(y0odzE||H{ zvK2zpq8R-g^YCYPUhEM+ z)AK`&;xPhugMfh;<0PY22WAc^RiPxJWT3LR8e(`>0`j$?f^r%ZT|xnJ+Q(8j4q9$x7Z?!hf=(;>bMe=59YGr>L4B;GU1Q_puaC z)->|1dmrPc;?DO}>4TtS2Rw-7Lujd6o=bx&-@Q{Bf@ArNMG5*TqG=)TKlL-5GW?yo zId(2{7)8tUxyOI&QSgB2c48C;e)m~R;^s#=YZx>MnIdW>5a zpe2>^SgO*R#j~B5J@d`16%`mPaEy^5vDp;PfHq=X4ydM3EHrXCdmU;b4jHGLv_1%2 z<#RyggKgyrDBuNHlcqF2&yqNxl688nIe0iclBb7;dr~1&5_`K({U9Pa2A|>I zrFdx!GfbBY)s{`eT^ko1OhI@TJO;+ek7EA>8L)${35WM2vxD$3V+Lsa>X4>Sf2*2x)*J)i__M2AlcK68dfXOQ4A`q z6lI6Xo%I5UngrCJwjY(RjbXaE?Z{r@e+I+kTR^_qQ|HOqS4GJ#&J@u4fvdFR+@eD8YxJ_LH# zdR9WXI`&MgQ2{xt0zkOGl;cG^8E)U(0N6lHFpi;z5DcERJ8eS^8IPKRAxRH{Hiee+bTzVA8sWB?u<0d*yRdvetb(U-}#%*9t-Fe(vK5&;$6K ztGA-W;@%&jXMYqDScpWo?l!Ck(cvRJ0<4*4dbb?TFJr%0@{pS2>3NtY`&KN6getxs znqb?9Sq7X5emaJ+i+L;&!c}%B#_MXKK_;lj zjN|gFEIeiK;tOtmFszg&m51L03As?k1Kyox29=+a@9}FeL3QqBxX0sr?H@p%df)pD zMx1BYe)=F*zk@t@X!TC=@O_U&oP>J zixpFR!O^QuxTwgp6H0$^q9s1BW4)^P+jA!vTjq{QF>3nKUjjAui1T=VzG`)6cT_NI z>paFnTzhFR8kex0@s*W|)d^w%7f!s`#W)b{N0{V#wB%6<4}Y2WB8T}4eqhd}7kELv z!~Gewr|ymXO~d?+n4@2e@ZR!!27rzL{A0!N?xgFx)A^GVb@YR`Mz%tbeW_-TC40w~~`-=Ul9$%C1lj*Op zYdH!38*rhTgs<}bOzN>!zGr{F1e*pwY%7hE!8gXCC~Rq_;brES4(iBV?Bkqj7|;0$KDGB;-mZz%>hHM@ zE?c;0;nGD*7q_x__}eSMr(yNkM~PNrLv!FIvwO;RzPPu<7=v?&z5}bz-qu6>M?-v8 z%wD^#y91vY^pW>yc6))poRSwrY1}bWZ-#Ra{f~2(QbOsh4_%mppM%nMcyGcRxb!D@ z!`bA#<(Po%%5j}GpTp2VjK>)>qkv|gn1)b&mrnJ8!@CO2aDDYWPE#YAbDGy6KPv9aC%Xx`s zjmJm59+q&o!2L3Qt-#;&uq3w${EV#nV}W0l**_QjZ_CI@fj^S*f0p*IrTwPBkioo~ z0HZYBcns}x43=bpz*d3l1!tSUK4XS%(thI>z)^wM3%mo+M_(|$XT<4y^t|Dt*Nk;q zoPKL?+vi=UU95#9ktDpYY6)ub&h{qg62R%S!TUT-ryYO^8pMc6*La!!#{kc!Pk8?o zC+CCS=Z&-JakPDO()&Ho{O`f+qZ|{}%z38JrQ?(wk1(WNrYxX8ZXQbFsIT_06nxb_!i)A1@nIdNb`^pWT^1p zAM~L8fuJ8S8w_bvX)wrAc}FK4vqANK zBfaZ3svh|-1m-=Q#(leuKz!UEZ-kUrq1Bfq|OdZGpXYy&>cS_3HB87AVOtDHWC50;6(9xV`JS?2s2@ zewhEprPBMDoS@w0x!dGBRJV5rJW{=Q?z7NGwf8yViz2^x?jintvAnqVW%;I#yItN- z2R0&})vL?pjN5>62HN_w&GPrYmQ0*GFHI-mvP4mYV5S>2+zWrDl4~>9LlY zspA}8*QK$RnrU~XH-xOhUyP}_5}i$-sq{vXCH|a?OIde4rRdp8@53~UUa#~%hWyru zQ&?f%Mzd)_rFRF-p>>tsXX#uyuNCI!kZ<_MVygEL{-W@nO7F|aJg+-F*3vvmovSce zOY^9=(t8ZwFbq3A*3x`>PXPfYU8>{c7Mz zP`Oq0WZeS#^Gfe&+$n!q={<*hyx=@p2h5)$UpC5R-$zq}Pvcvp2c6#Z;FHMUTNWw~ zQ-d2(!B4wT_09{vf?kK}fyyhils;7H{R=Il?@Div))xE?D*0EX*F~FyZ=e@g!c^4W zSI{)+slBft{<8(_NcOOT*41I|ti#+_hxsAtxg74PTla|c5>yP%2lr>4-jQH~wt|eM z%r8MV1ZPt#r7FFI)=C+tcUv%_t)wkZ?|5*Ab}`*k>7_K>a^*2Vg6 z3$#`A1*i9|;8Jb1l)B9Chv3&v52Enp-YdaYt(_L(%7emBe4>N4q`0Rvfu77I=&0GF zb;RE)K!(Szvb4FP+{$ zn*-YA^x7&l=Lr?Gjr7UYsy8(>8&&2bPVchwp_{yw0N5greteAj3dpiZ5qwH4+qGJZP>V~zf!-~&SKq5n{tO14_czY_Yt6^Pmb zdqi!4T{H?*;|gcFjI0n?#q^LGVVXKAoErt_4uOi-ebQ#A)5FqM`}#{6`JRmYPTFrt zTdh)1%|30ib^aS}xthb}cWCSMCZGpjuk-lRGW$Tlz^%rI^ZhL1(=;sbPI@%-5$#^& zjE`zT8F`3KhJIiB3Oy701mFvyPis%e_!IPxp+7{Mv&&cL=b;BM`+DeMz_&tQ1~kH7 z2aJRt1x$p$1$cJ&+koeVzpI^~Md3H-1ZBccYEOaMGulIl;^zT7!n5f)z~{kzQ~0Oa zOSC=wGO|K8{3_tp;U~dsbI?YcWp1T|;pYIa3IAM+5of(Nx*_~)(Et5#ALt(s|5j_G z`$6F)`eOJkaDOyx=x@^F;h@etiUR&T)CBl?Xd2*Kp%y?Rd=6kFd>&vTya4d*@M8U+ zu<9bcO;dco2QJ;xK0znL7wg^HLC}1OeiUv)`xS8S*8VlT5ilF>*00cB4{rkWMtb#K z+O)_Ul+`YbB(%K7S?&PnJfn?j9l)OjJv^iR6ZZH%J*zE?^y$a6_DD{@4LJJ&H%Er` zJGGsWL;6EnKj@qQ$0xNrwf&Jkx>GxX5nlTSbjX=6tKAftPhSx^XVIr3M|9ASY{Ut} z?Zwi5i5`yJpr4>`L~hfwqPH;pm&jfEbJ~f>{n*iyk%s{@*jG>}Yx+x(uj-oq&www{ z8cdXn00DChgm3Rzs(! zO`q3rx#tRfMZ|c#@~4fkm;UX>+4PkDT~I!u zzXy0hOHlxoNH1---8$(7f9Wp*-++^Hh{DElmtSUHc97hcq(AIA_+r;^-JP0~$Rnz521Q zO)&PDj3E!>^G6Q)_a+$oy9w4lFJtwR{Obf`r({gPB^#~5kwWe=+M@o6DeBFY%3DW@ z#hgvun|nq}<=hZ$X=`8OG%;4r76-HTpfk|R|L9Wh=x}bZCs(Gn{!-b_4wT#T#jHKr zl`rI|ra5O#AzLbKE9T2><+5Evwl6<4T*wXOisfuMZx#FGMf-DhF;`f+WZ__;KtrVg z%P!>m$>FO$7H%3D%GvpW60Na@hQZFJPTRKZl7qHzN4_{{9V#vCvg{#78q6_bA;)ZJ zgEf#XMoX-E}`zK^xPgA&MGT03LBQ%N7*a_v10K> zw5|HV>x<=!mdO}CgyG(t-BZpD_wKW;k-ht>rWKWM$d};HrBZIFzX1J9m_XA9^R`uF zPbDmaab4A_9vh*1b9G;A3CE6`hv8LaAUF=ZrI0P}fx7Xm86h>WzZY@CUWH~C{;jv1 zWAgTFiJNR3F-w(p)pOYPU4`u461!1V%RR$|e0j4dxI|m5e33o1!x~}NMu?YTN7mgs zH~>aEas&CHY=L?}Ow7q)au`0ED=5E%MX`nQsEDxz${)&sAQY3|lLz7L*}{m>qqgDU z4r?fzFVdQkLV3hi@gVzSrLy0V+HKufjd?qAd$L$sH%&dcfe}R8Xis)eZVeiF1aH-5 zP6WD0aL^LZx38%rhK{_AD7Nr7p6-yV8_83fN6Nz^W$`zba`$4DNlquU(8(|^l^t2T z$adeDE0wZ)bM4k)f(I9@y<2l7Y@)&q{)dE?Ey!}tw*y!Jd)q4BoB^QM<|t0bl^_B^ zYsAK~4cX$}5%6;22pBG&qob|J>7^Rw)C_Z)R^HF~sEVVuBe`&Eu8=(}ty&sR+EI?P z(A|Ac&OVrjNVJg+-p$$lFs;p%S)iT~BuA*7`y2B8+;WzQ4j83Ghw|lpNEGf&TcH5o ztWI(6w~B?)rAzwr6K4dC~0{2TfIe2(%cy!B}ZEW)rhXE!P0Xqj0 z@DL<%40j$LwqYMm`kjZ%e5NTZ*-A0jKDq^e%}~e}L1Y*$48wobC|0wr3x#|sS1je! zIt;3$=NJou6mnS`I>DlBnO%d1ollwRJS?i~$l-?QvLmQh?P!kJVNPE+ z6wpWNb9>g#v$0Ekw2&h}4*804IF_RHDoC3S%SWL|hxl|QtN&^mR&9*U;hoLF zMr0=?R17)7Vk#Y?J$WiscCe%(`I4nj5q6cw`3-FjQ6X2Pf_12`-x?_n(#SB$pBEon$TSB3LV<5k`h{)-(aa5LwWtV)%c6SN}(W|9SJK9&KJtgY5ssE6> z?Ip_ud1oivX%mts4kV7nby?(y+~GX@2T{;d$mNDn?2w!q*zO+HB1q=u@Lrb-pH4V& z2Q2JjNM#@D>+3@8g{mf}j;y{uH`lY?ke4t+`*BQwqVfukydj(c8{o`sHVzR>G9fmJ ze25fo+c@niwP1J2Iq;F?3rN9oe0RFyJBhneko5Jf*}SQ%do85s>+A06>)F-Q+qtoC zaURjK3xV?{d7?oj5_SZY zI+ej3N9}4#@rN@I(u+0VsEl@WZ))4p-B&jz-j9DlRCC z>#V*k6T}JQ9A=fsm12wwNb0qaeo*lATFzk)Q{_Of*OJ-@`*c2mhAOGoa_S`2kcF5* zk)#Hs;Zy~^)&}bk&LeBMuk*kNoT)sj{F9xBZ6xRC-dwq_yToxp(&Re?R3H(!J~v8j zgM%`Gc1aqJWu?6#SKN!*vS);^5^}Cu2XmYFmf?_>IO(;V;*-n3T(5V6x&SKdft<-irzcE|P@4Dg;R?JJe94AffWmwqif(4s_W7oTZ$qj%mRu3L;1MPGsU5~vZEl!Zvj{E$SW zV3}+hA@o9fD2rbep^zZzjvJlEWyUh&Qe%;^*+@~F(Qd59$V&86#7~`;8y8FaQnZ%f zy&AadjaJNd0B6uTQ0iDv zbBYcD=D|IWnZ5XjJc}Y5dFDVtJMnLVDcN(tD#IXEd9H#c)T8@T*-3#1dC?mq#(Nm33y-i@x2Hh%5+%6>`6S!-p;U3M%fCGbEb;UA<-dY+x7Di z3sT`iCy*N0%6nm%G9*Bh?jJ&*y@vVorN%;wt&xIXj<@uMGH=5cjBz@z3$eP&>q1Cf z$7||BaOPdB?56mVpZ(-~>~>H^0Y}j#yyHEBfwubAID)&w7VesDzYS(c@vF}cbeMK!cM?x9@;A~3bqbl`# z;=lIYQ9Pq$J+*Y$2U7TH$`a^O`6v5_^3V66n^o^j^NccG)eSkyu=se-=e%$jT4Rq@ z=cLLRjH}`yg}F1>V(sMA-xHm-F6gE{8d4(lyOspMdmi|OZ~eXfU0=F+{rYpeKj5Rl zH?FyI`&r9gyva8;eyJX94or<7>(t`My4}V`4+VcMM zfQO<{4~|XU7xnmB_}#4gbc_V>_IOj8))){N5vWF@()J281o{N}1qKD00z(4B0(Aq_ znz`8kb^(ts9>aSo-e+M(Z{}(6(zRwmY7zSJY5tT4Bk^e{OX9O26l9FI=)PuroQwB7 zyf46ZbVeaiV9<>b5F7hT48UC2(AW=P9gIIE z@P$aq+Z>IJy%ZmNrMaa!n9^IKE$IJKAf@X~)Yt@t!_du;Hhv&B_HU3s0#bU^%esj& z!DHTNki|IW#s5U03}K`t6U)(YaWW7~LtO%zZpt}>vo7?T4f4-~62r6y|9cs_P) zgxy`&!5v>W4>CZ-iTesP?#DlpuLbb-z}d0^zX6BB|1c26TgTuu!>`4sdHs5P8VA#? zpx+yBX^zi|Mty!m58#DGJkZRDvkYH+>;d2%i24K3_$+kd2N=un!>M@k1QQrL5eUE& z{d#ji_XTyIsry1fzppudAP_V|VZT2LD|6~-VWem?{w?=o8mKM?g=1bcmV13{KY_t> z{Y_At7GE6C#%JjPjK`OY-(?vG@1Th3H=@`g`Mf=}NCXQYvdc zwjfdhe*cXn?#uoEbJ8(zo-;UnHPP0dj-G$J?u4Z`mD+Fr``2H*WUc=$X2lm{=OQ~i z*iZEMjV>jkpKv=>?bo*4|No-u7@nTlxWvmh3-UdDE+^l~GsfHJQfhS-^*>wu<4O5j RpkK