Extracting vertices from scenekit - collada

I'm having a problem with understanding scenekit geometery.
I have the default cube from Blender, and I export as collada (DAE), and can bring it into scenekit.... all good.
Now I want to see the vertices for the cube. In the DAE I can see the following for the "Cube-mesh-positions-array",
"1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1"
Now what I'd like to do in scenekit, is get the vertices back, using something like the following:
SCNGeometrySource *vertexBuffer = [[cubeNode.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex] objectAtIndex:0];
If I process the vertexBuffer (I've tried numerous methods of looking at the data), it doesn't seem correct.
Can somebody explain what "SCNGeometrySourceSemanticVertex" is giving me, and how to extract the vertex data properly? What I'd like to see is:
X = "float"
Y = "float"
Z = "float"
Also I was investigating the following class / methods, which looked promising (some good data values here), but the data from gmpe appears empty, is anybody able to explain what the data property of "SCNGeometryElement" contains?
SCNGeometryElement *gmpe = [theCurrentNode.geometry geometryElementAtIndex:0];
Thanks, assistance much appreciated,
D

The geometry source
When you call geometrySourcesForSemantic: you are given back an array of SCNGeometrySource objects with the given semantic in your case the sources for the vertex data).
This data could have been encoded in many different ways and a multiple sources can use the same data with a different stride and offset. The source itself has a bunch of properties for you to be able to decode the data like for example
dataStride
dataOffset
vectorCount
componentsPerVector
bytesPerComponent
You can use combinations of these to figure out which parts of the data to read and make vertices out of them.
Decoding
The stride tells you how many bytes you should step to get to the next vector and the offset tells you how many bytes offset from the start of that vector you should offset before getting to the relevant pars of the data for that vector. The number of bytes you should read for each vector is componentsPerVector * bytesPerComponent
Code to read out all the vertices for a single geometry source would look something like this
// Get the vertex sources
NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources
NSInteger stride = vertexSource.dataStride; // in bytes
NSInteger offset = vertexSource.dataOffset; // in bytes
NSInteger componentsPerVector = vertexSource.componentsPerVector;
NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
NSInteger vectorCount = vertexSource.vectorCount;
SCNVector3 vertices[vectorCount]; // A new array for vertices
// for each vector, read the bytes
for (NSInteger i=0; i<vectorCount; i++) {
// Assuming that bytes per component is 4 (a float)
// If it was 8 then it would be a double (aka CGFloat)
float vectorData[componentsPerVector];
// The range of bytes for this vector
NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
bytesPerVector); // and read the lenght of one vector
// Read into the vector data buffer
[vertexSource.data getBytes:&vectorData range:byteRange];
// At this point you can read the data from the float array
float x = vectorData[0];
float y = vectorData[1];
float z = vectorData[2];
// ... Maybe even save it as an SCNVector3 for later use ...
vertices[i] = SCNVector3Make(x, y, z);
// ... or just log it
NSLog(#"x:%f, y:%f, z:%f", x, y, z);
}
The geometry element
This will give you all the vertices but won't tell you how they are used to construct the geometry. For that you need the geometry element that manages the indices for the vertices.
You can get the number of geometry elements for a piece of geometry from the geometryElementCount property. Then you can get the different elements using geometryElementAtIndex:.
The element can tell you if the vertices are used a individual triangles or a triangle strip. It also tells you the bytes per index (the indices may have been ints or shorts which will be necessary to decode its data.

Here is an extension method if the data isn't contiguous (the vector size isn't equal to the stride) which can be the case when the geometry is loaded from a DAE file. It also doesn't use copyByte function.
extension SCNGeometry{
/**
Get the vertices (3d points coordinates) of the geometry.
- returns: An array of SCNVector3 containing the vertices of the geometry.
*/
func vertices() -> [SCNVector3]? {
let sources = self.sources(for: .vertex)
guard let source = sources.first else{return nil}
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes { (buffer : UnsafePointer<Float>) -> [SCNVector3] in
var result = Array<SCNVector3>()
for i in 0...vectorCount - 1 {
let start = i * stride + offset
let x = buffer[start]
let y = buffer[start + 1]
let z = buffer[start + 2]
result.append(SCNVector3(x, y, z))
}
return result
}
}
}

The Swift Version
The Objective-C version and this are essentially identical.
let planeSources = _planeNode?.geometry?.geometrySourcesForSemantic(SCNGeometrySourceSemanticVertex)
if let planeSource = planeSources?.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](count: planeSource.vectorCount, repeatedValue: SCNVector3Zero)
let vertices = vectors.enumerate().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
var vectorData = [Float](count: componentsPerVector, repeatedValue: 0)
let byteRange = NSMakeRange(index * stride + offset, bytesPerVector)
planeSource.data.getBytes(&vectorData, range: byteRange)
return SCNVector3Make(vectorData[0], vectorData[1], vectorData[2])
})
// You have your vertices, now what?
}

Here's a Swift 5.3 version, based on the other answers, and that also supports a bytesPerComponent different from 4 (untested for size different from 4 though):
extension SCNGeometrySource {
var vertices: [SCNVector3] {
let stride = self.dataStride
let offset = self.dataOffset
let componentsPerVector = self.componentsPerVector
let bytesPerVector = componentsPerVector * self.bytesPerComponent
func vectorFromData<FloatingPoint: BinaryFloatingPoint>(_ float: FloatingPoint.Type, index: Int) -> SCNVector3 {
assert(bytesPerComponent == MemoryLayout<FloatingPoint>.size)
let vectorData = UnsafeMutablePointer<FloatingPoint>.allocate(capacity: componentsPerVector)
defer {
vectorData.deallocate()
}
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
let rangeStart = index * stride + offset
self.data.copyBytes(to: buffer, from: rangeStart..<(rangeStart + bytesPerVector))
return SCNVector3(
CGFloat.NativeType(vectorData[0]),
CGFloat.NativeType(vectorData[1]),
CGFloat.NativeType(vectorData[2])
)
}
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: self.vectorCount)
return vectors.indices.map { index -> SCNVector3 in
switch bytesPerComponent {
case 4:
return vectorFromData(Float32.self, index: index)
case 8:
return vectorFromData(Float64.self, index: index)
case 16:
return vectorFromData(Float80.self, index: index)
default:
return SCNVector3Zero
}
}
}
}

// call this function _ = vertices(node: mySceneView.scene!.rootNode)
// I have get the volume in Swift 4.2 :--- this function
func vertices(node:SCNNode) -> [SCNVector3] {
let planeSources1 = node.childNodes.first?.geometry
let planeSources = planeSources1?.sources(for: SCNGeometrySource.Semantic.vertex)
if let planeSource = planeSources?.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
let vertices = vectors.enumerated().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
let byteRange = Range(nsByteRange)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
planeSource.data.copyBytes(to: buffer, from: byteRange)
return SCNVector3Make(buffer[0], buffer[1], buffer[2])
})
var totalVolume = Float()
var x1 = Float(),x2 = Float(),x3 = Float(),y1 = Float(),y2 = Float(),y3 = Float(),z1 = Float(),z2 = Float(),z3 = Float()
var i = 0
while i < vertices.count{
x1 = vertices[i].x;
y1 = vertices[i].y;
z1 = vertices[i].z;
x2 = vertices[i + 1].x;
y2 = vertices[i + 1].y;
z2 = vertices[i + 1].z;
x3 = vertices[i + 2].x;
y3 = vertices[i + 2].y;
z3 = vertices[i + 2].z;
totalVolume +=
(-x3 * y2 * z1 +
x2 * y3 * z1 +
x3 * y1 * z2 -
x1 * y3 * z2 -
x2 * y1 * z3 +
x1 * y2 * z3);
i = i + 3
}
totalVolume = totalVolume / 6;
volume = "\(totalVolume)"
print("Volume Volume Volume Volume Volume Volume Volume :\(totalVolume)")
lbl_valume.text = "\(clean(String(totalVolume))) cubic mm"
}
return[]
}

With swift 3.1 you can extract vertices from SCNGeometry in a much faster and shorter way:
func vertices(node:SCNNode) -> [SCNVector3] {
let vertexSources = node.geometry?.getGeometrySources(for: SCNGeometrySource.Semantic.vertex)
if let vertexSource = vertexSources?.first {
let count = vertexSource.data.count / MemoryLayout<SCNVector3>.size
return vertexSource.data.withUnsafeBytes {
[SCNVector3](UnsafeBufferPointer<SCNVector3>(start: $0, count: count))
}
}
return []
}
...
Today i've noted that on osx this not going to work correct. This happens because on iOS SCNVector3 build with Float and on osx CGFloat (only apple good do smth simple so suffering). So I had to tweak the code for osx but this not gonna work as fast as on iOS.
func vertices() -> [SCNVector3] {
let vertexSources = sources(for: SCNGeometrySource.Semantic.vertex)
if let vertexSource = vertexSources.first {
let count = vertexSource.vectorCount * 3
let values = vertexSource.data.withUnsafeBytes {
[Float](UnsafeBufferPointer<Float>(start: $0, count: count))
}
var vectors = [SCNVector3]()
for i in 0..<vertexSource.vectorCount {
let offset = i * 3
vectors.append(SCNVector3Make(
CGFloat(values[offset]),
CGFloat(values[offset + 1]),
CGFloat(values[offset + 2])
))
}
return vectors
}
return []
}

For someone like me want to extract data of face from SCNGeometryElement.
Notice I only consider primtive type is triangle and index size is 2 or 4.
void extractInfoFromGeoElement(NSString* scenePath){
NSURL *url = [NSURL fileURLWithPath:scenePath];
SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:nil];
SCNGeometry *geo = scene.rootNode.childNodes.firstObject.geometry;
SCNGeometryElement *elem = geo.geometryElements.firstObject;
NSInteger componentOfPrimitive = (elem.primitiveType == SCNGeometryPrimitiveTypeTriangles) ? 3 : 0;
if (!componentOfPrimitive) {//TODO: Code deals with triangle primitive only
return;
}
for (int i=0; i<elem.primitiveCount; i++) {
void *idxsPtr = NULL;
int stride = 3*i;
if (elem.bytesPerIndex == 2) {
short *idxsShort = malloc(sizeof(short)*3);
idxsPtr = idxsShort;
}else if (elem.bytesPerIndex == 4){
int *idxsInt = malloc(sizeof(int)*3);
idxsPtr = idxsInt;
}else{
NSLog(#"unknow index type");
return;
}
[elem.data getBytes:idxsPtr range:NSMakeRange(stride*elem.bytesPerIndex, elem.bytesPerIndex*3)];
if (elem.bytesPerIndex == 2) {
NSLog(#"triangle %d : %d, %d, %d\n",i,*(short*)idxsPtr,*((short*)idxsPtr+1),*((short*)idxsPtr+2));
}else{
NSLog(#"triangle %d : %d, %d, %d\n",i,*(int*)idxsPtr,*((int*)idxsPtr+1),*((int*)idxsPtr+2));
}
//Free
free(idxsPtr);
}
}

The Swift 3 version:
// `plane` is some kind of `SCNGeometry`
let planeSources = plane.geometry.sources(for: SCNGeometrySource.Semantic.vertex)
if let planeSource = planeSources.first {
let stride = planeSource.dataStride
let offset = planeSource.dataOffset
let componentsPerVector = planeSource.componentsPerVector
let bytesPerVector = componentsPerVector * planeSource.bytesPerComponent
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: planeSource.vectorCount)
let vertices = vectors.enumerated().map({
(index: Int, element: SCNVector3) -> SCNVector3 in
let vectorData = UnsafeMutablePointer<Float>.allocate(capacity: componentsPerVector)
let nsByteRange = NSMakeRange(index * stride + offset, bytesPerVector)
let byteRange = Range(nsByteRange)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: componentsPerVector)
planeSource.data.copyBytes(to: buffer, from: byteRange)
let vector = SCNVector3Make(buffer[0], buffer[1], buffer[2])
})
// Use `vertices` here: vertices[0].x, vertices[0].y, vertices[0].z
}

OK, here is another Swift 5.5 version based on Oliver's answer.
extension SCNGeometry{
/**
Get the vertices (3d points coordinates) of the geometry.
- returns: An array of SCNVector3 containing the vertices of the geometry.
*/
func vertices() -> [SCNVector3]? {
let sources = self.sources(for: .vertex)
guard let source = sources.first else{return nil}
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes { dataBytes in
let buffer: UnsafePointer<Float> = dataBytes.baseAddress!.assumingMemoryBound(to: Float.self)
var result = Array<SCNVector3>()
for i in 0...vectorCount - 1 {
let start = i * stride + offset
let x = buffer[start]
let y = buffer[start + 1]
let z = buffer[start + 2]
result.append(SCNVector3(x, y, z))
}
return result
}
}
}
To use it you simply create a standard shape from which you can extract the vertex and rebuild the index.
let g = SCNSphere(radius: 1)
let newNode = SCNNode(geometry: g)
let vectors = newNode.geometry?.vertices()
var indices:[Int32] = []
for i in stride(from: 0, to: vectors!.count, by: 1) {
indices.append(Int32(i))
indices.append(Int32(i+1))
}
return self.createGeometry(
vertices:vectors!, indices: indices,
primitiveType: SCNGeometryPrimitiveType.line)
The createGeometry extension can be found here
It draws this...

Related

How can I have the pixel coordinate X,Y after ArrayFire match_template?

I'm trying to use the matching_template function from the ArrayFire library But I don't know how to find the X and Y coordinates of the best matching value.
I was using the imageproc library to perform this function and there it has the find_extremes function that returns the coordinates to me. How would you do the same using ArrayFire lib?
My example using imageproc
let template = image::open("connect.png").unwrap().to_luma8();
let screenshot = image::open("screenshot.png").unwrap().to_luma8();
let matching_probability= imageproc::template_matching::match_template(&screenshot, &template, MatchTemplateMethod::CrossCorrelationNormalized);
let positions = find_extremes(&matching_probability);
println!("{:?}", positions);
Extremes { max_value: 0.9998113, min_value: 0.42247093,
max_value_location: (843, 696), min_value_location: (657, 832) }
My example using ArrayFire
let template: Array<u8> = arrayfire::load_image(String::from("connect.png"), true);
let screenshot: Array<u8> = arrayfire::load_image(String::from("screenshot.png"), true);
let template_gray = rgb2gray(&template, 0.2126, 0.7152, 0.0722);
let screen_gray = rgb2gray(&screenshot, 0.2126, 0.7152, 0.0722);
let matching_probability = arrayfire::match_template(&screen_gray, &template_gray, arrayfire::MatchType::LSAD);
af_print!("{:?}", matching_probability);
139569.0469 140099.2500 139869.8594 140015.7969 140680.9844 141952.5781 142602.7344 142870.7188...
from here I don't havy any idea how to get the best matching pixel coordinates.
Arrayfire doesn't provide "extremum" function, but separate min and max families of functions.
The one that provides index informations are prefixed with i.
imin_all and imax_all returns the min and max value indexes respectively wrapped in a tupple.
You can derive pixel position from value indexes and array dimensions, knowing that arrayfire is column major.
let template: Array<u8> = arrayfire::load_image(String::from("connect.png"), true);
let screenshot: Array<u8> = arrayfire::load_image(String::from("screenshot.png"), true);
let template_gray = rgb2gray(&template, 0.2126, 0.7152, 0.0722);
let screen_gray = rgb2gray(&screenshot, 0.2126, 0.7152, 0.0722);
let matching_probability = arrayfire::match_template(&screen_gray, &template_gray, arrayfire::MatchType::LSAD);
let (min, _, min_idx) = imin_all(&matching_probability);
let (max, _, max_idx) = imax_all(&matching_probability);
let dims = matching_probability.dims();
let [_, height, _, _] = dims.get();
let px_x_min = min_idx as u64 / height;
let px_y_min = min_idx as u64 % height;
let px_x_max = max_idx as u64 / height;
let px_y_max = max_idx as u64 % height;
af_print!("{:?}", matching_probability);
println!("Minimum value: {} is at pixel ({},{}).",min, px_x_min, px_y_min);
println!("Maximum value: {} is at pixel ({},{}).", max, px_x_max, px_y_max);

How can I understand the following SVG code?

I have code from a web site, and it looks like it should be simple, but too simple for SVG. How can I determine if this is truly SVG, and what it does? I am especially interested in what looks like nested & and dots[.], then split, map.
Snippet:
// the shape of the dragon, converted from a SVG image
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
Full code:
//7 Dragons
//Rauri
// full source for entry into js1k dragons: http://js1k.com/2014-dragons/demo/1837
// thanks to simon for grunt help and sean for inspiration help
// js1k shim
var a = document.getElementsByTagName('canvas')[0];
var b = document.body;
var d = function(e){ return function(){ e.parentNode.removeChild(e); }; }(a);
// unprefix some popular vendor prefixed things (but stick to their original name)
var AudioContext =
window.AudioContext ||
window.webkitAudioContext;
var requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(f){ setTimeout(f, 1000/30); };
// stretch canvas to screen size (once, wont onresize!)
a.style.width = (a.width = innerWidth - 0) + 'px';
a.style.height = (a.height = innerHeight - 0) + 'px';
var c = a.getContext('2d');
// end shim
var sw = a.width,
sh = a.height,
M = Math,
Mc = M.cos,
Ms = M.sin,
ran = M.random,
pfloat = 0,
pi = M.PI,
dragons = [],
shape = [],
loop = function() {
a.width = sw; // clear screen
for ( j = 0; j < 7; j++) {
if ( !dragons[j] ) dragons[j] = dragon(j); // create dragons initially
dragons[j]();
}
pfloat++;
requestAnimationFrame(loop);
},
dragon = function(index) {
var scale = 0.1 + index * index / 49,
gx = ran() * sw / scale,
gy = sh / scale,
lim = 300, // this gets inlined, no good!
speed = 3 + ran() * 5,
direction = pi, //0, //ran() * pi * 2, //ran(0,TAU),
direction1 = direction,
spine = [];
return function() {
// check if dragon flies off screen
if (gx < -lim || gx > sw / scale + lim || gy < -lim || gy > sh / scale + lim) {
// flip them around
var dx = sw / scale / 2 - gx,
dy = sh / scale / 2 - gy;
direction = direction1 = M.atan(dx/dy) + (dy < 0 ? pi : 0);
} else {
direction1 += ran() * .1 - .05;
direction -= (direction - direction1) * .1;
}
// move the dragon forwards
gx += Ms(direction) * speed;
gy += Mc(direction) * speed;
// calculate a spine - a chain of points
// the first point in the array follows a floating position: gx,gy
// the rest of the chain of points following each other in turn
for (i=0; i < 70; i++) {
if (i) {
if (!pfloat) spine[i] = {x: gx, y: gy}
var p = spine[i - 1],
dx = spine[i].x - p.x,
dy = spine[i].y - p.y,
d = M.sqrt(dx * dx + dy * dy),
perpendicular = M.atan(dy/dx) + pi / 2 + (dx < 0 ? pi : 0);
// make each point chase the previous, but never get too close
if (d > 4) {
var mod = .5;
} else if (d > 2){
mod = (d - 2) / 4;
} else {
mod = 0;
}
spine[i].x -= dx * mod;
spine[i].y -= dy * mod;
// perpendicular is used to map the coordinates on to the spine
spine[i].px = Mc(perpendicular);
spine[i].py = Ms(perpendicular);
if (i == 20) { // average point in the middle of the wings so the wings remain symmetrical
var wingPerpendicular = perpendicular;
}
} else {
// i is 0 - first point in spine
spine[i] = {x: gx, y: gy, px: 0, py: 0};
}
}
// map the dragon to the spine
// the x co-ordinates of each point of the dragon shape are honoured
// the y co-ordinates of each point of the dragon are mapped to the spine
c.moveTo(spine[0].x,spine[0].y)
for (i=0; i < 154; i+=2) { // shape.length * 2 - it's symmetrical, so draw up one side and back down the other
if (i < 77 ) { // shape.length
// draw the one half from nose to tail
var index = i; // even index is x, odd (index + 1) is y of each coordinate
var L = 1;
} else {
// draw the other half from tail back to nose
index = 152 - i;
L = -1;
}
var x = shape[index];
var spineNode = spine[shape[index+1]]; // get the equivalent spine position from the dragon shape
if (index >= 56) { // draw tail
var wobbleIndex = 56 - index; // table wobbles more towards the end
var wobble = Ms(wobbleIndex / 3 + pfloat * 0.1) * wobbleIndex * L;
x = 20 - index / 4 + wobble;
// override the node for the correct tail position
spineNode = spine[ index * 2 - 83 ];
} else if (index > 13) { // draw "flappy wings"
// 4 is hinge point
x = 4 + (x-4) * (Ms(( -x / 2 + pfloat) / 25 * speed / 4) + 2) * 2; // feed x into sin to make wings "bend"
// override the perpindicular lines for the wings
spineNode.px = Mc(wingPerpendicular);
spineNode.py = Ms(wingPerpendicular);
}
c.lineTo(
(spineNode.x + x * L * spineNode.px) * scale,
(spineNode.y + x * L * spineNode.py) * scale
);
}
c.fill();
}
}
// the shape of the dragon, converted from a SVG image
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
loop();
While the context this is used in is <canvas>, the origin may well be a SVG <polyline>.
In a first step, the letters are mapped to numbers. A bit of obscuration, but nothing too serious: get the number representing the letter and write it to an array.
const shape = [];
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
results in an array
[1,0,8,8,6,8,6,10,4,8,4,12,6,14,9,15,13,14,16,12,20,5,19,2,23,4,27,8,32,15,37,33,33,28,31,26,28,25,27,27,24,24,21,23,19,23,18,25,15,23,12,22,8,24,6,27]
Now just write this array to a points attribute of a polyline, joining the numbers with a space character:
const outline = document.querySelector('#outline');
const shape = [];
'! ((&(&*$($,&.)/-.0,4%3"7$;(#/EAA<?:<9;;88573729/7,6(8&;'.split("").map(function(a,i) {
shape[i] = a.charCodeAt(0) - 32;
});
outline.setAttribute('points', shape.join(' '))
#outline {
stroke: black;
stroke-width: 0.5;
fill:none;
}
<svg viewBox="0 0 77 77" width="300" height="300">
<polyline id="outline" />
</svg>
and you get the basic outline of (half) a dragon. The rest is repetition and transformation to make things a bit more complex.

Calculating point to line distance in GLSL

I am trying to calculate a point to line distance in GSLS - precisely in turbo.js turbo.js
This is part of a more general problem in which I try to find the [closest points on GeoJSON multiline] respective to a set of GeoJSON points - the number of calculations for a 500-points set on 1000 segments line ends up being 500k point-to-distance calculations.
This is way too much to handle in the browser (even in workers) so parallelism helps a lot.
The trick is that AFAIK I can only use a vec4 as an input, which means I can only do calculations on pairs of points.
So far I've progressed to calculating distance and bearing of all pairs - but can't make the last leg to calculating point-to-line distance.
So the question is - given 3 points a, b and c, and knowing
their position in lon and lat
their pairwise bearing and distance
Is it possible to calculate the distance from a to the line defined by b and c using transforms that use vec2, vec3 or vec4 as input argument?
As a sub-problem - I know how to calculate the distance if the height of the triangle (a, b, c) doesn't intersect the line (a, b) because it's min(distance(a, b), distance(a, c)).
But then, how do I calculate if it intersects?
I'm not totally sure I understand your question.
It sounds like for 500 input points you want to know, for 1000 line segments, for each point, which segment is closest.
If that's what you're asking then put all the points in a floating point textures (another word for a texture is a 2D array). Draw a -1 to +1 quad that's the size of the number of results (500 results so 50x10 or 25x20 etc..) Pass in the resolution of the textures. Use gl_FragCoord to calculate an index to get the input, A, and loop over all the other lines. Read the results via readPixels by encoding the index of the closest pair as a color.
precision highp float;
uniform sampler2D aValues;
uniform vec2 aDimensions; // the size of the aValues texture in pixels (texels)
uniform sampler2D bValues;
uniform vec2 bDimensions; // the size of the bValues texture in pixels (texels)
uniform sampler2D cValues;
uniform vec2 cDimensions; // the size of the cValues texture in pixels (texels)
uniform vec2 outputDimensions; // the size of the thing we're drawing to (canvas)
// this code, given a sampler2D, the size of the texture, and an index
// computes a UV coordinate to pull one RGBA value out of a texture
// as though the texture was a 1D array.
vec3 getPoint(in sampler2D tex, in vec2 dimensions, in float index) {
vec2 uv = (vec2(
floor(mod(index, dimensions.x)),
floor(index / dimensions.x)) + 0.5) / dimensions;
return texture2D(tex, uv).xyz;
}
// from https://stackoverflow.com/a/6853926/128511
float distanceFromPointToLine(in vec3 a, in vec3 b, in vec3 c) {
vec3 ba = a - b;
vec3 bc = c - b;
float d = dot(ba, bc);
float len = length(bc);
float param = 0.0;
if (len != 0.0) {
param = clamp(d / (len * len), 0.0, 1.0);
}
vec3 r = b + bc * param;
return distance(a, r);
}
void main() {
// gl_FragCoord is the coordinate of the pixel that is being set by the fragment shader.
// It is the center of the pixel so the bottom left corner pixel will be (0.5, 0.5).
// the pixel to the left of that is (1.5, 0.5), The pixel above that is (0.5, 1.5), etc...
// so we can compute back into a linear index
float ndx = floor(gl_FragCoord.y) * outputDimensions.x + floor(gl_FragCoord.x);
// find the closest points
float minDist = 10000000.0;
float minIndex = -1.0;
vec3 a = getPoint(aValues, aDimensions, ndx);
for (int i = 0; i < ${bPoints.length / 4}; ++i) {
vec3 b = getPoint(bValues, bDimensions, float(i));
vec3 c = getPoint(cValues, cDimensions, float(i));
float dist = distanceFromPointToLine(a, b, c);
if (dist < minDist) {
minDist = dist;
minIndex = float(i);
}
}
// convert to 8bit color. The canvas defaults to RGBA 8bits per channel
// so take our integer index (minIndex) and convert to float values that
// will end up as the same 32bit index when read via readPixels as
// 32bit values.
gl_FragColor = vec4(
mod(minIndex, 256.0),
mod(floor(minIndex / 256.0), 256.0),
mod(floor(minIndex / (256.0 * 256.0)), 256.0) ,
floor(minIndex / (256.0 * 256.0 * 256.0))) / 255.0;
}
I'm only going to guess though that in general this is better solved with some spatial structure that somehow makes it so you don't have to check every line with every point but something like the code above should work and be very parallel. Each result will be computed by another GPU core.
const v3 = twgl.v3;
// note: I'm using twgl to make the code smaller.
// This is not lesson in WebGL. You should already know what it means
// to setup buffers and attributes and set uniforms and create textures.
// What's important is the technique, not the minutia of WebGL. If you
// don't know how to do those things you need a much bigger tutorial
// on WebGL like https://webglfundamentals.org
function main() {
const gl = document.createElement('canvas').getContext('webgl');
const ext = gl.getExtension('OES_texture_float');
if (!ext) {
alert('need OES_texture_float');
return;
}
const r = max => Math.random() * max;
const hsl = (h, s, l) => `hsl(${h * 360},${s * 100 | 0}%,${l * 100 | 0}%)`;
function createPoints(numPoints) {
const points = [];
for (let i = 0; i < numPoints; ++i) {
points.push(r(300), r(150), 0, 0); // RGBA
}
return points;
}
function distanceFromPointToLineSquared(a, b, c) {
const ba = v3.subtract(a, b);
const bc = v3.subtract(c, b);
const dot = v3.dot(ba, bc);
const lenSq = v3.lengthSq(bc);
let param = 0;
if (lenSq !== 0) {
param = Math.min(1, Math.max(0, dot / lenSq));
}
const r = v3.add(b, v3.mulScalar(bc, param));
return v3.distanceSq(a, r);
}
const aPoints = createPoints(6);
const bPoints = createPoints(15);
const cPoints = createPoints(15);
// do it in JS to check
{
// compute closest lines to points
const closest = [];
for (let i = 0; i < aPoints.length; i += 4) {
const a = aPoints.slice(i, i + 3);
let minDistSq = Number.MAX_VALUE;
let minIndex = -1;
for (let j = 0; j < bPoints.length; j += 4) {
const b = bPoints.slice(j, j + 3);
const c = cPoints.slice(j, j + 3);
const distSq = distanceFromPointToLineSquared(a, b, c);
if (distSq < minDistSq) {
minDistSq = distSq;
minIndex = j / 4;
}
}
closest.push(minIndex);
}
drawResults(document.querySelector('#js'), closest);
}
const vs = `
attribute vec4 position;
void main() {
gl_Position = position;
}
`;
const fs = `
precision highp float;
uniform sampler2D aValues;
uniform vec2 aDimensions; // the size of the aValues texture in pixels (texels)
uniform sampler2D bValues;
uniform vec2 bDimensions; // the size of the bValues texture in pixels (texels)
uniform sampler2D cValues;
uniform vec2 cDimensions; // the size of the cValues texture in pixels (texels)
uniform vec2 outputDimensions; // the size of the thing we're drawing to (canvas)
// this code, given a sampler2D, the size of the texture, and an index
// computes a UV coordinate to pull one RGBA value out of a texture
// as though the texture was a 1D array.
vec3 getPoint(in sampler2D tex, in vec2 dimensions, in float index) {
vec2 uv = (vec2(
floor(mod(index, dimensions.x)),
floor(index / dimensions.x)) + 0.5) / dimensions;
return texture2D(tex, uv).xyz;
}
// from https://stackoverflow.com/a/6853926/128511
float distanceFromPointToLine(in vec3 a, in vec3 b, in vec3 c) {
vec3 ba = a - b;
vec3 bc = c - b;
float d = dot(ba, bc);
float len = length(bc);
float param = 0.0;
if (len != 0.0) {
param = clamp(d / (len * len), 0.0, 1.0);
}
vec3 r = b + bc * param;
return distance(a, r);
}
void main() {
// gl_FragCoord is the coordinate of the pixel that is being set by the fragment shader.
// It is the center of the pixel so the bottom left corner pixel will be (0.5, 0.5).
// the pixel to the left of that is (1.5, 0.5), The pixel above that is (0.5, 1.5), etc...
// so we can compute back into a linear index
float ndx = floor(gl_FragCoord.y) * outputDimensions.x + floor(gl_FragCoord.x);
// find the closest points
float minDist = 10000000.0;
float minIndex = -1.0;
vec3 a = getPoint(aValues, aDimensions, ndx);
for (int i = 0; i < ${bPoints.length / 4}; ++i) {
vec3 b = getPoint(bValues, bDimensions, float(i));
vec3 c = getPoint(cValues, cDimensions, float(i));
float dist = distanceFromPointToLine(a, b, c);
if (dist < minDist) {
minDist = dist;
minIndex = float(i);
}
}
// convert to 8bit color. The canvas defaults to RGBA 8bits per channel
// so take our integer index (minIndex) and convert to float values that
// will end up as the same 32bit index when read via readPixels as
// 32bit values.
gl_FragColor = vec4(
mod(minIndex, 256.0),
mod(floor(minIndex / 256.0), 256.0),
mod(floor(minIndex / (256.0 * 256.0)), 256.0) ,
floor(minIndex / (256.0 * 256.0 * 256.0))) / 255.0;
}
`;
// compile shader, link program, lookup locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for a -1 to +1 quad
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
// make an RGBA float texture for each set of points
// calls gl.createTexture, gl.bindTexture, gl.texImage2D, gl.texParameteri
const aTex = twgl.createTexture(gl, {
src: aPoints,
width: aPoints.length / 4,
type: gl.FLOAT,
minMag: gl.NEAREST,
});
const bTex = twgl.createTexture(gl, {
src: bPoints,
width: bPoints.length / 4,
type: gl.FLOAT,
minMag: gl.NEAREST,
});
const cTex = twgl.createTexture(gl, {
src: cPoints,
width: cPoints.length / 4,
type: gl.FLOAT,
minMag: gl.NEAREST,
});
const numOutputs = aPoints.length / 4;
gl.canvas.width = numOutputs;
gl.canvas.height = 1;
gl.viewport(0, 0, numOutputs, 1);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.activeTexture, gl.bindTexture, gl.uniform
twgl.setUniforms(programInfo, {
aValues: aTex,
aDimensions: [aPoints.length / 4, 1],
bValues: cTex,
bDimensions: [bPoints.length / 4, 1],
cValues: bTex,
cDimensions: [cPoints.length / 4, 1],
outputDimensions: [aPoints.length / 4, 1],
});
// draw the quad
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
// get result
const pixels = new Uint8Array(numOutputs * 4);
const results = new Uint32Array(pixels.buffer);
gl.readPixels(0, 0, numOutputs, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
drawResults(document.querySelector('#glsl'), results);
function drawResults(canvas, closest) {
const ctx = canvas.getContext('2d');
// draw the lines
ctx.beginPath();
for (let j = 0; j < bPoints.length; j += 4) {
const b = bPoints.slice(j, j + 2);
const c = cPoints.slice(j, j + 2);
ctx.moveTo(...b);
ctx.lineTo(...c);
}
ctx.strokeStyle = '#888';
ctx.stroke();
// draw the points and closest lines
for (let i = 0; i < aPoints.length; i += 4) {
const a = aPoints.slice(i, i + 2);
const ndx = closest[i / 4] * 4;
const b = bPoints.slice(ndx, ndx + 2);
const c = cPoints.slice(ndx, ndx + 2);
const color = hsl(i / aPoints.length, 1, 0.4);
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.fillRect(a[0] - 2, a[1] - 2, 5, 5);
ctx.beginPath();
ctx.moveTo(...b);
ctx.lineTo(...c);
ctx.stroke();
}
}
}
main();
canvas { border: 1px solid black; margin: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<div>glsl</div>
<canvas id="glsl"></canvas>
<div>js</div>
<canvas id="js"></canvas>
If you use WebGL2 then you can use texelFetch so getPoint becomes
vec3 getPoint(in sampler2D tex, in int index) {
ivec2 size = textureSize(tex, 0);
ivec2 uv = ivec2(index % size.x, index / size.x);
return texelFetch(tex, uv, 0).xyz;
}
and you don't need to pass in the size of the input textures, only the output size. Also you could make your output R32U and output unsigned integer indices so no need to encode the result.
note: The code assumes you are doing less then 2048 values for each a, b and c so much of the code assumes 1 dimensional textures. If you need more than 2048 you'll need to adjust the code to make rectangular textures of a size that fits your data for example if you had 9000 values then a 9x1000 texture would work. If you have 8999 values then you still need a 9x1000 texture just padded to make a rectangle since textures are 2D arrays.
Also note that calling readPixels is considered slow. For example, if you just wanted to draw the results as above, instead of rendering to the canvas and reading the values out via readPixels you could render the result to a texture, then pass the texture into another shader.
addendum
This is probably the wrong place for this but as a terse explanation of GLSL for stuff like this you can think of GLSL as a fancy version of Array.prototype.map. When you use map you don't choose what is being written to directly. It happens indirectly.
const a = [1, 2, 3, 4, 5];
const b = a.map((v, index) => { return v * 2 + index; });
The { return v * 2 + index} part is analogous to a shader. In JavaScript the function inside map returns in value. in GLSL ES 1.0 the shader sets gl_FragColor as the output. In the Javascript index is the index of the array being written to (and happens to be the index of the input array as well). In GLSL gl_FragCoord serves the same role.
Otherwise, the output of the vertex shader determines which pixels (which array elements of a 2D array) will get written to so that makes it a more selective version of map. In the code above we're drawing a -1 to +1 quad effectively saying "map over all pixels".
In fact here's a version of the above code, no GLSL, just JavaScript, but the JavaScript re-structured to look more like GLSL.
const v3 = twgl.v3;
function main() {
const r = max => Math.random() * max;
const hsl = (h, s, l) => `hsl(${h * 360},${s * 100 | 0}%,${l * 100 | 0}%)`;
function createPoints(numPoints) {
const points = [];
for (let i = 0; i < numPoints; ++i) {
points.push(r(300), r(150), 0, 0); // RGBA
}
return points;
}
function distanceFromPointToLineSquared(a, b, c) {
const ba = v3.subtract(a, b);
const bc = v3.subtract(c, b);
const dot = v3.dot(ba, bc);
const lenSq = v3.lengthSq(bc);
let param = 0;
if (lenSq !== 0) {
param = Math.min(1, Math.max(0, dot / lenSq));
}
const r = v3.add(b, v3.mulScalar(bc, param));
return v3.distanceSq(a, r);
}
const aPoints = createPoints(6);
const bPoints = createPoints(15);
const cPoints = createPoints(15);
const gl_FragCoord = {};
let gl_FragColor;
const aValues = aPoints;
const aDimensions = {}; // N/A
const bValues = bPoints;
const bDimensions = {}; // N/A
const cValues = cPoints;
const cDimensions = {}; // N/A
const outputDimensions = {x: aPoints.length / 4, y: 1 };
function getPoint(sampler, dimension, ndx) {
return sampler.slice(ndx * 4, ndx * 4 + 3);
}
function javaScriptFragmentShader() {
// gl_FragCoord is the coordinate of the pixel that is being set by the fragment shader.
// It is the center of the pixel so the bottom left corner pixel will be (0.5, 0.5).
// the pixel to the left of that is (1.5, 0.5), The pixel above that is (0.5, 1.5), etc...
// so we can compute back into a linear index
const ndx = Math.floor(gl_FragCoord.y) * outputDimensions.x + Math.floor(gl_FragCoord.x);
// find the closest points
let minDist = 10000000.0;
let minIndex = -1.0;
const a = getPoint(aValues, aDimensions, ndx);
for (let i = 0; i < bPoints.length / 4; ++i) {
const b = getPoint(bValues, bDimensions, i);
const c = getPoint(cValues, cDimensions, i);
const dist = distanceFromPointToLineSquared(a, b, c);
if (dist < minDist) {
minDist = dist;
minIndex = i;
}
}
// convert to 8bit color. The canvas defaults to RGBA 8bits per channel
// so take our integer index (minIndex) and convert to float values that
// will end up as the same 32bit index when read via readPixels as
// 32bit values.
gl_FragColor = [
minIndex % 256.0,
Math.floor(minIndex / 256.0) % 256.0,
Math.floor(minIndex / (256.0 * 256.0)) % 256.0,
Math.floor(minIndex / (256.0 * 256.0 * 256.0)),
].map(v => v / 255.0);
}
// do it in JS to check
{
// compute closest lines to points
const closest = [];
const width = aPoints.length / 4;
const height = 1;
// WebGL drawing each pixel
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
gl_FragCoord.x = x + 0.5; // because pixels represent a rectangle one unit wide in pixel space
gl_FragCoord.y = y + 0.5; // so the center of each pixel in the middle of that rectangle
javaScriptFragmentShader();
const index = gl_FragColor[0] * 255 +
gl_FragColor[1] * 255 * 256 +
gl_FragColor[2] * 255 * 256 * 256 +
gl_FragColor[3] * 255 * 256 * 256 * 256;
closest.push(index);
}
}
drawResults(document.querySelector('#js'), closest);
}
function drawResults(canvas, closest) {
const ctx = canvas.getContext('2d');
// draw the lines
ctx.beginPath();
for (let j = 0; j < bPoints.length; j += 4) {
const b = bPoints.slice(j, j + 2);
const c = cPoints.slice(j, j + 2);
ctx.moveTo(...b);
ctx.lineTo(...c);
}
ctx.strokeStyle = '#888';
ctx.stroke();
// draw the points and closest lines
for (let i = 0; i < aPoints.length; i += 4) {
const a = aPoints.slice(i, i + 2);
const ndx = closest[i / 4] * 4;
const b = bPoints.slice(ndx, ndx + 2);
const c = cPoints.slice(ndx, ndx + 2);
const color = hsl(i / aPoints.length, 1, 0.4);
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.fillRect(a[0] - 2, a[1] - 2, 5, 5);
ctx.beginPath();
ctx.moveTo(...b);
ctx.lineTo(...c);
ctx.stroke();
}
}
}
main();
canvas { border: 1px solid black; margin: 5px; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas id="js"></canvas>

Know slice number?

I have a DICOM series, with following origin, spacing and extent:
int nExtent[6];
double dSpacing[3];
double dOrigin[3];
m_pReader->GetOutputInformation(0)->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), nExtent);
m_pReader->GetOutput()->GetSpacing(dSpacing);
m_pReader->GetOutput()->GetOrigin(dOrigin);
where m_pReader is vtkDICOMReader object ...
dOrigin is 0, 0, 0;
dSpacing id 0.447266, 0.447266, 3.998718;
nExtent is 0, 511, 0, 511, 0, 43;
the series is AXIAL.
Now, if I slice the series through AXIAL plan, I could slice the series by 44 slices, like that:
double deltaY = 0.0;
delta += 1.0;
pReslice->Update();
double dSliceSpacing = pReslice->GetOutput()->GetSpacing()[2];
vtkMatrix4x4* pMatrix = pReslice->GetResliceAxes();
// move the dCenter dPoint that we are slicing through
double dPoint[4];
double dCenter[4];
dPoint[0] = 0.0;
dPoint[1] = 0.0;
dPoint[2] = dSliceSpacing * deltaY;
dPoint[3] = 1.0;
pMatrix->MultiplyPoint(dPoint, dCenter);
pMatrix->SetElement(0, 3, dCenter[0]);
pMatrix->SetElement(1, 3, dCenter[1]);
pMatrix->SetElement(2, 3, dCenter[2]);
Everything is allright ...
The problem: if I slice the series through CORONAL plan, the slices number are not 44 !! But how many ? How can I know the slice number if the plan is CORONAL or SAGITTAL ?
On Coronal and Sagittal slicing its more about position instead of slice index.
You need to calculate your min/max origin for each axis (x,y,z)
e.g.
myOriginMax.X = myOrigin.X + ((ImageDimension.SizeX - 1) * mySpacing.X);
You can compute your new origin in your slicing Event, where _positionDelta is your inc/dec value. (more or less pseudo code)
e.g.
double[] _origin = myImageReslice.GetResliceAxesOrigin();
if(_view == "SAGITTAL")
{
_origin[0] = Math.Min(_origin[0] + _positionDelta * mySpacing.X, myOriginMax.X);
}
else if(_view == "CORONAL")
{
_origin[1] = Math.Min(_origin[1] + _positionDelta * mySpacing.Y, myOriginMax.Y);
}
else //AXIAL
{
_origin[2] = Math.Min(_origin[2] + _positionDelta * mySpacing.Z, myOriginMax.Z);
}
myImageReslice.SetReliceAxesOrigin(_origin[0], _origin[1], _origin[2]);
Render();
Yes, it is another compute method ... in my case I have m_pReslice for axial plan, m_pReslice2 for coronal plan, and m_pReslice3 for sagittal plan ... I don't know if is the proper architecture, but applying your algorithm I arrive in the same place :)
m_pReslice->GetResliceAxesOrigin(dOrigin);
int nSizeX = nExtent[0] + nExtent[1];
int nSizeY = nExtent[2] + nExtent[3];
int nSizeZ = nExtent[4] + nExtent[5];
double dOriginMax[3];
dOriginMax[0] = dOrigin[0] + ((nSizeX - 1) * dSpacing[0]);
dOriginMax[1] = dOrigin[1] + ((nSizeY - 1) * dSpacing[1]);
dOriginMax[2] = dOrigin[2] + ((nSizeZ - 1) * dSpacing[2]);
dOrigin[0] = min(dOrigin[0] + 1.0 * dSpacing[0], dOriginMax[0]);
dOrigin[1] = min(dOrigin[1] + 1.0 * dSpacing[1], dOriginMax[1]);
dOrigin[2] = min(dOrigin[2] + 1.0 * dSpacing[2], dOriginMax[2]);
m_pReslice->SetResliceAxesOrigin(dOrigin);
this is the case for m_pReslice (axial plan) ... if I apply the algorithm for m_pReslice2 (coronal) and for m_pReslice3 (sagittal), I still don't know how may slices I have in coronal case (or sagittal) ...

Graphic algorithm Unions, intersect, subtract

I need a good source for reading up on how to create a algorithm to take two polylines (a path comprised of many lines) and performing a union, subtraction, or intersection between them. This is tied to a custom API so I need to understand the underlying algorithm.
Plus any sources in a VB dialect would be doubly helpful.
This catalogue of implementations of intersection algorithms from the Stony Brook Algorithm Repository might be useful. The repository is managed by Steven Skiena,
author of a very well respected book on algorithms: The Algorithm Design Manual.
That's his own Amazon exec link by the way :)
Several routines for you here. Hope you find them useful :-)
// routine to calculate the square of either the shortest distance or largest distance
// from the CPoint to the intersection point of a ray fired at an angle flAngle
// radians at an array of line segments
// this routine returns TRUE if an intersection has been found in which case flD
// is valid and holds the square of the distance.
// and returns FALSE if no valid intersection was found
// If an intersection was found, then intersectionPoint is set to the point found
bool CalcIntersection(const CPoint &cPoint,
const float flAngle,
const int nVertexTotal,
const CPoint *pVertexList,
const BOOL bMin,
float &flD,
CPoint &intersectionPoint)
{
float d, dsx, dsy, dx, dy, lambda, mu, px, py;
int p0x, p0y, p1x, p1y;
// get source position
const float flSx = (float)cPoint.x;
const float flSy = -(float)cPoint.y;
// calc trig functions
const float flTan = tanf(flAngle);
const float flSin = sinf(flAngle);
const float flCos = cosf(flAngle);
const bool bUseSin = fabsf(flSin) > fabsf(flCos);
// initialise distance
flD = (bMin ? FLT_MAX : 0.0f);
// for each line segment in protective feature
for(int i = 0; i < nVertexTotal; i++)
{
// get coordinates of line (negate the y value so the y-axis is upwards)
p0x = pVertexList[i].x;
p0y = -pVertexList[i].y;
p1x = pVertexList[i + 1].x;
p1y = -pVertexList[i + 1].y;
// calc. deltas
dsx = (float)(cPoint.x - p0x);
dsy = (float)(-cPoint.y - p0y);
dx = (float)(p1x - p0x);
dy = (float)(p1y - p0y);
// calc. denominator
d = dy * flTan - dx;
// if line & ray are parallel
if(fabsf(d) < 1.0e-7f)
continue;
// calc. intersection point parameter
lambda = (dsy * flTan - dsx) / d;
// if intersection is not valid
if((lambda <= 0.0f) || (lambda > 1.0f))
continue;
// if sine is bigger than cosine
if(bUseSin){
mu = ((float)p0x + lambda * dx - flSx) / flSin;
} else {
mu = ((float)p0y + lambda * dy - flSy) / flCos;
}
// if intersection is valid
if(mu >= 0.0f){
// calc. intersection point
px = (float)p0x + lambda * dx;
py = (float)p0y + lambda * dy;
// calc. distance between intersection point & source point
dx = px - flSx;
dy = py - flSy;
d = dx * dx + dy * dy;
// compare with relevant value
if(bMin){
if(d < flD)
{
flD = d;
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
}
} else {
if(d > flD)
{
flD = d;
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
}
}
}
}
// return
return(bMin ? (flD != FLT_MAX) : (flD != 0.0f));
}
// Routine to calculate the square of the distance from the CPoint to the
// intersection point of a ray fired at an angle flAngle radians at a line.
// This routine returns TRUE if an intersection has been found in which case flD
// is valid and holds the square of the distance.
// Returns FALSE if no valid intersection was found.
// If an intersection was found, then intersectionPoint is set to the point found.
bool CalcIntersection(const CPoint &cPoint,
const float flAngle,
const CPoint &PointA,
const CPoint &PointB,
const bool bExtendLine,
float &flD,
CPoint &intersectionPoint)
{
// get source position
const float flSx = (float)cPoint.x;
const float flSy = -(float)cPoint.y;
// calc trig functions
float flTan = tanf(flAngle);
float flSin = sinf(flAngle);
float flCos = cosf(flAngle);
const bool bUseSin = fabsf(flSin) > fabsf(flCos);
// get coordinates of line (negate the y value so the y-axis is upwards)
const int p0x = PointA.x;
const int p0y = -PointA.y;
const int p1x = PointB.x;
const int p1y = -PointB.y;
// calc. deltas
const float dsx = (float)(cPoint.x - p0x);
const float dsy = (float)(-cPoint.y - p0y);
float dx = (float)(p1x - p0x);
float dy = (float)(p1y - p0y);
// Calc. denominator
const float d = dy * flTan - dx;
// If line & ray are parallel
if(fabsf(d) < 1.0e-7f)
return false;
// calc. intersection point parameter
const float lambda = (dsy * flTan - dsx) / d;
// If extending line to meet point, don't check for ray missing line
if(!bExtendLine)
{
// If intersection is not valid
if((lambda <= 0.0f) || (lambda > 1.0f))
return false; // Ray missed line
}
// If sine is bigger than cosine
float mu;
if(bUseSin){
mu = ((float)p0x + lambda * dx - flSx) / flSin;
} else {
mu = ((float)p0y + lambda * dy - flSy) / flCos;
}
// if intersection is valid
if(mu >= 0.0f)
{
// calc. intersection point
const float px = (float)p0x + lambda * dx;
const float py = (float)p0y + lambda * dy;
// calc. distance between intersection point & source point
dx = px - flSx;
dy = py - flSy;
flD = (dx * dx) + (dy * dy);
intersectionPoint.x = RoundValue(px);
intersectionPoint.y = -RoundValue(py);
return true;
}
return false;
}
// Fillet (with a radius of 0) two lines. From point source fired at angle (radians) to line Line1A, Line1B.
// Modifies line end point Line1B. If the ray does not intersect line, then it is rotates every 90 degrees
// and tried again until fillet is complete.
void Fillet(const CPoint &source, const float fThetaRadians, const CPoint &Line1A, CPoint &Line1B)
{
if(Line1A == Line1B)
return; // No line
float dist;
if(CalcIntersection(source, fThetaRadians, Line1A, Line1B, true, dist, Line1B))
return;
if(CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI * 0.5f), Line1A, Line1B, true, dist, Line1B))
return;
if(CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI), Line1A, Line1B, true, dist, Line1B))
return;
if(!CalcIntersection(source, CalcBaseFloat(TWO_PI, fThetaRadians + PI * 1.5f), Line1A, Line1B, true, dist, Line1B))
ASSERT(FALSE); // Could not find intersection?
}
// routine to determine if an array of line segments cross gridSquare
// x and y give the float coordinates of the corners
BOOL CrossGridSquare(int nV, const CPoint *pV,
const CRect &extent, const CRect &gridSquare)
{
// test extents
if( (extent.right < gridSquare.left) ||
(extent.left > gridSquare.right) ||
(extent.top > gridSquare.bottom) ||
(extent.bottom < gridSquare.top))
{
return FALSE;
}
float a, b, c, dx, dy, s, x[4], y[4];
int max_x, max_y, min_x, min_y, p0x, p0y, p1x, p1y, sign, sign_old;
// construct array of vertices for grid square
x[0] = (float)gridSquare.left;
y[0] = (float)gridSquare.top;
x[1] = (float)(gridSquare.right);
y[1] = y[0];
x[2] = x[1];
y[2] = (float)(gridSquare.bottom);
x[3] = x[0];
y[3] = y[2];
// for each line segment
for(int i = 0; i < nV; i++)
{
// get end-points
p0x = pV[i].x;
p0y = pV[i].y;
p1x = pV[i + 1].x;
p1y = pV[i + 1].y;
// determine line extent
if(p0x > p1x){
min_x = p1x;
max_x = p0x;
} else {
min_x = p0x;
max_x = p1x;
}
if(p0y > p1y){
min_y = p1y;
max_y = p0y;
} else {
min_y = p0y;
max_y = p1y;
}
// test to see if grid square is outside of line segment extent
if( (max_x < gridSquare.left) ||
(min_x > gridSquare.right) ||
(max_y < gridSquare.top) ||
(min_y > gridSquare.bottom))
{
continue;
}
// calc. line equation
dx = (float)(p1x - p0x);
dy = (float)(p1y - p0y);
a = dy;
b = -dx;
c = -dy * (float)p0x + dx * (float)p0y;
// evaluate line eqn. at first grid square vertex
s = a * x[0] + b * y[0] + c;
if(s < 0.0f){
sign_old = -1;
} else if(s > 1.0f){
sign_old = 1;
} else {
sign_old = 0;
}
// evaluate line eqn. at other grid square vertices
for (int j = 1; j < 4; j++)
{
s = a * x[j] + b * y[j] + c;
if(s < 0.0f){
sign = -1;
} else if(s > 1.0f){
sign = 1;
} else {
sign = 0;
}
// if there has been a chnage in sign
if(sign != sign_old)
return TRUE;
}
}
return FALSE;
}
// calculate the square of the shortest distance from point s
// and the line segment between p0 and p1
// t is the point on the line from which the minimum distance
// is measured
float CalcShortestDistanceSqr(const CPoint &s,
const CPoint &p0,
const CPoint &p1,
CPoint &t)
{
// if point is at a vertex
if((s == p0) || (s == p1))
return(0.0F);
// calc. deltas
int dx = p1.x - p0.x;
int dy = p1.y - p0.y;
int dsx = s.x - p0.x;
int dsy = s.y - p0.y;
// if both deltas are zero
if((dx == 0) && (dy == 0))
{
// shortest distance is distance is to either vertex
float l = (float)(dsx * dsx + dsy * dsy);
t = p0;
return(l);
}
// calc. point, p, on line that is closest to sourcePosition
// p = p0 + l * (p1 - p0)
float l = (float)(dsx * dx + dsy * dy) / (float)(dx * dx + dy * dy);
// if intersection is beyond p0
if(l <= 0.0F){
// shortest distance is to p0
l = (float)(dsx * dsx + dsy * dsy);
t = p0;
// else if intersection is beyond p1
} else if(l >= 1.0F){
// shortest distance is to p1
dsx = s.x - p1.x;
dsy = s.y - p1.y;
l = (float)(dsx * dsx + dsy * dsy);
t = p1;
// if intersection is between line end points
} else {
// calc. perpendicular distance
float ldx = (float)dsx - l * (float)dx;
float ldy = (float)dsy - l * (float)dy;
t.x = p0.x + RoundValue(l * (float)dx);
t.y = p0.y + RoundValue(l * (float)dy);
l = ldx * ldx + ldy * ldy;
}
return(l);
}
// Calculates the bounding rectangle around a set of points
// Returns TRUE if the rectangle is not empty (has area), FALSE otherwise
// Opposite of CreateRectPoints()
BOOL CalcBoundingRectangle(const CPoint *pVertexList, const int nVertexTotal, CRect &rect)
{
rect.SetRectEmpty();
if(nVertexTotal < 2)
{
ASSERT(FALSE); // Must have at least 2 points
return FALSE;
}
// First point, set rectangle (no area at this point)
rect.left = rect.right = pVertexList[0].x;
rect.top = rect.bottom = pVertexList[0].y;
// Increst rectangle by looking at other points
for(int n = 1; n < nVertexTotal; n++)
{
if(rect.left > pVertexList[n].x) // Take minimum
rect.left = pVertexList[n].x;
if(rect.right < pVertexList[n].x) // Take maximum
rect.right = pVertexList[n].x;
if(rect.top > pVertexList[n].y) // Take minimum
rect.top = pVertexList[n].y;
if(rect.bottom < pVertexList[n].y) // Take maximum
rect.bottom = pVertexList[n].y;
}
rect.NormalizeRect(); // Normalise rectangle
return !(rect.IsRectEmpty());
}

Resources