Rotate an Itext7 table - rotation

I made a table with Itext7 using the Table object and inserted into a Canvas object.
This is the table :
Now I would like to rotate the table (but not the page) to achieve this:
How can I rotate the Table or Canvas 270 degrees.
This is the code I use to generate the table (It is part of a larger project, however this is the method that generates the table) :
num_row = num_row + 1; // Indica il numero di riga della tabella
PdfDocument_Configurazione_Text cfgText;
if (intestazione) {
cfgText = cfg.getCfg_intestazione();
} else {
cfgText = cfg.getCfg_riga();
}
if (cfgText == null) {
throw new Exception("Configurazione testo " + (intestazione ? "intestazione" : "riga") + " null");
}
campi = campi == null ? new ArrayList<>() : campi;
Paragraph p;
PdfFont font = PdfFontFactory.createFont(cfgText.getTipo_font_testo());
table = new Table(cfg.getTabella_num_col());
for (int i = 1; i <= cfg.getTabella_num_col(); i++) {
Cell cell = new Cell();
p = new Paragraph("");
String str = campi.size() < i ? " " : campi.get(i - 1);
p.add(str);
p.setFont(font);
p.setFontSize(cfgText.getDim_font_testo());
if (cfgText.isTesto_bold()) {
p.setBold();
}
if (cfgText.isTesto_italic()) {
p.setItalic();
}
if (cfgText.isTesto_sottolineato()) {
p.setUnderline();
}
if (intestazione) {
if (cfg.getCol_int_border().containsKey(num_row)) { // Se nella mappa è presente il numero di riga per settaggio bordo
for (List border : UtilConvertListMap.convertListBorder(cfg.getCol_int_border().get(num_row))) { // Ciclo all'interno di una lista alla ricerca del bordo
if (border.get(0).equals(String.valueOf(i - 1))) { // Se nella lista è presente un numero di colonna uguale a quello che sta disegnando il programma
switch (border.get(1).toString().trim()) { // setto i relativi bordi per singola cella
case "BUTTOM":
if (border.get(2).toString().trim().equals("false")) {
cell.setBorderBottom(Border.NO_BORDER);
}
case "TOP":
if (border.get(2).toString().trim().equals("false")) {
cell.setBorderTop(Border.NO_BORDER);
}
case "LEFT":
if (border.get(2).toString().trim().equals("false")) {
cell.setBorderLeft(Border.NO_BORDER);
}
case "RIGHT":
if (border.get(2).toString().trim().equals("false")) {
cell.setBorderRight(Border.NO_BORDER);
}
}
}
}
}
} else { // per le righe dati manipolo solo bordi colonne le righe saranno tutte uguali
if (cfg.getCol_dati_border((i - 1), "BUTTOM") == false) {
cell.setBorderBottom(Border.NO_BORDER);
}
if (cfg.getCol_dati_border((i - 1), "TOP") == false) {
cell.setBorderTop(Border.NO_BORDER);
}
if (cfg.getCol_dati_border((i - 1), "LEFT") == false) {
cell.setBorderLeft(Border.NO_BORDER);
}
if (cfg.getCol_dati_border((i - 1), "RIGHT") == false) {
cell.setBorderRight(Border.NO_BORDER);
}
}
if (intestazione) {
cell.setTextAlignment(TextAlignment.valueOf(cfg.getCol_int_allign(num_row, (i - 1)).toString()));
} else {
cell.setTextAlignment(cfg.getCol_dati_allign(i - 1, intestazione));
}
if (intestazione) {
p.setWidth((float) cfg.getCol_int_width(num_row, (i - 1)));
} else {
p.setWidth((float) cfg.getCol_dati_width(i - 1));
}
cell.setWidth(p.getWidth());
cell.add(p);
p.setProperty(Property.OVERFLOW_X, OverflowPropertyValue.valueOf("FIT"));
table.setHeight((float) cfg.getRig_heigth());
if (intestazione) {
table.addCell(cell);
} else {
table.addCell(cell);
}
}
MinMaxWidth minMaxWidth = ((TableRenderer) table.createRendererSubTree().setParent(canvas.getRenderer())).getMinMaxWidth();
float minWidth = minMaxWidth.getMinWidth();
float maxWidth = minMaxWidth.getMaxWidth();
rectangle.setWidth(maxWidth);
canvas.add(table);
table.flushContent();
This piece of code generates the canvas :
cfg = cfgn;
pages = pdfDocument.getPage(numero_pagina);
pdfCanvas = new PdfCanvas(pages);
rectangle = new Rectangle(cfg.getPos_x_tabella(), cfg.getPos_y_tabella(), 100, 200);
canvas = new Canvas(pdfCanvas, rectangle);
canvas.setBorder(Border.NO_BORDER);

The first idea would be to set the RotationAngle property of the table
table.setRotationAngle(-Math.PI/2);
canvas.add(table);
(RotateSomeContent test testForDropVidProperty)
Unfortunately this property currently (7.2.4-SNAPSHOT) seems not to be supported by the Table class.
What you can always do, though, is rotate the PdfCanvas on which the table is drawn, e.g. like this:
PdfCanvas pdfCanvas = new PdfCanvas(page);
// Rectangle for the table in upright page coordinates
Rectangle rectangle = new Rectangle(100, 100, 400, 700);
// show rectangle area
pdfCanvas.saveState();
pdfCanvas.setFillColor(new DeviceRgb(255, 255, 128));
pdfCanvas.rectangle(rectangle);
pdfCanvas.fill();
pdfCanvas.restoreState();
// apply a translation and a rotation so that the table will be rotated
// and the origin will be in the lower left corner of the rectangle
AffineTransform transform = AffineTransform.getTranslateInstance(rectangle.getLeft(), rectangle.getTop());
transform.rotate(-Math.PI/2);
pdfCanvas.concatMatrix(transform);
Rectangle rotatedRectangle = new Rectangle(0, 0, rectangle.getHeight(), rectangle.getWidth());
try ( Canvas canvas = new Canvas(pdfCanvas, rotatedRectangle) ) {
Table table = new Table(5);
table.addHeaderCell("DEBITO");
table.addHeaderCell("INTERESSI DI MORA");
table.addHeaderCell("ONERI DI RISCOSSIONE");
table.addHeaderCell("SPESE DI NOTIFICA\nE ACCESSORI");
table.addHeaderCell("SPESE ESECUTIVE");
table.addCell("3.304,24");
table.addCell("0,00");
table.addCell("183,55");
table.addCell("8,75");
table.addCell("0,00");
canvas.add(table);
}
(RotateSomeContent test testForDropVid)
The result looks like this:
(I marked the rectangle area in yellow for illustration only.)

Related

html/css-/js drag and drop for mouse and touch combine with rotate with every click?

I use a script for moving an object both with mouse and touch screen. This works.
I have another script to make the object rotate 90 degrees with each click, this also works.
It just doesn't work together.
When I merge them, the move still works, but with a click the object moves back to the starting position and only rotates there.
I've read that I should merge this but I don't know how. Below both scripts.
Thanks in advance for responses.
var container = document.querySelector("#container");
var activeItem = null;
var active = false;
container.addEventListener("touchstart", dragStart, false);
container.addEventListener("touchend", dragEnd, false);
container.addEventListener("touchmove", drag, false);
container.addEventListener("mousedown", dragStart, false);
container.addEventListener("mouseup", dragEnd, false);
container.addEventListener("mousemove", drag, false);
function dragStart(e) {
if (e.target !== e.currentTarget) {
active = true;
// this is the item we are interacting with
activeItem = e.target;
if (activeItem !== null) {
if (!activeItem.xOffset) {
activeItem.xOffset = 0;
}
if (!activeItem.yOffset) {
activeItem.yOffset = 0;
}
if (e.type === "touchstart") {
activeItem.initialX = e.touches[0].clientX - activeItem.xOffset;
activeItem.initialY = e.touches[0].clientY - activeItem.yOffset;
} else {
console.log("doing something!");
activeItem.initialX = e.clientX - activeItem.xOffset;
activeItem.initialY = e.clientY - activeItem.yOffset;
}
}
}
}
function dragEnd(e) {
if (activeItem !== null) {
activeItem.initialX = activeItem.currentX;
activeItem.initialY = activeItem.currentY;
}
active = false;
activeItem = null;
}
function drag(e) {
if (active) {
if (e.type === "touchmove") {
e.preventDefault();
activeItem.currentX = e.touches[0].clientX - activeItem.initialX;
activeItem.currentY = e.touches[0].clientY - activeItem.initialY;
} else {
activeItem.currentX = e.clientX - activeItem.initialX;
activeItem.currentY = e.clientY - activeItem.initialY;
}
activeItem.xOffset = activeItem.currentX;
activeItem.yOffset = activeItem.currentY;
setTranslate(activeItem.currentX, activeItem.currentY, activeItem);
}
}
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)";
}
// Make blocks rotate 90 deg on each click
var count=0;
function rot(e){
count++;
var deg=count*90;
e.style.transform = "rotate("+deg+"deg)";
}
So I put them in a file but then it only works as described above.

Error with a class for creating faces with three.js quaternion from two vectors

I'm creating a class to make the faces of an object, but I'm having problens with the quaternion. When I use the baseNormal vector as (0,0,0) the vertical faces disappears and i get this error.
THREE.DirectGeometry: Faceless geometries are not supported.
But, when i do baseNormal vector as (0,0,1) all the faces became horizontal and far way from the solide. Can some one help me?
"use strict";
// Classe Tface
// Representa visualmente o face
//three
import * as THREE from "three";
import { Object3D } from "three/src/core/Object3D.js";
import { Vector3 } from "three/src/math/Vector3";
import { Triangle } from "three/src/math/Triangle";
/**
* #param nome: string
* #param arrTponto = []
* #param arrFaces = []
*/
class Tface extends Object3D {
constructor(nome, arrTponto, arrFaces) {
//Parametros: string, Vector3(X,Y,Z)
super();
this.type = "Tface";
//name of face
this.nome = nome;
//receives Tpontos array
this.arrPt = arrTponto;
//receives array of points name that makes up the face
this.arrFaces = arrFaces;
}
/* recebe o array de pontos e o array com o nomes dos pontos de cada face, compara ambos
e separa os pontos de cada face determina a normal de cada face, cria a malha da mesma e adiciona na cena.
*/
//intersect array of point names with Tponto array (vector3)
criaFace() {
this.points = [];
this.arrFaces.forEach((elemento) => {
this.arrPt.forEach((e) => {
if (e.nome == elemento) {
this.pontV = new Vector3(e.X, e.Y, e.Z);
this.points.push(this.pontV);
}
});
});
//determines the normal of the faces
this.triangulo = new Triangle(
this.points[2],
this.points[1],
this.points[0]
);
this.normal = new Vector3();
this.triangulo.getNormal(this.normal);
this.baseNormal = new Vector3(0, 0, 1);
this.quaternion.setFromUnitVectors(this.normal, this.baseNormal);
this.tempPoints = [];
this.points.forEach((p) => {
this.tempPoints.push(p.clone().applyQuaternion(this.quaternion));
console.log(p, this.tempPoints);
});
//generates the face mesh
this.shape = new THREE.Shape(this.tempPoints);
this.shapeGeom = new THREE.ShapeGeometry(this.shape);
this.mesh = new THREE.Mesh(
this.shapeGeom,
new THREE.MeshBasicMaterial({
color: 0xfcee2b,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.6,
wireframe: false,
})
);
this.mesh.geometry.vertices = this.points;
this.name = String;
this.mesh.name;
//add face to the scene
this.add(this.mesh);
this.geom = new THREE.BufferGeometry().setFromPoints(this.points);
this.matLines = new THREE.LineBasicMaterial({ color: "red" });
this.lines = new THREE.LineLoop(this.geom, this.matLines);
this.lines.name = "line" + String(this.name);
this.add(this.lines);
//requestRenderIfNotRequested();
}
dispose() {
//libera a memória
this.children.forEach((element) => {
element.material.dispose();
element.geometry.dispose();
});
this.children = [];
}
update(nome, pt) {
//atualiza o ponto: nome e posição
this.nome = nome;
this.children.forEach((element) => {
element.position.copy(pt);
});
}
}
export { Tface };
Please notice that both parameters of Quaternion.setFromUnitVectors() are expected to have unit length. The vector (0,0,0) is not a unit vector since it has a length of 0. So you need to use a different input vector.

Intercction objects fabric.js

I have two elements(rectangle, text), and when I move rentangle, the text move with the rentangle. enter image description here
After moving the rectangle, I want to select the text but I can not select it by clicking on it. How do I get the text is above the rectangle?
This is my code:
canvas.on("object:moving", function () {
//Objeto que se mueve
var obj = this.relatedTarget;
var type = obj.type;
var id = obj.id;
var top, left;
switch (type)
{
case 'rectangleElemento':
var elementos = buscarporElementoID(mapa.mapaElementos, id);
if (elementos.idTextoElemento != null)
{
var textoElemento = canvas.item(elementos.idTextoElemento);
top = obj.top + 20;
left = obj.left + 20;
textoElemento.setTop(top);
textoElemento.setLeft(left);
textoElemento.bringToFront();
canvas.bringForward(textoElemento);
//canvas.br
// obj.bringToBack();
//Pendiente que se active el texto por encima
}
break;
case 'textoElemento':
var elementos = buscarporElementoIDTexto(mapa.mapaElementos, id);
var bounds = canvas.item(elementos.idRectanguloElemento);
if (bounds.id == elementos.idRectanguloElemento) {
top = bounds.get('left');
left = bounds.get('top');
obj.setCoords();
if (!obj.isContainedWithinObject(bounds)) {
obj.setTop(goodtop);
obj.setLeft(goodleft);
// canvas.renderAll();
} else {
goodtop = obj.top;
goodleft = obj.left;
}
}
break;
}
});
Have you grouped the two objects to act as one or are you manipulating them separately?
I have done an older live exmple that loads an Image(t-shirt) and a logo(i-text) on it and we manipulate the text.
I have added textbox where we can update the i-text realtime and buttons to move the text up/down/left/right ,into the image element.
But first of all i create and group the two objects like this:
fabric.Image.fromURL(myImg, function(myImg) {
var img1 = myImg.scale(scaleFactor).set({ left: 0, top: 0 });
var text = new fabric.Text('the_text_sample\nand more', {
fontFamily: 'Arial',
fontSize:20,
});
text.set("top",myImg.height*scaleFactor-myImg.height*scaleFactor+150);
text.set("left",myImg.width*scaleFactor/2-text.width/2);
var group = new fabric.Group([ img1,text ], { left: 10, top: 10 });
canvas.add(group);
console.log(canvas._objects[0]._objects[1].text);
});
Live example : https://jsfiddle.net/tornado1979/zrazuhcq/
I hope that helps, good lick.

JFreechart multiline Label on Symbolaxis or other way to align charts

i have a lot of charts, each in a different JInternalFrame:
But the horizontal axis should be aligned to the same point(maybe the red line). The problem is, that the space for the label is set automatically by jFreechart.
So i tried to find a solution for multiline ticklabels. I found this:
int optionsCount = state.getStatusOptions().toArray().length;
String[] grade = new String[optionsCount + 1];
grade[0] = "";
for (int x = 1; x < optionsCount + 1; x++) {
//grade[x] ="blaa"+x;//state.getStatusOptions().get(x - 1);
//grade[x]="1.line\n2.line\n3.line";
grade[x] = newLineString(state.getStatusOptions().get(x - 1), 5);
}
// grade[1]="1.line\n2.line";
SymbolAxis rangeAxis;
rangeAxis = new SymbolAxis("", grade){
#Override
protected Rectangle2D getLabelEnclosure(Graphics2D g2, RectangleEdge edge) {
Rectangle2D l = super.getLabelEnclosure(g2, edge);
l.setRect(l.getX(),l.getY(),l.getWidth()*0.5,l.getHeight());
return l;
}
#Override
protected AxisState drawTickMarksAndLabels(Graphics2D g2, double cursor, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge) {
AxisState state = new AxisState(cursor);
if (isAxisLineVisible()) {
drawAxisLine(g2, cursor, dataArea, edge);
}
double ol = getTickMarkOutsideLength();
double il = getTickMarkInsideLength();
List ticks = refreshTicks(g2, state, dataArea, edge);
state.setTicks(ticks);
g2.setFont(getTickLabelFont());
Iterator iterator = ticks.iterator();
// remember the max number of lines used in any label
int maxLinesUsed = 0;
while (iterator.hasNext()) {
ValueTick tick = (ValueTick) iterator.next();
if (isTickLabelsVisible()) {
g2.setPaint(getTickLabelPaint());
float[] anchorPoint = calculateAnchorPoint(tick, cursor, dataArea, edge);
g2.draw(plotArea);
g2.setPaint(Color.green);
g2.draw(dataArea);
g2.setPaint(getTickLabelPaint());
// split by "\n" and draw text in a new line for each result
String tickText = tick.getText();
int line = 1;
for (String tickTextLine : tickText.split("\n")) {
float x = anchorPoint[0];
// one row down...
float y = anchorPoint[1] + line * g2.getFont().getSize();
TextUtilities.drawRotatedString(tickTextLine, g2, x, y, tick.getTextAnchor(), tick.getAngle(), tick
.getRotationAnchor());
line++;
}
// if we used more lines than any time before remember it
if (line > maxLinesUsed) {
maxLinesUsed = line;
}
}
if (isTickMarksVisible() && tick.getTickType().equals(TickType.MAJOR)) {
float xx = (float) valueToJava2D(tick.getValue(), dataArea, edge);
Line2D mark = null;
g2.setStroke(getTickMarkStroke());
g2.setPaint(getTickMarkPaint());
if (edge == RectangleEdge.LEFT) {
mark = new Line2D.Double(cursor - ol, xx, cursor + il, xx);
} else if (edge == RectangleEdge.RIGHT) {
mark = new Line2D.Double(cursor + ol, xx, cursor - il, xx);
} else if (edge == RectangleEdge.TOP) {
mark = new Line2D.Double(xx, cursor - ol, xx, cursor + il);
} else if (edge == RectangleEdge.BOTTOM) {
mark = new Line2D.Double(xx, cursor + ol, xx, cursor - il);
}
g2.draw(mark);
}
}
// need to work out the space used by the tick labels...
// so we can update the cursor...
// patched using maxLinesUsed => we need more space because of multiple lines
double used = 0.0;
if (isTickLabelsVisible()) {
if (edge == RectangleEdge.LEFT) {
used += findMaximumTickLabelWidth(ticks, g2, plotArea, isVerticalTickLabels()) * maxLinesUsed;
state.cursorLeft(used);
} else if (edge == RectangleEdge.RIGHT) {
used = findMaximumTickLabelWidth(ticks, g2, plotArea, isVerticalTickLabels()) * maxLinesUsed;
state.cursorRight(used);
} else if (edge == RectangleEdge.TOP) {
used = findMaximumTickLabelHeight(ticks, g2, plotArea, isVerticalTickLabels()) * maxLinesUsed;
state.cursorUp(used);
} else if (edge == RectangleEdge.BOTTOM) {
used = findMaximumTickLabelHeight(ticks, g2, plotArea, isVerticalTickLabels()) * maxLinesUsed;
state.cursorDown(used);
}
}
return state;
}
};
As you can see in the picture above, the new line function works, but the spacing for the labels does not work. I tried to override the getLabelEnclosure method, but its given string is just "".
Does anyone know a solution for my problem. Either the multiline or an other way to align the charts?
thanks!
Override the reserveSpace function and in the place where the height is calculated modify it with the number of rows you need:
Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
if (RectangleEdge.isTopOrBottom(edge)) {
double labelHeight = labelEnclosure.getHeight();
space.add(labelHeight + **YOUR_NUMBER_OF_ROWS** * tickLabelHeight, edge);
} else if (RectangleEdge.isLeftOrRight(edge)) {
double labelWidth = labelEnclosure.getWidth();
space.add(labelWidth + tickLabelWidth, edge);
}
return space;

Slicing up a Rectangle

I need to get AS3 Rectangle objects from a function receiving other Rectangles as parameters. The result is very similar to the slice tool in Photoshop. It is quite hard to explain, so here is a picture:
(source: free.fr)
The blue squares are the rectangles that are given as parameters and the green ones are the result. Given Rectangles can overlap, as seen on picture 2 or be out of frame.
I don't look for a graphical realisation but for a way to get Rectangle objects as result.
Do you know any lib to do that?
Looked like a fun problem, so I gave it a crack. My idea was to just brute force it by:
Determine which points where the corners of the generated rectangles could be.
Remove all duplicates from this list of points.
Check all rectangles that could theoretically be drawn where the rect would have all 4 corners in the list of point.
Filter out all invalid rectangles (it intersects with one of our original rectangles etc.)
Reduce all valid rectangles to the minimum amount needed (if a valid rectangle contains another valid rectangle the "child" is removed.
It seems to work (although I haven't tested extensively).
Here's a demo. Sorry about the color palette. I was winging it.
Here's the source code (could probably be optimized quite a bit):
package
{
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.utils.getTimer;
public class Main extends Sprite {
private var m_colors : Array = [0xffaaaa, 0x77ff77, 0xaaaaff, 0xffff44, 0xff44ff, 0xaaffff, 0x444444, 0xffaa55, 0xaaff55, 0x55aaff, 0x55ffaa];
private var m_roomRect : Rectangle;
private var m_sourceRects : Vector.<Rectangle> = new Vector.<Rectangle>();
private var m_currentDragRect : Rectangle;
private var m_dragMousePoint : Point = new Point();
private var m_outputTextField : TextField;
public function Main() : void {
m_roomRect = new Rectangle(40, 40, 400, 400);
m_sourceRects.push(new Rectangle(60, 60, 60, 80));
m_sourceRects.push(new Rectangle(130, 220, 70, 80));
m_sourceRects.push(new Rectangle(160, 260, 100, 80));
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseEvent);
this.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseEvent);
var tf : TextField = new TextField();
tf.defaultTextFormat = new TextFormat("_sans", 12);
tf.text = "Click and drag blue rectangles to move them";
tf.autoSize = TextFieldAutoSize.LEFT;
tf.x = (m_roomRect.left + m_roomRect.right) / 2 - tf.width / 2;
tf.y = m_roomRect.top - tf.height;
this.stage.addChild(tf);
m_outputTextField = new TextField();
m_outputTextField.defaultTextFormat = tf.defaultTextFormat;
m_outputTextField.width = m_roomRect.width;
m_outputTextField.x = m_roomRect.x;
m_outputTextField.y = m_roomRect.bottom + 5;
this.stage.addChild(m_outputTextField);
redraw();
}
private function onMouseEvent(event : MouseEvent):void {
switch(event.type) {
case MouseEvent.MOUSE_DOWN:
checkMouseDownOnRect();
break;
case MouseEvent.MOUSE_MOVE:
checkMouseDrag();
break;
case MouseEvent.MOUSE_UP:
m_currentDragRect = null;
break;
}
}
private function checkMouseDownOnRect():void {
m_currentDragRect = null;
m_dragMousePoint = new Point(this.stage.mouseX, this.stage.mouseY);
for each(var sourceRect : Rectangle in m_sourceRects) {
if (sourceRect.containsPoint(m_dragMousePoint)) {
m_currentDragRect = sourceRect;
break;
}
}
}
private function checkMouseDrag():void {
if (m_currentDragRect != null) {
m_currentDragRect.x += this.stage.mouseX - m_dragMousePoint.x;
m_currentDragRect.y += this.stage.mouseY - m_dragMousePoint.y;
m_dragMousePoint.x = this.stage.mouseX;
m_dragMousePoint.y = this.stage.mouseY;
redraw();
}
}
private function redraw():void {
// calculate data
var time : int = getTimer();
var data : CalculationData = calculate();
var calcTime : int = getTimer() - time;
// draw room bounds
this.graphics.clear();
this.graphics.lineStyle(3, 0x0);
this.graphics.drawRect(m_roomRect.x, m_roomRect.y, m_roomRect.width, m_roomRect.height);
// draw generated rectangles
for (var i : int = 0; i < data.outputRects.length; i++) {
var color : int = m_colors[i % m_colors.length];
var rect : Rectangle = data.outputRects[i];
this.graphics.lineStyle(2, color, 0.5);
this.graphics.beginFill(color, 0.5);
this.graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
this.graphics.endFill();
}
// draw horisontal lines (a line that crosses each red point) for debug purposes
for each (var lineY : int in data.lines) {
this.graphics.lineStyle(1, 0, 0.2);
this.graphics.moveTo(m_roomRect.x, lineY);
this.graphics.lineTo(m_roomRect.x + m_roomRect.width, lineY);
this.graphics.endFill();
}
// the original rectangles
for each (var sourceRect : Rectangle in m_sourceRects) {
this.graphics.lineStyle(2, 0x0);
this.graphics.beginFill(0x0000aa, 0.5);
this.graphics.drawRect(sourceRect.x, sourceRect.y, sourceRect.width, sourceRect.height);
this.graphics.endFill();
}
// draw all points that was used to generate the output rectangles for debug purposes
for each (var p : Point in data.points) {
this.graphics.lineStyle(0, 0, 0);
this.graphics.beginFill(0xff0000, 1);
this.graphics.drawCircle(p.x, p.y, 3);
this.graphics.endFill();
}
m_outputTextField.text = "Rect count: " + data.outputRects.length + " (calculation time: " + calcTime + "ms)";
}
private function calculate(): CalculationData {
// list of y coords for horisontal lines,
// which are interesting when determining which rectangles to generate
var lines : Vector.<int> = new Vector.<int>();
// list of all points which are interesting
// when determining where the corners of the generated rect could be
var points : Vector.<Point> = new Vector.<Point>();
// add the 4 corners of the room to interesting points
points.push(new Point(m_roomRect.left, m_roomRect.top));
points.push(new Point(m_roomRect.right, m_roomRect.top));
points.push(new Point(m_roomRect.left, m_roomRect.bottom));
points.push(new Point(m_roomRect.right, m_roomRect.bottom));
for (var i:int = 0; i < m_sourceRects.length; i++) {
var sourceRect : Rectangle = m_sourceRects[i];
// source rect is completely outside of the room, we shoud ignore it
if (!m_roomRect.containsRect(sourceRect) && !m_roomRect.intersects(sourceRect)) {
continue;
}
// push the y coord of the rect's top edge to the list of lines if it's not already been added
if (lines.indexOf(sourceRect.y) == -1) {
lines.push(sourceRect.y);
}
// push the y coord of the rect's bottom edge to the list of lines if it's not already been added
if (lines.indexOf(sourceRect.bottom) == -1) {
lines.push(sourceRect.bottom);
}
// add the 4 corners of the source rect to the list of interesting points
addCornerPoints(points, sourceRect);
// find the intersections between source rectangles and add those points
for (var j:int = 0; j < m_sourceRects.length; j++) {
if (j != i) {
var intersect : Rectangle = m_sourceRects[i].intersection(m_sourceRects[j]);
if (intersect.width != 0 && intersect.height != 0) {
addCornerPoints(points, intersect);
}
}
}
}
for (i = 0; i < lines.length; i++) {
// add the points where the horisontal lines intersect with the room's left and right edges
points.push(new Point(m_roomRect.x, lines[i]));
points.push(new Point(m_roomRect.right, lines[i]));
var lineRect : Rectangle = new Rectangle(m_roomRect.x, m_roomRect.y,
m_roomRect.width, lines[i] - m_roomRect.y);
// add all points where the horisontal lines intersect with the source rectangles
for (a = 0; a < m_sourceRects.length;a++) {
intersect = m_sourceRects[a].intersection(lineRect);
if (intersect.width != 0 && intersect.height != 0) {
addCornerPoints(points, intersect);
}
}
}
// clamp all points that are outside of the room to the room edges
for (i = 0; i < points.length; i++) {
points[i].x = Math.min(Math.max(m_roomRect.left, points[i].x), m_roomRect.right);
points[i].y = Math.min(Math.max(m_roomRect.top, points[i].y), m_roomRect.bottom);
}
removeDuplicatePoints(points);
var outputRects : Vector.<Rectangle> = new Vector.<Rectangle>();
var pointsHash : Object = { };
for (a = 0; a < points.length; a++) {
pointsHash[points[a].x + "_" + points[a].y] = true;
}
for (var a:int = 0; a < points.length; a++) {
for (var b:int = 0; b < points.length; b++) {
if (b != a && points[b].x > points[a].x && points[b].y == points[a].y) {
for (var c:int = 0; c < points.length; c++) {
// generate a rectangle that has its four corners in our points of interest
if (c != b && c != a && points[c].y > points[b].y && points[c].x == points[b].x) {
var r : Rectangle = new Rectangle(points[a].x, points[a].y, points[b].x - points[a].x, points[c].y - points[b].y);
// make sure the rect has the bottom left corner in one of our points
if (pointsHash[r.left+"_"+r.bottom]) {
var containsOrIntersectsWithSource : Boolean = false;
for (i = 0; i < m_sourceRects.length;i++) {
if (r.containsRect(m_sourceRects[i]) || r.intersects(m_sourceRects[i])) {
containsOrIntersectsWithSource = true;
break;
}
}
// we don't add any rectangles that either intersects with a source rect
// or completely contains a source rect
if (!containsOrIntersectsWithSource) {
outputRects.push(r);
}
}
}
}
}
}
}
trace("outputRects before cleanup:", outputRects.length);
combineOutputRects(outputRects)
trace("outputRects after cleanup", outputRects.length);
var data : CalculationData = new CalculationData();
data.outputRects = outputRects;
data.lines = lines;
data.points = points;
return data;
}
private function addCornerPoints(points : Vector.<Point>, rect : Rectangle) : void {
points.push(new Point(rect.left, rect.top));
points.push(new Point(rect.right, rect.top));
points.push(new Point(rect.left, rect.bottom));
points.push(new Point(rect.right, rect.bottom));
}
// removes all rectangle that are already contained in another rectangle
private function combineOutputRects(outputRects : Vector.<Rectangle>):Boolean {
for (var a : int = 0; a < outputRects.length; a++) {
for (var b : int = 0; b < outputRects.length; b++) {
if (b != a) {
if (outputRects[a].containsRect(outputRects[b])) {
trace("\tremoved rect " + outputRects[b] + ", it was contained in " + outputRects[a]);
outputRects.splice(b, 1);
b--;
a = 0;
}
}
}
}
return false;
}
private function removeDuplicatePoints(points : Vector.<Point>) : void {
var usedPoints : Object = {};
for (var i : int = 0; i < points.length; i++) {
if (usedPoints[points[i].toString()]) {
points.splice(i, 1);
i--;
} else {
usedPoints[points[i].toString()] = true;
}
}
}
}
}
import flash.geom.Point;
import flash.geom.Rectangle;
class CalculationData {
public var outputRects : Vector.<Rectangle> = new Vector.<Rectangle>;
public var lines : Vector.<int> = new Vector.<int>;
public var points : Vector.<Point> = new Vector.<Point>;
}

Resources