Reading legacy Word forms checkboxes converted to PDF - c#

Our customers sends us orders as PDF forms which is generated from a Word document built with legacy forms.
Currently people at our customer center is punching the orders into our system, but we have decided to try and automate this task.
I'm able to read the content of the PDF with a simple PdfReader per page:
public static string GetPdfText(string path)
{
var text = string.Empty;
using (var reader = new PdfReader(path))
{
for (var page = 1; page <= reader.NumberOfPages; page++)
{
text += PdfTextExtractor.GetTextFromPage(reader, page);
}
}
return text;
}
But not the checkboxes...
I am able to detect the checkboxes as dictionaries while running through every object in the PDF, but I'm unable to distinguish them from other objects or read the value...
public static IEnumerable<PdfDictionary> ReadCheckboxes(string path)
{
using (var reader = new PdfReader(path))
{
var checkboxes = new List<PdfDictionary>();
for (var i = 0; i < reader.XrefSize; i++)
{
var pdfObject = reader.GetPdfObject(i);
checkboxes.Add((PdfDictionary) pdfObject);
}
return checkboxes;
}
}
What am I missing? I've also tried reading the AcroFields, but they're empty...
I have uploaded a sample PDF with legacy checkboxes here.
Currently there is not option to integrate between our systems or do any changes to the underlying PDF or Word document.

The OP indicated in comments that a solution which returns an output like "checkbox at position x0, y0, checked; checkbox at position x1, y1, not checked; ..." would suffice, i.e. his "forms" are static enough so that these positions allow identification of the meaning of the respective checkboxes. Thus, here an implementation of this variant.
I just saw that the question is tagged c# while I have implemented the search using Java. This should not be too big a problem, the code should be easy to port. If there are problems porting, I'll add a C# version here.
As the checkboxes are drawn using vector graphics, the text extraction already used by the OP does not find them. Fortunately, though, the iText parsing framework can also be used to look for vector graphics.
Thus, we first need an ExtRenderListener (IExtRenderListener in iTextSharp) which collects the boxes. It only has non-trivial implementations of the interface methods modifyPath and renderPath:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
switch (renderInfo.getOperation())
{
case PathConstructionRenderInfo.RECT:
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
rectangle = new Rectangle(x, y, x+w, y+h);
}
case PathConstructionRenderInfo.MOVETO:
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
moveToVector = new Vector(x, y, 1);
lineToVector = null;
break;
}
case PathConstructionRenderInfo.LINETO:
{
if (moveToVector != null)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
lineToVector = new Vector(x, y, 1);
}
break;
}
default:
moveToVector = null;
lineToVector = null;
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (rectangle != null)
{
Vector a = new Vector(rectangle.getLeft(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
Vector b = new Vector(rectangle.getRight(), rectangle.getBottom(), 1).cross(renderInfo.getCtm());
Vector c = new Vector(rectangle.getRight(), rectangle.getTop(), 1).cross(renderInfo.getCtm());
Vector d = new Vector(rectangle.getLeft(), rectangle.getTop(), 1).cross(renderInfo.getCtm());
Box box = new Box(new LineSegment(a, c), new LineSegment(b, d));
boxes.add(box);
}
if (moveToVector != null && lineToVector != null)
{
if (!boxes.isEmpty())
{
Vector from = moveToVector.cross(renderInfo.getCtm());
Vector to = lineToVector.cross(renderInfo.getCtm());
boxes.get(boxes.size() - 1).selectDiagonal(new LineSegment(from, to));
}
}
}
moveToVector = null;
lineToVector = null;
rectangle = null;
return null;
}
Vector moveToVector = null;
Vector lineToVector = null;
Rectangle rectangle = null;
public Iterable<Box> getBoxes()
{
return boxes;
}
final List<Box> boxes = new ArrayList<Box>();
(from CheckBoxExtractionStrategy.java)
It uses a helper class Box which models the checkboxes using their respective diagonals:
public class Box
{
public LineSegment getDiagonal()
{
return diagonalA;
}
public boolean isChecked()
{
return selectedA && selectedB;
}
Box(LineSegment diagonalA, LineSegment diagonalB)
{
this.diagonalA = diagonalA;
this.diagonalB = diagonalB;
}
void selectDiagonal(LineSegment diagonal)
{
if (approximatelyEquals(diagonal, diagonalA))
selectedA = true;
else if (approximatelyEquals(diagonal, diagonalB))
selectedB = true;
}
boolean approximatelyEquals(LineSegment a, LineSegment b)
{
float permissiveness = a.getLength() / 10.0f;
if (approximatelyEquals(a.getStartPoint(), b.getStartPoint(), permissiveness) &&
approximatelyEquals(a.getEndPoint(), b.getEndPoint(), permissiveness))
return true;
if (approximatelyEquals(a.getStartPoint(), b.getEndPoint(), permissiveness) &&
approximatelyEquals(a.getEndPoint(), b.getStartPoint(), permissiveness))
return true;
return false;
}
boolean approximatelyEquals(Vector a, Vector b, float permissiveness)
{
return a.subtract(b).length() < permissiveness;
}
boolean selectedA = false;
boolean selectedB = false;
final LineSegment diagonalA, diagonalB;
}
(Inner class in CheckBoxExtractionStrategy.java)
Applying it like this to the sample document:
for (int page = 1; page <= pdfReader.getNumberOfPages(); page++)
{
System.out.printf("\nPage %s\n====\n", page);
CheckBoxExtractionStrategy strategy = new CheckBoxExtractionStrategy();
PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
parser.processContent(page, strategy);
for (Box box : strategy.getBoxes())
{
Vector basePoint = box.getDiagonal().getStartPoint();
System.out.printf("at %s, %s - %s\n", basePoint.get(Vector.I1), basePoint.get(Vector.I2),
box.isChecked() ? "checked" : "unchecked");
}
}
one gets the output
Page 1
====
at 73.104, 757.8 - checked
at 86.544, 757.8 - checked
at 99.984, 757.8 - unchecked
for the OP's document

Related

Esri GIS setting to the same ViewPoint yields different result

I'm trying to implement some zoom-in-zoom out feature in my program, for this I'm using two ViewPoints with Wgs84 projection, one is default and other one is current, when I return to the default one it seems to "drift" by ~ 0.1 degrees(P.s the logic working as intended)
so on the first time _currentViewPoint ,XMin for example, is 4.77075... which is exactly as _defaultViewPoint XMin
but on the second time(after some panning and zooming the else condition is met and returning to _defaultViewPoint) the _currentViewPoint XMin is 4.62159...
why this so called drift happens?
public void Click()
{
if(_firstClick)
{
_firstClick = false;
_mapService = GetMapService();
_defaultViewPoint = _mapService.IntialViewPoint;
}
_currentViewPoint = _mapService.GetViewPoint();
if(ArePointsEqual(_defaultViewPoint,_currentViewPoint)
{
if(_viewPointToZoom != null)
{
_mapService.SetViewPointAsync(_viewPointToZoom)
}
}
else
{
_viewPointToZoom = _mapService.GetViewPoint();
_mainMapService.SetViewPointAsync(_defaultViewPoint);
}
}
public ViewPoint GetViewPoint()
{
ViewPoint current = null;
try
{
current = EsriGeoView.GetCurrentViewPoint(ViewpointType.BoundingGeomerty);
return ProjectToWgs84(current.TargetGeometry);
}
catch(...)
{
//catch
}
return current;
}
private ViewPoint ProjectToWgs94(Geometry targetGeometry)
{
try
{
var geometry = GeometryEngine.Project(targetGeometry, SpatialReefernces.Wgs84);
return new ViewPoint(geometry);
}
catch(...)
{
//catch
}
}
private async Task SetViewPointAsync(ViewPoint viewpoint)
{
//some non relevent code
await EsriGeoView.SetViewPointAsync(viewpoint);
}
private bool AreViewPointsEqual(Viewpoint vp1, Viewpoint vp2, double tolerance = 1e-4)
{
Envelope r1 = vp1.TargetGeometry.Extent, r2 = vp2.TargetGeometry.Extent;
var x1 = (r1.XMax + r1.XMin) / 2;
var x2 = (r1.YMax + r1.YMin) / 2;
var y1 = (r2.XMax + r2.XMin) / 2;
var y2 = (r2.YMax + r2.YMin) / 2;
return Math.abs(x1-x2) < tolerance && Math.abs(y1-y2) < tolerance);
}

Why is each frame presented in my SharpDX Direct2D setup alternating between black and my rendered content?

Sorry this is so long, I wanted to proved enough code as it's hard to explain on it's own
I'm setting up a small test program to make a 2D visual gauge. The structure of the project is that I have a class; 'DirectX11Renderer' which initialises all the SharpDX objects to set up the factories, devices, back buffers, rendertargets, swapchains and layers.
This class then contains drawing methods for drawing basic shapes, filled shapes and text, they can likely be ignored, however the idea of the program is to be able to render the static content of the gauge on one layer (the background, stuff that doesn't really move) and the dynamic content on another layer, so each drawing method has an enum as an argument which specifies the layer to draw on, and then that is passed into this method, which starts drawing on the render target (if not already), pops the last pushed layer (if there was one) and pushes the layer that is required to be drawn on:
private void StartDrawingAndSwitchLayer(TargetLayer layer, bool draw = true)
{
if (draw && !isDrawing)
{
d2dRenderTarget.BeginDraw();
isDrawing = true;
}
switch (layer)
{
case TargetLayer.Static:
if (isDynamicLayerPushed)
{
d2dRenderTarget.PopLayer();
isDynamicLayerPushed = false;
}
if (!isStaticLayerPushed)
{
d2dRenderTarget.PushLayer(ref layerParams, staticLayer);
isStaticLayerPushed = true;
}
break;
case TargetLayer.Dynamic:
if (isStaticLayerPushed)
{
d2dRenderTarget.PopLayer();
isStaticLayerPushed = false;
}
if (!isDynamicLayerPushed)
{
d2dRenderTarget.PushLayer(ref layerParams, dynamicLayer);
isDynamicLayerPushed = true;
}
break;
case TargetLayer.None:
if (isStaticLayerPushed)
{
d2dRenderTarget.PopLayer();
isStaticLayerPushed = false;
}
if (isDynamicLayerPushed)
{
d2dRenderTarget.PopLayer();
isDynamicLayerPushed = false;
}
break;
}
if (!draw && isDrawing)
{
d2dRenderTarget.EndDraw();
isDrawing = false;
}
}
So in a class that uses an instance of this DirectX11Renderer class, it starts by setting the background colour without pushing a layer, rendering the static content by clearing the layer then calling several of the draw methods using the static layer as an argument for the enum to push the static layer, then rendering the dynamic content every time a value changes, doing the same thing by using the enum as an argument to pop the static layer and push the dynamic layer, then drawing on it. Finally at the end of the dynamic drawing method I present the frame by using the enum argument of TargetLayer.None to pop all layers, and then present the frame.
What I'm finding is that every time I change the value, (rendering the dynamic content on it's layer, popping the layer then presenting) the screen alternates between showing the content of the dynamic layer on a black background, and all the static and dynamic content as I would want to see it if this were working 100%.
As a minimum working example, here is the code for a form which contains the gauge control:
using System.Windows.Forms;
using System;
namespace SharpDXGauge
{
public partial class Form1 : Form
{
Gauge control;
public Form1()
{
InitializeComponent();
control = new Gauge(100, 0);
control.Dock = DockStyle.Left;
this.Controls.Add(control);
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
control.IndicatorValue = (float)numericUpDown1.Value;
}
}
}
Here is a minimal example of the gauge class (I've removed most of the rendering):
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.DirectWrite;
using DXGI = SharpDX.DXGI; // Direct X Graphics Infrastructure
using D3D = SharpDX.Direct3D;
using D2D1 = SharpDX.Direct2D1;
using D3D11 = SharpDX.Direct3D11;
using DWrite = SharpDX.DirectWrite;
namespace SharpDXGauge
{
public partial class Gauge : UserControl
{
#region Members
private DirectX11Renderer renderer;
private float max;
private float min;
private float indicatorValue;
private Point topLeft;
private Point bottomRight;
private float height;
private float width;
Point valueTextTopLeft;
Point valueLabelOutlineTopLeft;
Point valueLabelOutlineBottomRight;
#endregion
public Gauge(float max, float min)
{
InitializeComponent();
renderer = new DirectX11Renderer(this);
this.max = max;
this.min = min;
this.indicatorValue = min;
renderer.SetBackgroundColor(renderer.SystemDrawingColorToSharpDXColor(this.BackColor));
DrawStaticContent();
DrawDynamicContent();
}
#region Drawing
private void CalculateSizes()
{
// Static
topLeft = new Point() { x = 25, y = 10 };
bottomRight = new Point() { x = topLeft.x + 20, y = this.Height - 50 };
height = bottomRight.y - topLeft.y;
width = bottomRight.x - topLeft.x;
// Dynamic
string valueText = indicatorValue.ToString();
Size2F valueTextSize = renderer.GetTextSize(valueText, 9.75f);
valueTextTopLeft = new Point() { x = bottomRight.x + 15, y = bottomRight.y - height * indicatorValue / (max - min) - (valueTextSize.Height / 2) };
}
private void DrawStaticContent()
{
CalculateSizes();
renderer.ClearLayer(TargetLayer.Static, Color.Transparent);
renderer.DrawRectangle(TargetLayer.Static, topLeft, bottomRight, Color.Black);
DrawDynamicContent();
renderer.PresentFrame();
}
private void DrawDynamicContent()
{
renderer.ClearLayer(TargetLayer.Dynamic, Color.Transparent);
CalculateSizes();
renderer.DrawText(TargetLayer.Dynamic, indicatorValue.ToString(), Color.White, valueTextTopLeft, 9.75f);
renderer.DrawRoundedRectangle(TargetLayer.Dynamic, valueLabelOutlineTopLeft, valueLabelOutlineBottomRight, 20, Color.Black);
renderer.PresentFrame();
}
#endregion
#region Properties
public float IndicatorValue
{
get
{
return indicatorValue;
}
set
{
if (indicatorValue != value)
{
indicatorValue = value;
DrawDynamicContent();
}
}
}
#endregion
#region Disposal
public new void Dispose()
{
renderer.Dispose();
}
#endregion
}
}
And my DirectX11Renderer class, I've removed all the unnecessary drawing methods etc:
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.DirectWrite;
using DXGI = SharpDX.DXGI; // Direct X Graphics Infrastructure
using D3D = SharpDX.Direct3D;
using D2D1 = SharpDX.Direct2D1;
using D3D11 = SharpDX.Direct3D11;
using DWrite = SharpDX.DirectWrite;
using DMaths = SharpDX.Mathematics.Interop;
namespace SharpDXGauge
{
public enum TargetLayer
{
Static,
Dynamic,
None
}
public struct Point
{
public float x;
public float y;
}
public partial class DirectX11Renderer : IDisposable
{
private Surface backBuffer;
private D3D11.Device d3dDevice;
private D3D11.Device1 d3dDevice1;
private DXGI.Device2 dxgiDevice2;
private D2D1.Device d2dDevice;
private SwapChain1 swapChain1;
private D2D1.Factory d2dFactory;
private DXGI.Factory2 dxgiFactory2;
private Adapter dxgiAdapter;
private RenderTarget d2dRenderTarget;
private DWrite.Factory dWriteFactory;
private Layer staticLayer;
private Layer dynamicLayer;
SolidColorBrush solidColorBrush;
//RadialGradientBrush radialGradientBrush;
//LinearGradientBrush linearGradientBrush;
private Color lastBackColour;
private bool isDrawing;
private LayerParameters layerParams;
private bool isStaticLayerPushed;
private bool isDynamicLayerPushed;
List<double> renderTimesMsec = new List<double>();
public DirectX11Renderer(Control surfaceToDrawOn)
{
// There seems to be endless ways to set up the different objects required for rendering
// This is the most efficient I've found that get's everything you need for 2d rendering
SwapChainDescription1 description = new SwapChainDescription1()
{
Width = surfaceToDrawOn.Width,
Height = surfaceToDrawOn.Height,
Format = Format.R8G8B8A8_UNorm,
Stereo = false,
SampleDescription = new SampleDescription(1, 0),
Usage = Usage.BackBuffer | Usage.RenderTargetOutput,
BufferCount = 2,
Scaling = Scaling.None,
SwapEffect = SwapEffect.FlipSequential,
};
d3dDevice = new D3D11.Device(DriverType.Hardware, DeviceCreationFlags.BgraSupport);
d3dDevice1 = d3dDevice.QueryInterface<D3D11.Device1>();
dxgiDevice2 = d3dDevice1.QueryInterface<DXGI.Device2>();
d2dDevice = new D2D1.Device(dxgiDevice2);
dxgiAdapter = dxgiDevice2.Adapter;
d2dFactory = new D2D1.Factory(D2D1.FactoryType.SingleThreaded);
dxgiFactory2 = dxgiAdapter.GetParent<DXGI.Factory2>();
swapChain1 = new SwapChain1(dxgiFactory2, d3dDevice1, surfaceToDrawOn.Handle, ref description);
backBuffer = swapChain1.GetBackBuffer<Surface>(0);
d2dRenderTarget = new RenderTarget(d2dFactory, backBuffer,
new RenderTargetProperties(new PixelFormat(Format.R8G8B8A8_UNorm, D2D1.AlphaMode.Premultiplied)));
staticLayer = new Layer(d2dRenderTarget);
dynamicLayer = new Layer(d2dRenderTarget);
dWriteFactory = new DWrite.Factory(DWrite.FactoryType.Shared);
layerParams = new LayerParameters()
{
ContentBounds = RectangleF.Infinite,
Opacity = 1
};
solidColorBrush = new SolidColorBrush(d2dRenderTarget, Color.White);
}
public void PresentFrame()
{
StartDrawingAndSwitchLayer(TargetLayer.None, false);
swapChain1.Present(0, PresentFlags.None);
}
public void SetBackgroundColor(Color colour)
{
StartDrawingAndSwitchLayer(TargetLayer.None);
lastBackColour = colour;
d2dRenderTarget.Clear(colour);
}
public void ClearLayer(TargetLayer layer, Color fillColorAfterClear)
{
StartDrawingAndSwitchLayer(layer);
d2dRenderTarget.Clear(fillColorAfterClear);
}
public void DrawRectangle(TargetLayer layer, Point topLeft, Point bottomRight, Color colour, float strokeWidth = 1)
{
StartDrawingAndSwitchLayer(layer);
solidColorBrush.Color = colour;
d2dRenderTarget.DrawRectangle(new DMaths.RawRectangleF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y), solidColorBrush, strokeWidth);
}
public void DrawRoundedRectangle(TargetLayer layer, Point topLeft, Point bottomRight, float roundingRadius, Color colour, float strokeWidth = 1)
{
StartDrawingAndSwitchLayer(layer);
solidColorBrush.Color = colour;
RoundedRectangle roundedRect = new RoundedRectangle();
roundedRect.Rect = new DMaths.RawRectangleF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
roundedRect.RadiusX = roundingRadius;
roundedRect.RadiusY = roundingRadius;
d2dRenderTarget.DrawRoundedRectangle(roundedRect, solidColorBrush, strokeWidth);
}
public void DrawText(TargetLayer layer, string text, Color colour, Point topLeft, float fontSize, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, string font = "Microsoft Sans Serif")
{
StartDrawingAndSwitchLayer(layer);
solidColorBrush.Color = colour;
TextFormat format = new TextFormat(dWriteFactory, font, weight, style, FontStretch.Normal, fontSize);
Size2F textSize = GetTextSize(text, fontSize, 100, 100);
Point bottomRight = new Point() { x = topLeft.x + (int)Math.Ceiling(textSize.Width), y = topLeft.y + (int)Math.Ceiling(textSize.Height) };
d2dRenderTarget.DrawText(text, format,
new DMaths.RawRectangleF(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y),
solidColorBrush);
}
public Size2F GetTextSize(string text, float fontSize, float maxWidth = 100000, float maxHeight = 100000, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, string font = "Microsoft Sans Serif")
{
TextFormat format = new TextFormat(dWriteFactory, font, weight, style, FontStretch.Normal, fontSize);
TextLayout layout = new TextLayout(dWriteFactory, text, format, maxWidth, maxHeight);
return new Size2F(layout.Metrics.Width, layout.Metrics.Height);
}
public Color SystemDrawingColorToSharpDXColor(System.Drawing.Color colour)
{
Color toRtn = new Color(new byte[4] { colour.R, colour.G, colour.B, colour.A, });
return toRtn;
}
private void StartDrawingAndSwitchLayer(TargetLayer layer, bool draw = true)
{
if (draw && !isDrawing)
{
d2dRenderTarget.BeginDraw();
isDrawing = true;
}
switch (layer)
{
case TargetLayer.Static:
if (isDynamicLayerPushed)
{
d2dRenderTarget.PopLayer();
isDynamicLayerPushed = false;
}
if (!isStaticLayerPushed)
{
d2dRenderTarget.PushLayer(ref layerParams, staticLayer);
isStaticLayerPushed = true;
}
break;
case TargetLayer.Dynamic:
if (isStaticLayerPushed)
{
d2dRenderTarget.PopLayer();
isStaticLayerPushed = false;
}
if (!isDynamicLayerPushed)
{
d2dRenderTarget.PushLayer(ref layerParams, dynamicLayer);
isDynamicLayerPushed = true;
}
break;
case TargetLayer.None:
if (isStaticLayerPushed)
{
d2dRenderTarget.PopLayer();
isStaticLayerPushed = false;
}
if (isDynamicLayerPushed)
{
d2dRenderTarget.PopLayer();
isDynamicLayerPushed = false;
}
break;
}
if (!draw && isDrawing)
{
d2dRenderTarget.EndDraw();
isDrawing = false;
}
}
public void Dispose()
{
// Release all resources
backBuffer.Dispose();
d3dDevice.Dispose();
d3dDevice1.Dispose();
dxgiDevice2.Dispose();
d2dDevice.Dispose();
swapChain1.Dispose();
d2dFactory.Dispose();
dxgiFactory2.Dispose();
dxgiAdapter.Dispose();
d2dRenderTarget.Dispose();
staticLayer.Dispose();
dynamicLayer.Dispose();
}
}
}
Putting this together gives a minimal example of what I'm seeing, I've removed code handling form resizing so with this, the bottom half appears black to begin with, but using a numerical updown on the form to update the IndicatorValue property creates the alternating all black/ what I want to see frames.
I've been trying to ebug this for a while, I'm beginning to wonder if it's to do with how I've set up my swap chain? At first I thought it must be how I'm handing my layers, and perhaps somehow every other change and dynamic content render it's not popping the layer and only showing the content of the dynamic layer? But stepping through each update has it pushing and popping the layers correctly as far as I can see, and generally not doing it right throws an exception.
Sorry this is so long,
Joe.

Why I don't see any more the TextField inside OnGUI in EditorWindow script?

using UnityEngine;
using UnityEditor;
public class SearchableWindow : EditorWindow
{
string searchString = "";
[MenuItem("Tools/Searching")]
private static void Searching()
{
const int width = 340;
const int height = 420;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<SearchableWindow>().position = new Rect(x, y, width, height);
}
void OnGUI()
{
GUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
searchString = GUILayout.TextField(searchString, EditorStyles.toolbarTextField);
GUILayout.EndHorizontal();
var items = Selection.gameObjects;
// Do comparison here. For example
for (int i = 0; i < items.Length; i++)
{
if (items[i].name.Contains(searchString))
{
GUILayout.Label(items[i].name);
}
}
}
}
Before it was working fine but now everything is very slow and also I don't see the TextField and when selecting a GameObject in the Hierarchy it's taking almost 5 seconds to show it in the Editor Window.
And before it was all fast and showing the whole Hierarchy in the Editor Window.
Now it's empty:
I took the answer from this question:
Searchable
OnGUI is only called while the mouse is (moved/clicked) over the according Window -> it is not taking almost 5 seconds but until you move your mouse over the window again.
In order to solve that you could implement EditorWindow.OnSelectionChange and force a EditorWindow.Repaint so your window is refreshed everytime the Selection changes.
private void OnSelectionChange()
{
Repaint();
}
The second issue is produced as follows:
GUILayout.TextField together with GUILayout.FlexibleSpace if not defined different uses automatically the width of the inserted text -> Since you have an empty text at start the width is almost 0 ... you actually can see your TextField very thin there and it gets bigger as you fill it:
Using the drawers from the EditorGUILayout instead solves this:
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
searchString = EditorGUILayout.TextField(searchString, EditorStyles.toolbarTextField);
EditorGUILayout.EndHorizontal();
var items = Selection.gameObjects;
// Do comparison here. For example
foreach (var selectedObject in selected)
{
if (selectedObject .name.Contains(searchString))
{
EditorGUILayout.LabelField(selectedObject.name);
}
}
Tip If you replace
EditorGUILayout.LabelField(items[i].name);
with
if (GUILayout.Button(selectedObject.name, EditorStyles.label))
{
EditorGUIUtility.PingObject(selectedObject);
}
you can click on the name and "ping" the according GameObject in the hierachy!
Update since asked in the comments
If you want to include all children of the selection recursive you could do something like (there might be better ways but that's what I came up with within 10 minutes)
private static IEnumerable<GameObject> GetChildrenRecursive(GameObject root)
{
var output = new List<GameObject>();
//add the root object itself
output.Add(root);
// iterate over direct children
foreach (Transform child in root.transform)
{
// add the child itslef
output.Add(child.gameObject);
// Recursion here: Get all subchilds of this child
var childsOfchild = GetChildrenRecursive(child.gameObject);
output.AddRange(childsOfchild);
}
return output;
}
private static IEnumerable<GameObject> GetChildrenRecursive(IEnumerable<GameObject> rootObjects)
{
var output = new List<GameObject>();
foreach (var root in rootObjects)
{
output.AddRange(GetChildrenRecursive(root));
}
// remove duplicates
return output.Distinct().ToList();
}
and using
var selected = GetChildrenRecursive(Selection.gameObjects);
Another Update
Since this might not be very efficient you probably should move it to Searching and than only refresh the selected value also in OnSelectionChange like
public class SearchableWindow : EditorWindow
{
private string searchString = "";
private List<GameObject> selected;
[MenuItem("Tools/Searching")]
private static void Searching()
{
const int width = 340;
const int height = 420;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
var window = GetWindow<SearchableWindow>();
window.position = new Rect(x, y, width, height);
window.selected = GetChildrenRecursive(Selection.gameObjects).ToList();
}
private void OnSelectionChange()
{
selected = GetChildrenRecursive(Selection.gameObjects).ToList();
Repaint();
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
searchString = EditorGUILayout.TextField(searchString, EditorStyles.toolbarTextField);
EditorGUILayout.EndHorizontal();
// only as fallback
if (selected == null)
{
selected = GetChildrenRecursive(Selection.gameObjects).ToList();
}
// Do comparison here. For example
foreach (var selectedObject in selected)
{
if (selectedObject.name.Contains(searchString))
{
if (GUILayout.Button(selectedObject.name, EditorStyles.label))
{
EditorGUIUtility.PingObject(selectedObject);
}
}
}
}
private static IEnumerable<GameObject> GetChildrenRecursive(GameObject root)
{
var output = new List<GameObject>();
//add the root object itself
output.Add(root);
// iterate over direct children
foreach (Transform child in root.transform)
{
// add the children themselves
output.Add(child.gameObject);
var childsOfchild = GetChildrenRecursive(child.gameObject);
output.AddRange(childsOfchild);
}
return output;
}
private static IEnumerable<GameObject> GetChildrenRecursive(IEnumerable<GameObject> rootObjects)
{
var output = new List<GameObject>();
foreach (var root in rootObjects)
{
output.AddRange(GetChildrenRecursive(root));
}
// remove any duplicates that would e.g. appear if you select a parent and its child
return output.Distinct();
}
}

how to draw the blocks for tetris

So Im making tetris and I dont know how to draw the blocks( L,I,Z etc) I have one block as Texture2D and every class for the blocks look like this:
namespace Tetris
{
public class ZBlock
{
Color Color;
const int x = 4;
const int y = 4;
bool[,] vorm;
public bool[,] zblock()
{
vorm = new bool[x, y];
for(int i=0; i< x; i++)
for (int j=0; j<y; j++)
{
vorm[i, j] = false;
vorm[0, 0] = true;
vorm[1, 0] = true;
vorm[1, 1] = true;
vorm[2, 1] = true;
}
Color = Color.Purple;
return vorm;
}
}
and this is the block class:
namespace Tetris
{
public class Block
{
Texture2D block;
Vector2 BlockPosition = new Vector2(30, 30);
float FallTimer;
Random Random = new Random();
ZBlock zBlock = new ZBlock();
TBlock tBlock = new TBlock();
SBlock sBlock = new SBlock();
OBlock oBlock = new OBlock();
JBlock jBlock = new JBlock();
LBlock lBlock = new LBlock();
IBlock iblock = new IBlock();
public bool[,] blockvorm()
{
bool[,] vorm;
vorm = new bool[4, 4];
vorm[3, 3] = false;
int r = Random.Next(7);
if (r == 0)
{
ZBlock.zblock();
}
else if (r == 1)
{
TBlock.tblock();
}
else if (r == 2)
{
SBlock.sblock();
}
else if (r == 3)
{
OBlock.oblock();
}
else if (r == 4)
{
JBlock.jblock();
}
else if (r == 5)
{
LBlock.lblock();
}
else if (r == 6)
{
IBlock.iblock();
}
return vorm;
}
public TBlock TBlock
{
get { return tBlock; }
}
public ZBlock ZBlock
{
get { return zBlock; }
}
public SBlock SBlock
{
get { return sBlock; }
}
public OBlock OBlock
{
get { return oBlock; }
}
public JBlock JBlock
{
get { return jBlock; }
}
public LBlock LBlock
{
get { return lBlock; }
}
public IBlock IBlock
{
get { return iblock; }
}
public void Draw(GameTime gameTime, SpriteBatch spriteBatch, ContentManager Content)
{
block = Content.Load<Texture2D>("Block");
int[,] Grid = Tetris.GameWorld.TetrisGrid;
spriteBatch.Begin();
spriteBatch.Draw(?????????????);
spriteBatch.End();
}
So the problem is: I dont know how to draw those blocks (I know how to draw one block but I want the complete ones). I thought maybe ZBlock.vorm or ZBLock.zblock but both give errors.
Does anyone know how to draw the blocks?
Ok so here is a partial answer. What you want to do is basically just draw each block with a certain offset from the next block equal to: blockWidth / 2 in pixels. This means that the blocks will be correctly orientated without overlap.
Here is what you should put in the draw statement:
public void Draw(int theXPosition, int theYPosition, Color theColor, SpriteBatch theSpriteBatch, Texture2D theBlockTexture)
{
int aTextureStartX = Color * Convert.ToInt32(mBlockSize);
for (int aBlock = 0; aBlock < mNumberOfBlocks; aBlock++)
{
int aXPosition = (int)(theXPosition + (CurrentShape[Rotation, aBlock, 0] * mBlockSize));
int aYPosition = (int)(theYPosition + (CurrentShape[Rotation, aBlock, 1] * mBlockSize));
theSpriteBatch.Draw(theBlockTexture, new Rectangle(aXPosition, aYPosition, mBlockSize, mBlockSize), new Rectangle(aTextureStartX, 0, mBlockSize, mBlockSize),
}
}
This is from a blog: http://www.xnadevelopment.com/tutorials/fallingblocksyoumovetomakelines/fallingblocksyoumovetomakelines.shtml
The source code is at the top of the page.

List Operations without ForLoops

I have a class with the following members:
X
Y
Width
Height
One can create a rectangle with these parameters.
Now my problem is I have a list of this class, List<MyClass>.
I need to compare each object of the list with all the remaining objects in such a way that if the currentObject.Location(X, Y) falls in the rectangle(X, Y, Width, Height) of the other object, I need to delete the other object from the list.
I implemented it with for loops.
But the major problem is: performance.
My minimum list count is 300000.
Is there any procedure to improve the performance for this task uisng any of the .Net versions including LINQ?
`public class RectBase
{
private int _rectId;
private PointF _rectLocation;
private SizeF _rectSize;
public RectBase()
{
_rectId = -911;
_rectLocation = new PointF(0, 0);
_rectSize = new SizeF(0, 0);
}
public RectBase(int id, PointF loc, SizeF size)
{
_rectId = id;
_rectLocation = loc;
_rectSize = size;
}
public bool IsIntersected(RectBase otherRectObject)
{
RectangleF currentRect = new RectangleF(_rectLocation, _rectSize);
if (currentRect.Contains(otherRectObject.RectLocation))
return true;
else
return false;
}
public int RectId
{
get { return _rectId; }
set { _rectId = value; }
}
public PointF RectLocation
{
get { return _rectLocation; }
set { _rectLocation = value; }
}
public SizeF RectSize
{
get { return _rectSize; }
set { _rectSize = value; }
}
}
public class RectProcessor
{
List<RectBase> _rectList;
int maxCount = 300000;
public RectProcessor()
{
_rectList = new List<RectBase>();
FillList();
}
private void FillList()
{
// Adding the items to the list with dummy values
for (int i = 0; i < maxCount; i++)
{
int id = i+1;
PointF loc = new PointF(id, id);
SizeF sz = new SizeF(id, id);
RectBase obj = new RectBase(id, loc, sz);
_rectList.Add(obj);
}
}
private void RemoveIntersectedObjects()
{
List<RectBase> filteredList = new List<RectBase>();
bool isIntersected = false;
for (int i = 0; i < maxCount; i++)
{
for (int j = 0; j < maxCount; j++)
{
if (_rectList[i].IsIntersected(_rectList[j]))
{
isIntersected = true;
break;
}
}
if (!isIntersected)
{
filteredList.Add(_rectList[i]);
}
isIntersected = false;
}
}
}
`
The problem isn't eliminating for loops, at least in the way that you're thinking of it. Rewriting this in LINQ is just going to hide the for loops but they'll still be there. And that's the fundamental problem. Your algorithm, as written, is O(n^2) and that's why you see a ridiculous explosion in time as you go from 20,000 elements to 300,000 elements. You're doing 400,000,000 comparisons in the first case, and 90,000,000,000 in the second case and it will continue to grow like O(n^2).
So, the question you really want to ask is: is there an algorithm with time complexity better than O(n^2) for this problem?
Frankly, I don't know the answer to that question. I suspect that the answer is no: you can't know if a point is contained in some rectangle without comparing it to all the rectangles, and you have to inspect all the points. But maybe there's a clever way to do it such as computing the convex hull of all the rectangles and use that somehow?
This problem is an example of the field of computational geometry.

Categories