I have an immutable array structure holding convex shapes as in the image above (they may vary in size and count, however they are always convex and never overlapping). What I want to do is connect the corners between them that can be connected without overlaping any edge, as in the image bellow where the blue lines represent the connections.
The data I have available are data structures holding the corner positions in the convex shapes, represented as a Vector structure similar to the following:
class Vector2
{
public:
float x, y;
}
The convex shape structure looks something like this:
class ConvexShape
{
public:
std::vector<Vector2> edges;
}
What I want to return from the function is an std::vector of a structure similar to the following:
class LinkedVector2 : public Vector2
{
public:
std::vector<LinkedVector2*> links;
}
So each linked vector is supposed to have a pointer to each other linked vector it is connected to.
The final function will thereby have this format:
std::vector<LinkedVector>* generateLinks(const std::vector<ConvexShape>& shapes)
{
std::vector<LinkedVector> links{ new std::vector<LinkedVector>{} };
// Create a linked vector for each shape's corner.
// Calculate links.
return links;
}
All of these links I then want to save for use in a later function which connects two points to the already linked shapes, along the lines of this:
The function should not alter the already existing connections and should look something like this:
// Argument 'links' will contain the previously generated links.
std::vector<LinkedVector>* connectPoints(const Vector2& a, const Vector2& b, const std::vector<LinkedVector>& links)
{
std::vector<LinkedVector>* connections{ new std::vector<LinkedVector>{} };
// Add old links to 'connections'.
// Connect the new links to the old.
// Add the new links to 'connections'.
return connections;
}
Could someone help me with how this could be done?
This is a description for an algorithm with example implementation to get you going.
Step 1
Preprocess every edge of the two shapes (s0 and s1) and extract the following information:
Distances from every edge in one shape to the vertices in the other
An ordered set of the vertices in one shape facing towards the other
Finding the distances is an exhaustive task (O(|V(s0)| * |V(s1)|)), it is also very cheap (line-point distance) and embarrassingly parallelisable. The facing vertices are found using the distances from above:
Start with the first vertex on the first shape, where the other shape is completely outside of its two adjacent edges (i.e. for any adjacent edge there exist outside values in its distances).
Since the facing set is a unique sequential set of vertices for convex polygons, continue adding vertices...
...until you reach a vertex where all vertices from the other shape lie inside of its adjacent edges
Doing this for both sides results in two sequences of facing vertices in each shape (the green dots per shape):
Step 2
To connect the two facing sets a scanline approach can be used:
In the ordered set of facing vertices the first vertex from one shape is always in line of sight of the last vertex from the other shape (first and last in case the shapes are oriented the same). From there we'll search sequentially, using the angle criteria from above for both the query from the first and the candidate vertex from the other shape, in the facing set to initialise our loop.
Looping sequentially over the facing vertices of the first shape, remove vertices that have broken line of sight (red line) and add vertices that came within line of sight (green line).
Step 3
Connecting the two outside points with the shapes is equivalent to finding the facing set of one shape in step 1 but instead of to another shape now there are only those individual outside points.
I've implemented step 1 and 2 in the following little browser demo as a prove of concept:
Click on the canvas and drag to move the camera
Click inside a shape and drag to move the shape
(function(canvas) {
function v2(x, y) { return { x: x, y: y }; }
function v2mul(lhs, rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; }
function v2subed(lhs, rhs) { return v2(lhs.x - rhs.x, lhs.y - rhs.y); }
function v2dot(lhs, rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; }
function v2normalized(v) { var len = Math.sqrt(v2dot(v, v)); if(len < 1e-7) len = 1; return v2(v.x / len, v.y / len); }
function v2perped(v) { return v2(-v.y, v.x); }
// Line from origin o : v2 and direction d : v2
function Line(o, d) {
this.o = o;
this.d = d;
}
// Signed distance to a point v : v2, in units of direction this.d
Line.prototype.distance = function(v) {
var o = v2subed(v, this.o);
var d = v2perped(this.d);
return v2dot(o, d);
};
// A polygon is made up of a sequence of points (arguments[i] : v2)
function Polygon() {
this.positions = [].slice.call(arguments);
}
// Transform polygon to new base [bx, by] and translation t
Polygon.prototype.transform = function(bx, by, t) {
this.positions.forEach(function(v) {
var x = bx.x * v.x + by.x * v.y + t.x;
var y = bx.y * v.x + by.y * v.y + t.y;
v.x = x;
v.y = y;
});
};
// Naive point inside polygon test for polygon picking
Polygon.prototype.isInside = function(v) {
if(this.positions.length < 3)
return false;
var o0 = this.positions[this.positions.length - 1];
for(var i = 0, imax = this.positions.length; i < imax; ++i) {
var o1 = this.positions[i];
var line = new Line(o0, v2normalized(v2subed(o1, o0)));
if(line.distance(v) <= 0)
return false;
o0 = o1;
}
return true;
};
// A camera positioned at eye : v2
function Camera(eye) {
this.eye = eye;
}
// Prepare temporaries for screen conversions
Camera.prototype.prepare = function(w, h) {
this.screen = {
off: v2(w / 2, h / 2),
};
};
Camera.prototype.toScreenX = function(x) { return x + this.screen.off.x - this.eye.x; }
Camera.prototype.toScreenY = function(y) { return this.screen.off.y - y + this.eye.y; }
Camera.prototype.fromScreenX = function(x) { return x - this.screen.off.x + this.eye.x; }
Camera.prototype.fromScreenY = function(y) { return this.screen.off.y - y + this.eye.y; }
Camera.prototype.toScreen = function(v) { return v2(this.toScreenX(v.x), this.toScreenY(v.y)); };
Camera.prototype.fromScreen = function(v) { return v2(this.fromScreenX(v.x), this.fromScreenY(v.y)); }
// Compute the distances of the line through e0 in p0 to each vertex in p1
// #post e0.distances.length === p1.positions.length
function computeEdge(e0, p0, p1) {
var line = new Line(p0.positions[e0.start], v2normalized(v2subed(p0.positions[e0.end], p0.positions[e0.start])));
var distances = [];
p1.positions.forEach(function(v) { distances.push(line.distance(v)); });
e0.line = line;
e0.distances = distances;
return e0;
}
// Find vertices in a convex polygon p0 that face p1
// #pre edges.length === p0.positions.length
function computeFacing(edges, p0, p1) {
var facing = [];
var count0 = p0.positions.length;
var count1 = p1.positions.length;
function isFacingVertex(i0) {
var e0 = edges[(i0 + count0 - 1) % count0];
var e1 = edges[i0];
for(var i1 = 0; i1 < count1; ++i1)
if(e0.distances[i1] < 0 || e1.distances[i1] < 0)
return true;
return false;
}
// Find the first vertex in the facing set of two non-intersecting, convex polygons
for(var i0 = 0; i0 < count0; ++i0) {
// For the first chance facing vertex
if(isFacingVertex(i0)) {
if(i0 === 0) {
// Search backwards here, s.t. we can complete the loop in one sitting
var iStart = count0;
for(; iStart > 1 && isFacingVertex(iStart - 1); --iStart);
while(iStart < count0)
facing.push(iStart++);
}
facing.push(i0++);
// In a convex polygon the (single) set of facing vertices is sequential
while(i0 < count0 && isFacingVertex(i0))
facing.push(i0++);
break;
}
}
return facing;
}
// Preprocesses the convex polygon p0 building the edges and facing lists
function preprocessPolygon(p0, p1) {
var result = {
edges: [],
facing: null,
};
for(var i = 0, imax = p0.positions.length; i < imax; ++i)
result.edges.push(computeEdge({ start: i, end: (i + 1) % imax }, p0, p1));
result.facing = computeFacing(result.edges, p0, p1);
return result;
}
// Scanline-approach to find all line of sight connections between the facing vertices of two preprocessed convex polygons p0 : Polygon and p1 : Polygon
// Output is prep.connections where prep.connections[i] : { v0, v1 } describes an unobstructed line of sight edge between vertex index v0 in p0 and v1 in p1
function computeConnections(prep, p0, p1) {
var connections = [];
var facing1count = prep.p1.facing.length;
// For oriented polygons the first facing vertex in p0 must surely face the last facing vertex in p1
var facing1begin = facing1count - 1, facing1end = facing1count;
prep.p0.facing.forEach(function(v0) {
function isConnectingVertex(v1) {
// Is v1 outside of adjacent edge-lines from v0?
var count0 = prep.p0.edges.length;
var ep = prep.p0.edges[(v0 + count0 - 1) % count0];
var en = prep.p0.edges[v0];
if(!(ep.distances[v1] < 0 || en.distances[v1] < 0)) return false;
// Is v0 outside of adjacent edge-lines from v1?
var count1 = prep.p1.edges.length;
ep = prep.p1.edges[(v1 + count1 - 1) % count1];
en = prep.p1.edges[v1];
return ep.distances[v0] < 0 || en.distances[v0] < 0;
}
// Throw away vertices that are no longer facing the current vertex
for(; facing1end > 0 && !isConnectingVertex(prep.p1.facing[facing1end - 1]); --facing1end);
// Add newly facing vertices
for(; facing1begin > 0 && isConnectingVertex(prep.p1.facing[facing1begin - 1]); --facing1begin);
// Generate the connections in facing range
for(var facing1 = facing1begin; facing1 < facing1end; ++facing1)
connections.push({ v0: v0, v1: prep.p1.facing[facing1] });
});
prep.connections = connections;
}
function process(prep, p0, p1) {
delete prep.p0;
delete prep.p1;
delete prep.connections;
prep.p0 = preprocessPolygon(p0, p1);
prep.p1 = preprocessPolygon(p1, p0);
computeConnections(prep, p0, p1);
}
var polygons = null;
var prep = null;
var camera = null;
var ui = null;
function reset() {
polygons = [
new Polygon(v2(25, -75), v2(50, -175), v2(140, -225), v2(255, -200), v2(195, -65), v2(140, -40)),
new Polygon(v2(400, -100), v2(295, -70), v2(260, -80), v2(310, -220), v2(425, -230)),
];
// Scale to a fitting size and move to center
var bx = v2(0.5, 0), by = v2(0, 0.5), off = v2(-120, 70);
polygons[0].transform(bx, by, off);
polygons[1].transform(bx, by, off);
prep = {};
camera = new Camera(v2(0, 0));
ui = { pickedPolygon: -1 };
update();
draw();
}
function update() {
// Reprocess polygons
process(prep, polygons[0], polygons[1]);
}
function draw() {
var g = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
camera.prepare(w, h);
g.fillStyle = "linen";
g.fillRect(0, 0, w, h);
var iPick = 0;
polygons.forEach(function(polygon) {
var highlight = iPick++ === ui.pickedPolygon;
var positions = polygon.positions;
if(positions.length > 2) {
g.beginPath();
g.lineWidth = highlight ? 2 : 1;
g.strokeStyle = "black";
var pLast = camera.toScreen(positions[positions.length - 1]);
g.moveTo(pLast.x, pLast.y);
positions.forEach(function(pos) {
var pScreen = camera.toScreen(pos);
g.lineTo(pScreen.x, pScreen.y);
});
g.stroke();
}
});
prep.connections.forEach(function(connection) {
var v0 = camera.toScreen(polygons[0].positions[connection.v0]);
var v1 = camera.toScreen(polygons[1].positions[connection.v1]);
g.beginPath();
g.lineWidth = 2;
g.strokeStyle = "cyan";
g.moveTo(v0.x, v0.y);
g.lineTo(v1.x, v1.y);
g.stroke();
});
}
(function(c) {
reset();
var dragStartPos = null, dragLastPos = null;
var pickedPolygon = null;
var cameraStartPos = v2(0, 0);
function toScreen(client) {
var rect = c.getBoundingClientRect();
return v2(client.x - rect.left, client.y - rect.top);
}
function startDragging(x, y) {
dragStartPos = v2(x, y);
dragLastPos = v2(x, y);
if(pickedPolygon !== null) {
// Nothing to prepare
} else {
cameraStartPos.x = camera.eye.x;
cameraStartPos.y = camera.eye.y;
}
}
function continueDragging(x, y) {
if(pickedPolygon !== null) {
var dx = x - dragLastPos.x, dy = -(y - dragLastPos.y);
pickedPolygon.transform(v2(1, 0), v2(0, 1), v2(dx, dy));
update();
} else {
var dx = -(x - dragStartPos.x), dy = y - dragStartPos.y;
camera.eye.x = cameraStartPos.x + dx;
camera.eye.y = cameraStartPos.y + dy;
}
dragLastPos.x = x;
dragLastPos.y = y;
}
function stopDragging() {
dragStartPos = null;
dragLastPos = null;
if(pickedPolygon !== null) {
// Nothing to do here...
} else {
cameraStartPos.x = 0;
cameraStartPos.y = 0;
}
}
c.onmousemove = function(e) {
if(dragStartPos !== null)
continueDragging(e.clientX, e.clientY);
else {
pickedPolygon = null;
var iPick = 0;
var cursorPos = camera.fromScreen(toScreen(v2(e.clientX, e.clientY)));
for(var imax = polygons.length; iPick < imax; ++iPick) {
if(polygons[iPick].isInside(cursorPos)) {
pickedPolygon = polygons[iPick];
break;
}
}
ui.pickedPolygon = pickedPolygon !== null ? iPick : -1;
}
draw();
};
c.onmouseleave = function(e) {
if(dragStartPos !== null)
stopDragging();
pickedPolygon = null;
ui.pickedPolygon = -1;
draw();
};
c.onmousedown = function(e) {
if(e.button === 0)
startDragging(e.clientX, e.clientY);
draw();
};
c.onmouseup = function(e) {
if(e.button === 0 && dragStartPos !== null)
stopDragging();
draw();
};
})(canvas);
})(document.getElementById("screen"));
<canvas id="screen" width="300" height="300"></canvas>
I am trying to render random meshes to an MTKView as fast as the device will allow.. Pretty much all the metal examples I have found show how to draw a piece of geometry for which the buffer size is defined only once (i.e. fixed):
let dataSize = vertexCount * MemoryLayout<VertexWithColor>.size // size of the vertex data in bytes
let vertexBuffer: MTLBuffer = device!.makeBuffer(bytes: verticesWithColorArray, length: dataSize, options: []) // create a new buffer on the GPU
The goal is to eventually generate meshes on the fly given some point cloud input. I've set up drawing to be triggered with a tap as follows:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchPoint = touch.location(in: view)
print ("...touch \(touchPoint)")
autoreleasepool {
delaunayView.setupTriangles()
delaunayView.renderTriangles()
}
}
}
I can get the screen to refresh with new triangles, as long as I don't tap too frequently. However, if I tap too quickly (like say a double tap), the app crashes with the following error:
[CAMetalLayerDrawable texture] should not be called after presenting the drawable.
Performance will obviously be linked to the number of triangles drawn. Besides getting the app to function stably, just as important is the question, how can I best take advantage of the GPU to push as many triangles as possible? (In its current state, the app draws about 30,000 triangles at 3 fps on an iPad Air 2).
Any pointers/gotchas for speed and frame rate would be most welcome
The whole project can be found here:
Also, below is the pertinent updated metal class
import Metal
import MetalKit
import GameplayKit
protocol MTKViewDelaunayTriangulationDelegate: NSObjectProtocol{
func fpsUpdate (fps: Int)
}
class MTKViewDelaunayTriangulation: MTKView {
//var kernelFunction: MTLFunction!
var pipelineState: MTLComputePipelineState!
var defaultLibrary: MTLLibrary! = nil
var commandQueue: MTLCommandQueue! = nil
var renderPipeline: MTLRenderPipelineState!
var errorFlag:Bool = false
var verticesWithColorArray : [VertexWithColor]!
var vertexCount: Int
var verticesMemoryByteSize:Int
let fpsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 20))
var frameCounter: Int = 0
var frameStartTime = CFAbsoluteTimeGetCurrent()
weak var MTKViewDelaunayTriangulationDelegate: MTKViewDelaunayTriangulationDelegate?
////////////////////
init(frame: CGRect) {
vertexCount = 100000
//verticesMemoryByteSize = vertexCount * MemoryLayout<VertexWithColor>.size
verticesMemoryByteSize = vertexCount * MemoryLayout<VertexWithColor>.stride // apple recommendation
super.init(frame: frame, device: MTLCreateSystemDefaultDevice())
setupMetal()
//setupTriangles()
//renderTriangles()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/*
override func draw(_ rect: CGRect) {
step() // needed to update frame counter
autoreleasepool {
setupTriangles()
renderTriangles()
}
} */
func step() {
frameCounter += 1
if frameCounter == 100
{
let frametime = (CFAbsoluteTimeGetCurrent() - frameStartTime) / 100
MTKViewDelaunayTriangulationDelegate?.fpsUpdate(fps: Int(1 / frametime)) // let the delegate know of the frame update
print ("...frametime: \((Int(1/frametime)))")
frameStartTime = CFAbsoluteTimeGetCurrent() // reset start time
frameCounter = 0 // reset counter
}
}
func setupMetal(){
// Steps required to set up metal for rendering:
// 1. Create a MTLDevice
// 2. Create a Command Queue
// 3. Access the custom shader library
// 4. Compile shaders from library
// 5. Create a render pipeline
// 6. Set buffer size of objects to be drawn
// 7. Draw to pipeline through a renderCommandEncoder
// 1. Create a MTLDevice
guard let device = MTLCreateSystemDefaultDevice() else {
errorFlag = true
//particleLabDelegate?.particleLabMetalUnavailable()
return
}
// 2. Create a Command Queue
commandQueue = device.makeCommandQueue()
// 3. Access the custom shader library
defaultLibrary = device.newDefaultLibrary()
// 4. Compile shaders from library
let fragmentProgram = defaultLibrary.makeFunction(name: "basic_fragment")
let vertexProgram = defaultLibrary.makeFunction(name: "basic_vertex")
// 5a. Define render pipeline settings
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexFunction = vertexProgram
renderPipelineDescriptor.sampleCount = self.sampleCount
renderPipelineDescriptor.colorAttachments[0].pixelFormat = self.colorPixelFormat
renderPipelineDescriptor.fragmentFunction = fragmentProgram
// 5b. Compile renderPipeline with above renderPipelineDescriptor
do {
renderPipeline = try device.makeRenderPipelineState(descriptor: renderPipelineDescriptor)
} catch let error as NSError {
print("render pipeline error: " + error.description)
}
// initialize counter variables
frameStartTime = CFAbsoluteTimeGetCurrent()
frameCounter = 0
} // end of setupMetal
/// Generate set of vertices for our triangulation to use
func generateVertices(_ size: CGSize, cellSize: CGFloat, variance: CGFloat = 0.75, seed: UInt64 = numericCast(arc4random())) -> [Vertex] {
// How many cells we're going to have on each axis (pad by 2 cells on each edge)
let cellsX = (size.width + 4 * cellSize) / cellSize
let cellsY = (size.height + 4 * cellSize) / cellSize
// figure out the bleed widths to center the grid
let bleedX = ((cellsX * cellSize) - size.width)/2
let bleedY = ((cellsY * cellSize) - size.height)/2
let _variance = cellSize * variance / 4
var points = [Vertex]()
let minX = -bleedX
let maxX = size.width + bleedX
let minY = -bleedY
let maxY = size.height + bleedY
let generator = GKLinearCongruentialRandomSource(seed: seed)
for i in stride(from: minX, to: maxX, by: cellSize) {
for j in stride(from: minY, to: maxY, by: cellSize) {
let x = i + cellSize/2 + CGFloat(generator.nextUniform()) + CGFloat.random(-_variance, _variance)
let y = j + cellSize/2 + CGFloat(generator.nextUniform()) + CGFloat.random(-_variance, _variance)
points.append(Vertex(x: Double(x), y: Double(y)))
}
}
return points
} // end of generateVertices
func setupTriangles(){
// generate n random triangles
///////////////////
verticesWithColorArray = [] // empty out vertex array
for _ in 0 ... vertexCount {
//for vertex in vertices {
let x = Float(Double.random(-1.0, 1.0))
let y = Float(Double.random(-1.0, 1.0))
let v = VertexWithColor(x: x, y: y, z: 0.0, r: Float(Double.random()), g: Float(Double.random()), b: Float(Double.random()), a: 0.0)
verticesWithColorArray.append(v)
} // end of for _ in
} // end of setupTriangles
func renderTriangles(){
// 6. Set buffer size of objects to be drawn
//let dataSize = vertexCount * MemoryLayout<VertexWithColor>.size // size of the vertex data in bytes
let dataSize = vertexCount * MemoryLayout<VertexWithColor>.stride // apple recommendation
let vertexBuffer: MTLBuffer = device!.makeBuffer(bytes: verticesWithColorArray, length: dataSize, options: []) // create a new buffer on the GPU
let renderPassDescriptor: MTLRenderPassDescriptor? = self.currentRenderPassDescriptor
// If the renderPassDescriptor is valid, begin the commands to render into its drawable
if renderPassDescriptor != nil {
// Create a new command buffer for each tessellation pass
let commandBuffer: MTLCommandBuffer? = commandQueue.makeCommandBuffer()
// Create a render command encoder
// 7a. Create a renderCommandEncoder four our renderPipeline
let renderCommandEncoder: MTLRenderCommandEncoder? = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
renderCommandEncoder?.label = "Render Command Encoder"
//////////renderCommandEncoder?.pushDebugGroup("Tessellate and Render")
renderCommandEncoder?.setRenderPipelineState(renderPipeline!)
renderCommandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, at: 0)
// most important below: we tell the GPU to draw a set of triangles, based on the vertex buffer. Each triangle consists of three vertices, starting at index 0 inside the vertex buffer, and there are vertexCount/3 triangles total
//renderCommandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexCount, instanceCount: vertexCount/3)
renderCommandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexCount)
///////////renderCommandEncoder?.popDebugGroup()
renderCommandEncoder?.endEncoding() // finalize renderEncoder set up
commandBuffer?.present(self.currentDrawable!) // needed to make sure the new texture is presented as soon as the drawing completes
// 7b. Render to pipeline
commandBuffer?.commit() // commit and send task to gpu
} // end of if renderPassDescriptor
}// end of func renderTriangles()
} // end of class MTKViewDelaunayTriangulation
You shouldn't be calling setupTriangles() or, especially, renderTriangles() from init(). Nor, as per your comment, from touchesBegan(). In general, you should only attempt to draw when the framework calls your override of draw(_:).
How you update for user events depends on the drawing mode of the MTKView, as explained in the class overview. By default, your draw(_:) method is called periodically. In this mode, you shouldn't have to do anything about drawing in touchesBegan(). Just update your class's internal state about what it should draw. The actual drawing will happen automatically a short time later.
If you've configured the view to redraw after setNeedsDisplay(), then touchesBegan() should update internal state and then call setNeedsDisplay(). It shouldn't attempt to draw immediately. A short time after you return control back to the framework (i.e. return from touchesBegan()), it will call draw(_:) for you.
If you've configured the view to only draw when you explicitly call draw(), then you would do that after updating internal state.
I have a game that involves lots of collisions and explosions.
Basically, enemy-projectiles are launched from the bottom-right corner of the screen towards the left side. The player can intercept them using his weapon.
I want whenever a "bullet" and a projectile collide, the code will draw pre-made particles at the impact position.
My problem is that in real-time, in collision, the particles are drawn nicely but they never stop. The particles continue to be drawn even after the collision.
Now, I use callback mechanism to inform the renderer a collision took place somewhere, and I pass him the coordinates of the collision(x,y)
Here is my renderer code. I cut some parts from the code, leaving only the relevant ones:
package com.david.gameworld;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.utils.Array;
import com.david.helpers.AssetsLoader;
import com.david.helpers.CollisionAndTouchCallbacks;
public class GameRenderer implements CollisionAndTouchCallbacks.DrawThings{
/* The GameRenderer class. Responsible for all the rendering work and graphics */
public static final float FLOOR_HEIGHT = 100.0f; // Public variable - The floor height
private static final float Pixels_To_Meters = 32.0f; // PTM Ration
public static float screenWidth; // Screen width
public static float screenHeight; // Screen height
public OrthographicCamera camera; // The orthographic camera
private GameWorld world; // The game world object, for rendering
private boolean drawTrajectory;
private boolean drawParticle;
private boolean isDrawnParticle;
private Box2DDebugRenderer debugRenderer; // Debug renderer, to see the positions and sizes of the bodies/fixtures
private SpriteBatch batch; // A sprite batch
private Array<Body> tempBodies; // An array that contains all the bodies of Box2D
private Array<Vector2> collisionPositions;
private float delta;
public GameRenderer(float screenWidth, float screenHeight, GameWorld world) {
this.world = world;
GameRenderer.screenHeight = screenHeight;
GameRenderer.screenWidth = screenWidth;
debugRenderer = new Box2DDebugRenderer();
batch = new SpriteBatch();
camera = new OrthographicCamera();
tempBodies = new Array<Body>();
collisionPositions = new Array<Vector2>();
drawTrajectory = false;
drawParticle = false;
isDrawnParticle = false;
}
public void render(float delta) {
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
this.delta = delta;
// Update the world
world.updateWorld();
batch.setProjectionMatrix(camera.combined);
// Get box2d World bodies
world.getBox2DWorld().getBodies(tempBodies);
// Draw the entire scene
batch.begin();
drawBodiesBox2D();
if(drawTrajectory)
drawTrajectory();
if(drawParticle) {
if(!isDrawnParticle) {
drawParticle();
isDrawnParticle = true;
} else {
if(AssetsLoader.explosion.isComplete()) {
drawParticle = false;
isDrawnParticle = false;
}
}
}
batch.end();
// Debug Renderer for box2d world
}
// If user is aiming, draw the trajectory of the acorn
public void drawTrajectory() {
Gdx.app.log("System Out Println!!!!!!!!!", "Draw projectile");
float t = 0.1f, x = world.getArrayAcorns().peek().getX(), y = world
.getArrayAcorns().peek().getY();
float width = AssetsLoader.orbit.getWidth() / 8 / Pixels_To_Meters;
float height = AssetsLoader.orbit.getHeight() / 8 / Pixels_To_Meters;
float timeSeparation = 0.08f;
for (int i = 0; i < 40; i++) {
x = world.getAcornEquation().getX(t);
y = world.getAcornEquation().getY(t);
batch.draw(AssetsLoader.orbit, x, y, width, height);
t += timeSeparation;
}
}
private void drawBodiesBox2D() {
for (Body body : tempBodies) {
if (body.getUserData() != null
&& body.getUserData() instanceof Sprite) {
Sprite sprite = (Sprite) body.getUserData();
if (!sprite.getTexture().equals(AssetsLoader.ground)) {
sprite.setPosition(body.getPosition().x - sprite.getWidth()
/ 2, body.getPosition().y - sprite.getHeight() / 2);
} else {
sprite.setSize(screenWidth, FLOOR_HEIGHT / Pixels_To_Meters);
}
sprite.setRotation((float) Math.toDegrees(body.getAngle()));
batch.draw(AssetsLoader.catapult_base1, 450 / Pixels_To_Meters,
(FLOOR_HEIGHT - 10) / Pixels_To_Meters,
AssetsLoader.catapult_base1.getWidth()
/ Pixels_To_Meters,
AssetsLoader.catapult_base1.getHeight()
/ Pixels_To_Meters);
sprite.draw(batch);
}
}
}
public void dispose() {
AssetsLoader.dispose();
batch.dispose();
world.dispose();
debugRenderer.dispose();
}
// Update method for the orthographic camera
public void updateCamera(int width, int height) {
camera.setToOrtho(false, width, height);
screenWidth = (float) width;
screenHeight = (float) height;
camera.update();
// Already Meters
}
public OrthographicCamera getCamera() {
return this.camera;
}
private void drawParticle() {
// TODO Auto-generated method stub
AssetsLoader.explosion.setPosition(collisionPositions.first().x,collisionPositions.first().y);
AssetsLoader.explosion.start();
AssetsLoader.explosion.draw(batch,delta);
AssetsLoader.explosion.allowCompletion();
}
#Override
public void signalToTrajectory(boolean flag) {
// TODO Auto-generated method stub
drawTrajectory = flag;
}
#Override
public void signalToParticle(boolean flag, Array<Vector2> positions) {
// TODO Auto-generated method stub
if(flag)
collisionPositions = positions;
drawParticle = flag;
}
}
How can I draw the particle effect once and that's it?
You can use/optimise my particle class, look my post here
To draw the particle effect once and that's it : ( it's in myparticle free method ) :
ParticleEffect effect = new ParticleEffect();
Emitter emitter = effect.getControllers().first().emitter;
if (emitter instanceof RegularEmitter){
RegularEmitter reg = (RegularEmitter) emitter;
reg.setEmissionMode(RegularEmitter.EmissionMode.EnabledUntilCycleEnd);
//reg.durationValue.setLow(10f);
reg.dispose();
}
emitter.dispose();
I've read a bunch of tutorials involving XNA (and it's various versions) and I still am a little confused on drawing primitives. Everything seems to be really convoluted.
Can someone show me, using code, the simplest XNA implementation of drawing one or two lines on to the screen? Perhaps with a brief explanation (including the boilerplate)?
I'm not a games programmer and I have little XNA experience. My ultimate goal is to draw some lines onto the screen which I will eventually transform with rotations, etc (by hand). However, for this first step.. I need to simply draw the lines! I remember back in my ancient OpenGL days it was fairly straightforward when drawing a line with a few method calls. Should I simply revert to using unmanaged directx calls?
When working with XNA, everything (even 2d primitives) have to be expressed in a way that a 3d card can understand, which means that a line is just a set of vertices.
MSDN has a pretty good walkthrough here:
http://msdn.microsoft.com/en-us/library/bb196414.aspx#ID2EEF
You'll find that it takes more code to render a primitive line than it would take to just setup a textured quad and rotate that, since in essence, your doing the same thing when rendering a line.
Following NoHayProblema's answer (I cannot comment yet).
That answer, although the correct one for this old question, is incomplete. Texture2D constructor returns an uninitialized texture, which is never painted on screen.
In order to use that approach, you need to set the texture's data like this:
Texture2D SimpleTexture = new Texture2D(GraphicsDevice, 1, 1, false,
SurfaceFormat.Color);
Int32[] pixel = {0xFFFFFF}; // White. 0xFF is Red, 0xFF0000 is Blue
SimpleTexture.SetData<Int32> (pixel, 0, SimpleTexture.Width * SimpleTexture.Height);
// Paint a 100x1 line starting at 20, 50
this.spriteBatch.Draw(SimpleTexture, new Rectangle(20, 50, 100, 1), Color.Blue);
Take into account that the way you write the data into pixel must be consistent with the texture's SurfaceFormat. The example works because the texture is being formatted as RGB.
Rotations can be applied in spriteBatch.Draw like this:
this.spriteBatch.Draw (SimpleTexture, new Rectangle(0, 0, 100, 1), null,
Color.Blue, -(float)Math.PI/4, new Vector2 (0f, 0f), SpriteEffects.None, 1f);
found a tutorial for that
http://www.bit-101.com/blog/?p=2832
its using a BasicEffect (shader)
and the built in draw user primitive in XNA 4.0
some code samples i find helpful:
load content method
basicEffect = new BasicEffect(GraphicsDevice);
basicEffect.VertexColorEnabled = true;
basicEffect.Projection = Matrix.CreateOrthographicOffCenter
(0, GraphicsDevice.Viewport.Width, // left, right
GraphicsDevice.Viewport.Height, 0, // bottom, top
0, 1);
draw method
basicEffect.CurrentTechnique.Passes[0].Apply();
var vertices = new VertexPositionColor[4];
vertices[0].Position = new Vector3(100, 100, 0);
vertices[0].Color = Color.Black;
vertices[1].Position = new Vector3(200, 100, 0);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(200, 200, 0);
vertices[2].Color = Color.Black;
vertices[3].Position = new Vector3(100, 200, 0);
vertices[3].Color = Color.Red;
GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, vertices, 0, 2);
have fun and vote up if this helped you. also pay a visit to the tutorial i got this from.
Well, you can do it in a very simple way without getting into the 3D horrible vector stuff.
Just create a quick texture, for example:
Texture2D SimpleTexture = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
And then just draw a line using that texture:
this.spriteBatch.Draw(SimpleTexture, new Rectangle(100, 100, 100, 1), Color.Blue);
I hope this helps
The simplest best way, I think, is to get the image of just a white pixel then stretch that pixel in a rectangle to look like a line
I made a Line class,
class Line
{
Texture pixel = ((set this to a texture of a white pixel with no border));
Vector2 p1, p2; //this will be the position in the center of the line
int length, thickness; //length and thickness of the line, or width and height of rectangle
Rectangle rect; //where the line will be drawn
float rotation; // rotation of the line, with axis at the center of the line
Color color;
//p1 and p2 are the two end points of the line
public Line(Vector2 p1, Vector2 p2, int thickness, Color color)
{
this.p1 = p1;
this.p2 = p2;
this.thickness = thickness;
this.color = color;
}
public void Update(GameTime gameTime)
{
length = (int)Vector2.Distance(p1, p2); //gets distance between the points
rotation = getRotation(p1.X, p1.Y, p2.X, p2.Y); //gets angle between points(method on bottom)
rect = new Rectangle((int)p1.X, (int)p1.Y, length, thickness)
//To change the line just change the positions of p1 and p2
}
public void Draw(SpriteBatch spriteBatch, GameTime gameTime)
{
spriteBatch.Draw(pixel, rect, null, color, rotation, new Vector2.Zero, SpriteEffects.None, 0.0f);
}
//this returns the angle between two points in radians
private float getRotation(float x, float y, float x2, float y2)
{
float adj = x - x2;
float opp = y - y2;
float tan = opp / adj;
float res = MathHelper.ToDegrees((float)Math.Atan2(opp, adj));
res = (res - 180) % 360;
if (res < 0) { res += 360; }
res = MathHelper.ToRadians(res);
return res;
}
Hope this helps
There is also the "round line" code that "manders" has released on CodePlex:
http://roundline.codeplex.com/
Here is the blog post about it:
XNA RoundLine Code Released on CodePlex
Just stretch a white pixel.
point = game.Content.Load<Texture2D>("ui/point");
public void DrawLine(Vector2 start, Vector2 end, Color color)
{
Vector2 edge = end - start;
float angle = (float)Math.Atan2(edge.Y, edge.X);
spriteBatch.Begin();
spriteBatch.Draw(point,
new Rectangle((int)start.X, (int)start.Y, (int)edge.Length(), 1),
null,
color,
angle,
new Vector2(0, 0),
SpriteEffects.None,
0);
spriteBatch.End();
}
I wanted to draw rays so that I could debug rays created by explosions and where they intersect objects. This will draw a single pixel thin line between two points. This is what I did:
Class to store some simple ray data. The XNA default ray class could work, but it doesn't store the length of the ray to intersection.
public class myRay
{
public Vector3 position, direction;
public float length;
}
A list to store the rays that are to be drawn:
List<myRay> DebugRays= new List<myRay>();
Create a BasicEffect and pass it a "Matrix.CreateOrthographicOffCenter" projection with your desired resolution in the LoadContent method.
Then run this in the draw method:
private void DrawRays()
{
spriteBatch.Begin();
foreach (myRay ray in DebugRays)
{
//An array of 2 vertices - a start and end position
VertexPositionColor[] Vertices = new VertexPositionColor[2];
int[] Indices = new int[2];
//Starting position of the ray
Vertices[0] = new VertexPositionColor()
{
Color = Color.Orange,
Position = ray.position
};
//End point of the ray
Vertices[1] = new VertexPositionColor()
{
Color = Color.Orange,
Position = ray.position + (ray.direction * ray.length)
};
Indices[0] = 0;
Indices[1] = 1;
foreach (EffectPass pass in BasicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.LineStrip, Vertices, 0, 2, Indices, 0, 1, VertexPositionColorTexture.VertexDeclaration);
}
}
spriteBatch.End();
}
So when an explosion happens in my game it does this (Psuedocode):
OnExplosionHappened()
{
DebugRays.Clear()
myRay ray = new myRay()
{
position = explosion.Position,
direction = GetDirection(explosion, solid),
//Used GetValueOrDefault here to prevent null value errors
length = explosionRay.Intersects(solid.BoundingBox).GetValueOrDefault()
};
DebugRays.Add(ray);
}
It's pretty simple (It possibly looks way more complicated than it is) and it'd be easy to put it into a separate class that you never have to think about again. It also lets you draw a whole lot of lines at once.
I encountered this problem my self and decided to make a class called LineBatch.
LineBatch will draw lines without needing a spriteBatch or dots.
The class is below.
public class LineBatch
{
bool cares_about_begin_without_end;
bool began;
GraphicsDevice GraphicsDevice;
List<VertexPositionColor> verticies = new List<VertexPositionColor>();
BasicEffect effect;
public LineBatch(GraphicsDevice graphics)
{
GraphicsDevice = graphics;
effect = new BasicEffect(GraphicsDevice);
Matrix world = Matrix.Identity;
Matrix view = Matrix.CreateTranslation(-GraphicsDevice.Viewport.Width / 2, -GraphicsDevice.Viewport.Height / 2, 0);
Matrix projection = Matrix.CreateOrthographic(GraphicsDevice.Viewport.Width, -GraphicsDevice.Viewport.Height, -10, 10);
effect.World = world;
effect.View = view;
effect.VertexColorEnabled = true;
effect.Projection = projection;
effect.DiffuseColor = Color.White.ToVector3();
cares_about_begin_without_end = true;
}
public LineBatch(GraphicsDevice graphics, bool cares_about_begin_without_end)
{
this.cares_about_begin_without_end = cares_about_begin_without_end;
GraphicsDevice = graphics;
effect = new BasicEffect(GraphicsDevice);
Matrix world = Matrix.Identity;
Matrix view = Matrix.CreateTranslation(-GraphicsDevice.Viewport.Width / 2, -GraphicsDevice.Viewport.Height / 2, 0);
Matrix projection = Matrix.CreateOrthographic(GraphicsDevice.Viewport.Width, -GraphicsDevice.Viewport.Height, -10, 10);
effect.World = world;
effect.View = view;
effect.VertexColorEnabled = true;
effect.Projection = projection;
effect.DiffuseColor = Color.White.ToVector3();
}
public void DrawAngledLineWithRadians(Vector2 start, float length, float radians, Color color)
{
Vector2 offset = new Vector2(
(float)Math.Sin(radians) * length, //x
-(float)Math.Cos(radians) * length //y
);
Draw(start, start + offset, color);
}
public void DrawOutLineOfRectangle(Rectangle rectangle, Color color)
{
Draw(new Vector2(rectangle.X, rectangle.Y), new Vector2(rectangle.X + rectangle.Width, rectangle.Y), color);
Draw(new Vector2(rectangle.X, rectangle.Y), new Vector2(rectangle.X, rectangle.Y + rectangle.Height), color);
Draw(new Vector2(rectangle.X + rectangle.Width, rectangle.Y), new Vector2(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height), color);
Draw(new Vector2(rectangle.X, rectangle.Y + rectangle.Height), new Vector2(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height), color);
}
public void DrawOutLineOfTriangle(Vector2 point_1, Vector2 point_2, Vector2 point_3, Color color)
{
Draw(point_1, point_2, color);
Draw(point_1, point_3, color);
Draw(point_2, point_3, color);
}
float GetRadians(float angleDegrees)
{
return angleDegrees * ((float)Math.PI) / 180.0f;
}
public void DrawAngledLine(Vector2 start, float length, float angleDegrees, Color color)
{
DrawAngledLineWithRadians(start, length, GetRadians(angleDegrees), color);
}
public void Draw(Vector2 start, Vector2 end, Color color)
{
verticies.Add(new VertexPositionColor(new Vector3(start, 0f), color));
verticies.Add(new VertexPositionColor(new Vector3(end, 0f), color));
}
public void Draw(Vector3 start, Vector3 end, Color color)
{
verticies.Add(new VertexPositionColor(start, color));
verticies.Add(new VertexPositionColor(end, color));
}
public void End()
{
if (!began)
if (cares_about_begin_without_end)
throw new ArgumentException("Please add begin before end!");
else
Begin();
if (verticies.Count > 0)
{
VertexBuffer vb = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), verticies.Count, BufferUsage.WriteOnly);
vb.SetData<VertexPositionColor>(verticies.ToArray());
GraphicsDevice.SetVertexBuffer(vb);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawPrimitives(PrimitiveType.LineList, 0, verticies.Count / 2);
}
}
began = false;
}
public void Begin()
{
if (began)
if (cares_about_begin_without_end)
throw new ArgumentException("You forgot end.");
else
End();
verticies.Clear();
began = true;
}
}
Here is a simple way that I use to make lines by specifying a start coordinate, an end coordinate, width, and color of them:
NOTE: you must add a file named "dot" to the content directory (the line will be made out of these).
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace Xna.LineHelper
{
public class LineManager
{
int loopCounter;
int lineLegnth;
Vector2 lineDirection;
Vector2 _position;
Color dotColor;
Rectangle _rectangle;
List<Texture2D> _dots = new List<Texture2D>();
FunctionsLibrary functions = new FunctionsLibrary();
public void CreateLineFiles(Vector2 startPosition, Vector2 endPosition, int width, Color color, ContentManager content)
{
dotColor = color;
_position.X = startPosition.X;
_position.Y = startPosition.Y;
lineLegnth = functions.Distance((int)startPosition.X, (int)endPosition.X, (int)startPosition.Y, (int)endPosition.Y);
lineDirection = new Vector2((endPosition.X - startPosition.X) / lineLegnth, (endPosition.Y - startPosition.Y) / lineLegnth);
_dots.Clear();
loopCounter = 0;
_rectangle = new Rectangle((int)startPosition.X, (int)startPosition.Y, width, width);
while (loopCounter < lineLegnth)
{
Texture2D dot = content.Load<Texture2D>("dot");
_dots.Add(dot);
loopCounter += 1;
}
}
public void DrawLoadedLine(SpriteBatch sb)
{
foreach (Texture2D dot in _dots)
{
_position.X += lineDirection.X;
_position.Y += lineDirection.Y;
_rectangle.X = (int)_position.X;
_rectangle.Y = (int)_position.Y;
sb.Draw(dot, _rectangle, dotColor);
}
}
}
public class FunctionsLibrary
{
//Random for all methods
Random Rand = new Random();
#region math
public int TriangleArea1(int bottom, int height)
{
int answer = (bottom * height / 2);
return answer;
}
public double TriangleArea2(int A, int B, int C)
{
int s = ((A + B + C) / 2);
double answer = (Math.Sqrt(s * (s - A) * (s - B) * (s - C)));
return answer;
}
public int RectangleArea(int side1, int side2)
{
int answer = (side1 * side2);
return answer;
}
public int SquareArea(int side)
{
int answer = (side * side);
return answer;
}
public double CircleArea(int diameter)
{
double answer = (((diameter / 2) * (diameter / 2)) * Math.PI);
return answer;
}
public int Diference(int A, int B)
{
int distance = Math.Abs(A - B);
return distance;
}
#endregion
#region standardFunctions
public int Distance(int x1, int x2, int y1, int y2)
{
return (int)(Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
}
#endregion
}
}