Tinting a terrain model in a radius around a given point in XNA 4.0 - visual-studio-2010

I'm writing a game in Visual Studio 2010, using the XNA 4.0 framework. I have a 3D terrain model generated from a height map. What I'm trying to accomplish is to tint this model in a given radius around a certain point, the end goal being to display to the player the radius in which a unit can move in a given turn. The method I'm using to draw the model at the moment is this:
void DrawModel(Model model, Matrix worldMatrix)
{
Matrix[] boneTransforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(boneTransforms);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.World = boneTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = camera.viewMatrix;
effect.Projection = camera.projectionMatrix;
effect.EnableDefaultLighting();
effect.EmissiveColor = Color.Green.ToVector3();
effect.PreferPerPixelLighting = true;
// Set the fog to match the black background color
effect.FogEnabled = true;
effect.FogColor = Color.CornflowerBlue.ToVector3();
effect.FogStart = 1000;
effect.FogEnd = 3200;
}
mesh.Draw();
}
}
Also, in case it's relevant, I followed this tutorial http://create.msdn.com/en-US/education/catalog/sample/collision_3d_heightmap to create my heightmap and terrain.
Thanks in advance for any help!

You can use a shader to achieve that...
you only would need to pass as argument the world position of the center and the radius,
and let the pixel shader receive the pixel world position interpolated from the vertex shader as a texture coord...
then only have to check the distance of the pixel position to the center and tint it with a color if the pixel position is in range...

The technique you are looking for is called decaling.
You have to extract the part of the terrain, where the circle will be drawn, apply an appropriate texture to that part and draw it blending it with the terrain.
For the case of a terrain based on a uniform grid, this will look like the following:
You have the center position of the decal and its radius. Then you can determine min and max row/col in the grid, so that the cells include every drawn region. Create a new vertex buffer from these vertices. Positions can be read from the heightmap. You have to alter the texture coordinates, so the texture will be placed at the right position. Assume, the center position has coordinate (0.5, 0.5), center position + (radius, radius) has coordinate (1, 1) and so on. With that you should be able to find an equation for the texture coordinates for each vertex.
In the above example, the top left red vertex has texture coordinates of about (-0.12, -0.05)
Then you have the subgrid of the terrain. Apply the decal texture to it. Set an appropriate depth bias (you have to try out some values). In most cases, a negative SlopeScaleDepthBias will work. Turn off texture coordinate wrapping in the sampler. Draw the subgrid.
Here's some VB SlimDX Code I wrote for that purpose:
Public Sub Init()
Verts = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2) ^ 2
Tris = (Math.Ceiling(2 * Radius / TriAngleWidth) + 1) ^ 2 * 2
Dim Indices(Tris * 3 - 1) As Integer
Dim curN As Integer
Dim w As Integer
w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2)
For y As Integer = 0 To w - 2
For x As Integer = 0 To w - 2
Indices(curN) = x + y * w : curN += 1
Indices(curN) = x + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + (y) * w : curN += 1
Indices(curN) = x + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + (y + 1) * w : curN += 1
Indices(curN) = (x + 1) + y * w : curN += 1
Next
Next
VB = New Buffer(D3DDevice, New BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes))
IB = New Buffer(D3DDevice, New DataStream(Indices, False, False), New BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4))
End Sub
Public Sub Update()
Dim Vertex(Verts - 1) As VertexPosTexColor.Struct
Dim curN As Integer
Dim rad As Single 'The decal radius
Dim height As Single
Dim p As Vector2
Dim yx, yz As Integer
Dim t As Vector2 'texture coordinates
Dim center As Vector2 'decal center
For y As Integer = Math.Floor((center.Y - rad) / TriAngleWidth) To Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
For x As Integer = Math.Floor((center.X - rad) / TriAngleWidth) To Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1
p.X = x * TriAngleWidth
p.Y = y * TriAngleWidth
yx = x : yz = y
If yx < 0 Then yx = 0
If yx > HeightMap.GetUpperBound(0) Then yx = HeightMap.GetUpperBound(0)
If yz < 0 Then yz = 0
If yz > HeightMap.GetUpperBound(1) Then yz = HeightMap.GetUpperBound(1)
height = HeightMap(yx, yz)
t.X = (p.X - center.X) / (2 * rad) + 0.5
t.Y = (p.Y - center.Y) / (2 * rad) + 0.5
Vertex(curN) = New VertexPosTexColor.Struct With {.Position = New Vector3(p.X, hoehe, p.Y), .TexCoord = t, .Color = New Color4(1, 1, 1, 1)} : curN += 1
Next
Next
Dim data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None)
data.Data.WriteRange(Vertex)
D3DContext.UnmapSubresource(VB, 0)
End Sub
And here's the according C# code.
public void Init()
{
Verts = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 2, 2);
Tris = Math.Pow(Math.Ceiling(2 * Radius / TriAngleWidth) + 1, 2) * 2;
int[] Indices = new int[Tris * 3];
int curN;
int w;
w = (Math.Ceiling(2 * Radius / TriAngleWidth) + 2);
for(int y = 0; y <= w - 2; ++y)
{
for(int x = 0; x <= w - 2; ++x)
{
Indices[curN] = x + y * w ; curN += 1;
Indices[curN] = x + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + (y) * w ; curN += 1;
Indices[curN] = x + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + (y + 1) * w ; curN += 1;
Indices[curN] = (x + 1) + y * w ; curN += 1;
}
}
VB = new Buffer(D3DDevice, new BufferDescription(Verts * VertexPosTexColor.Struct.SizeOfBytes, ResourceUsage.Dynamic, BindFlags.VertexBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, VertexPosTexColor.Struct.SizeOfBytes));
IB = new Buffer(D3DDevice, new DataStream(Indices, False, False), new BufferDescription(4 * Tris * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 4));
}
public void Update()
{
VertexPosTexColor.Struct[] Vertex = new VertexPosTexColor.Struct[Verts] ;
int curN;
float rad; //The decal radius
float height;
Vector2 p;
int yx, yz;
Vector2 t; //texture coordinates
Vector2 center; //decal center
for(int y = Math.Floor((center.Y - rad) / TriAngleWidth); y <= Math.Floor((center.Y - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++y)
for(int x = Math.Floor((center.X - rad) / TriAngleWidth); x <= Math.Floor((center.X - rad) / TriAngleWidth) + Math.Ceiling(2 * rad / TriAngleWidth) + 1; ++x)
{
p.X = x * TriAngleWidth;
p.Y = y * TriAngleWidth;
yx = x ; yz = y;
if( yx < 0)
yx = 0;
if (yx > HeightMap.GetUpperBound(0))
yx = HeightMap.GetUpperBound(0);
if (yz < 0)
yz = 0;
if (yz > HeightMap.GetUpperBound(1))
yz = HeightMap.GetUpperBound(1);
height = HeightMap[yx, yz];
t.X = (p.X - center.X) / (2 * rad) + 0.5;
t.Y = (p.Y - center.Y) / (2 * rad) + 0.5;
Vertex[curN] = new VertexPosTexColor.Struct() {Position = new Vector3(p.X, hoehe, p.Y), TexCoord = t, Color = New Color4(1, 1, 1, 1)}; curN += 1;
}
}
var data = D3DContext.MapSubresource(VB, MapMode.WriteDiscard, MapFlags.None);
data.Data.WriteRange(Vertex);
D3DContext.UnmapSubresource(VB, 0);
}

Related

How to create a text made of glass in canvas with refraction and reflection?

What I'd like to achieve is close to this there. You can also just take a look at those screenshots.
The actual result
Notice how the refraction is evolving as the page scrolls down/up. Scrolling, there is also a source of light going right to left.
After scrolling
Ideally I'd like the text to have that transparent glass reflective aspect like on the example provided. But also, to refract what is behind, which does not seem to be the case here. Indeed, when the canvas is left alone, the refraction still happens, so i suspect the effects is done knowing what would be displayed in the background. As for me, I'd like to refract whats behind dynamically. Yet again i'm thinking that i might have been achieved this way for a reason, maybe performance issue
All non canvas elements removed
Indeed, it looks like it it based from the background, but the background is not within the canvas. Also, as you can see, on the next picture, the refraction effect is still hapenning even though the background is removed.
Refraction
The source of light is still there and i suspect it's using some kind of ray casting/ray tracing method. I'm not at all familiar with drawing in the canvas (except using p5.js for simple things),and it took me a long time to find ray tracing with no idea of what i'm looking for.
.... Questions ....
How do i get the glass transparent reflective aspect on the text ? Should it be achieve with graphic design tools ? (I don't know how to get an object (see screenshot below) that seem to have the texture bind afterwards.I'm not even sure if i'm using the right vocabulary but assuming I am, I don't know how to make such texture.)
text object no "texture"
How to refract everything that would be placed behind the glass object? (Before I came to the conclusion that I needed to use canvas, not just because I found this exemple, but also because of other considerations related to the project I'm working on. I've invest a lot of time learning suffisant svg to achieve what you can see on the next screenshot,and failed to achieve what was aimed. I'm not willing to do so the same with ray casting thus my third question. I hope it's understandable...Still the refracted part is there but looks a lot less realistic than in the provided example.)
SVG
Is ray casting/ray tracing is the right path to dig in for achieving the refraction ? Will it be okay to use if its ray tracing every objects behind.
Thanks for your time and concern.
Reflection and Refraction
There are so many tutorials online to achieve this FX I can not see the point in repeating them.
This answer presents an approximation using a normal map in place of a 3D model, and flat texture maps to represent the reflection and refraction maps, rather than 3D textures traditionally used to get reflections and refraction.
Generating a normal map.
The snippet below generates a normal map from input text with various options. The process is reasonably quick (not real time) and will be the stand in for a 3D model in the webGL rendering solution.
It first creates a height map of the text, adds some smoothing, then converts the map to a normal map.
text.addEventListener("keyup", createNormalMap)
createNormalMap();
function createNormalMap(){
text.focus();
setTimeout(() => {
const can = normalMapText(text.value, "Arial Black", 96, 8, 2, 0.1, true, "round");
result.innerHTML = "";
result.appendChild(can);
}, 0);
}
function normalMapText(text, font, size, bevel, smooth = 0, curve = 0.5, smoothNormals = true, corners = "round") {
const canvas = document.createElement("canvas");
const mask = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const ctxMask = mask.getContext("2d");
ctx.font = size + "px " + font;
const tw = ctx.measureText(text).width;
const cx = (mask.width = canvas.width = tw + bevel * 3) / 2;
const cy = (mask.height = canvas.height = size + bevel * 3) / 2;
ctx.font = size + "px " + font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.lineJoin = corners;
const step = 255 / (bevel + 1);
var j, i = 0, val = step;
while (i < bevel) {
ctx.lineWidth = bevel - i;
const v = ((val / 255) ** curve) * 255;
ctx.strokeStyle = `rgb(${v},${v},${v})`;
ctx.strokeText(text, cx, cy);
i++;
val += step;
}
ctx.fillStyle = "#FFF";
ctx.fillText(text, cx, cy);
if (smooth >= 1) {
ctxMask.drawImage(canvas, 0, 0);
ctx.filter = "blur(" + smooth + "px)";
ctx.drawImage(mask, 0, 0);
ctx.globalCompositeOperation = "destination-in";
ctx.filter = "none";
ctx.drawImage(mask, 0, 0);
ctx.globalCompositeOperation = "source-over";
}
const w = canvas.width, h = canvas.height, w4 = w << 2;
const imgData = ctx.getImageData(0,0,w,h);
const d = imgData.data;
const heightBuf = new Uint8Array(w * h);
j = i = 0;
while (i < d.length) {
heightBuf[j++] = d[i]
i += 4;
}
var x, y, xx, yy, zz, xx1, yy1, zz1, xx2, yy2, zz2, dist;
i = 0;
for(y = 0; y < h; y ++){
for(x = 0; x < w; x ++){
if(d[i + 3]) { // only pixels with alpha > 0
const idx = x + y * w;
const x1 = 1;
const z1 = heightBuf[idx - 1] === undefined ? 0 : heightBuf[idx - 1] - heightBuf[idx];
const y1 = 0;
const x2 = 0;
const z2 = heightBuf[idx - w] === undefined ? 0 : heightBuf[idx - w] - heightBuf[idx];
const y2 = -1;
const x3 = 1;
const z3 = heightBuf[idx - w - 1] === undefined ? 0 : heightBuf[idx - w - 1] - heightBuf[idx];
const y3 = -1;
xx = y3 * z2 - z3 * y2
yy = z3 * x2 - x3 * z2
zz = x3 * y2 - y3 * x2
dist = (xx * xx + yy * yy + zz * zz) ** 0.5;
xx /= dist;
yy /= dist;
zz /= dist;
xx1 = y1 * z3 - z1 * y3
yy1 = z1 * x3 - x1 * z3
zz1 = x1 * y3 - y1 * x3
dist = (xx1 * xx1 + yy1 * yy1 + zz1 * zz1) ** 0.5;
xx += xx1 / dist;
yy += yy1 / dist;
zz += zz1 / dist;
if (smoothNormals) {
const x1 = 2;
const z1 = heightBuf[idx - 2] === undefined ? 0 : heightBuf[idx - 2] - heightBuf[idx];
const y1 = 0;
const x2 = 0;
const z2 = heightBuf[idx - w * 2] === undefined ? 0 : heightBuf[idx - w * 2] - heightBuf[idx];
const y2 = -2;
const x3 = 2;
const z3 = heightBuf[idx - w * 2 - 2] === undefined ? 0 : heightBuf[idx - w * 2 - 2] - heightBuf[idx];
const y3 = -2;
xx2 = y3 * z2 - z3 * y2
yy2 = z3 * x2 - x3 * z2
zz2 = x3 * y2 - y3 * x2
dist = (xx2 * xx2 + yy2 * yy2 + zz2 * zz2) ** 0.5 * 2;
xx2 /= dist;
yy2 /= dist;
zz2 /= dist;
xx1 = y1 * z3 - z1 * y3
yy1 = z1 * x3 - x1 * z3
zz1 = x1 * y3 - y1 * x3
dist = (xx1 * xx1 + yy1 * yy1 + zz1 * zz1) ** 0.5 * 2;
xx2 += xx1 / dist;
yy2 += yy1 / dist;
zz2 += zz1 / dist;
xx += xx2;
yy += yy2;
zz += zz2;
}
dist = (xx * xx + yy * yy + zz * zz) ** 0.5;
d[i+0] = ((xx / dist) + 1.0) * 128;
d[i+1] = ((yy / dist) + 1.0) * 128;
d[i+2] = 255 - ((zz / dist) + 1.0) * 128;
}
i += 4;
}
}
ctx.putImageData(imgData, 0, 0);
return canvas;
}
<input id="text" type="text" value="Normal Map" />
<div id="result"></div>
Approximation
To render the text we need to create some shaders. As we are using a normal map the vertex shader can be very simple.
Vertex shader
We are using a quad to render the whole canvas. The vertex shader outputs the 4 corners and converts each corner to a texture coordinate.
#version 300 es
in vec2 vert;
out vec2 texCoord;
void main() {
texCoord = vert * 0.5 + 0.5;
gl_Position = vec4(verts, 1, 1);
}
Fragment shader
The fragment shader has 3 texture inputs. The normal map, and the reflection and refraction maps.
The fragment shader first works out if the pixel is part of the background, or on the text. If on the text it converts the RGB texture normal into a vector normal.
It then uses vector addition to get the reflected and refracted textures. Mixing those textures by the normal maps z value. In effect refraction is strongest when the normal is facing up and reflection strongest when normal facing away
#version 300 es
uniform sampler2D normalMap;
uniform sampler2D refractionMap;
uniform sampler2D reflectionMap;
in vec2 texCoord;
out vec4 pixel;
void main() {
vec4 norm = texture(normalMap, texCoord);
if (norm.a > 0) {
vec3 normal = normalize(norm.rgb - 0.5);
vec2 tx1 = textCoord + normal.xy * 0.1;
vec2 tx2 = textCoord - normal.xy * 0.2;
pixel = vec4(mix(texture(refractionMap, tx2).rgb, texture(reflectionMap, tx1).rgb, abs(normal.z)), norm.a);
} else {
pixel = texture(refactionMap, texCoord);
}
}
That is the most basic form that will give the impression of reflection and refraction.
Example NOT REAL reflection refraction.
The example is a little more complex as the various textures have different sizes and thus need to be scaled in the fragment shader to be the correct size.
I have also added some tinting to both the refraction and reflections and mixed the reflection via a curve.
The background is scrolled to the mouse position. To match a background on the page you would move the canvas over the background.
There are a few #defines in the frag shader to control the settings. You could make them uniforms, or constants.
mixCurve controls the mix of reflect refract textures. Values < 1 > 0 ease out refraction, values > 1 ease out the reflection.
The normal map is one to one with rendered pixels. As 2D canvas rendering is rather poor quality you can get a better result by over sampling the normal map in the fragment shader.
const vertSrc = `#version 300 es
in vec2 verts;
out vec2 texCoord;
void main() {
texCoord = verts * vec2(0.5, -0.5) + 0.5;
gl_Position = vec4(verts, 1, 1);
}
`
const fragSrc = `#version 300 es
precision highp float;
#define refractStrength 0.1
#define reflectStrength 0.2
#define refractTint vec3(1,0.95,0.85)
#define reflectTint vec3(1,1.25,1.42)
#define mixCurve 0.3
uniform sampler2D normalMap;
uniform sampler2D refractionMap;
uniform sampler2D reflectionMap;
uniform vec2 scrolls;
in vec2 texCoord;
out vec4 pixel;
void main() {
vec2 nSize = vec2(textureSize(normalMap, 0));
vec2 scaleCoords = nSize / vec2(textureSize(refractionMap, 0));
vec2 rCoord = (texCoord - scrolls) * scaleCoords;
vec4 norm = texture(normalMap, texCoord);
if (norm.a > 0.99) {
vec3 normal = normalize(norm.rgb - 0.5);
vec2 tx1 = rCoord + normal.xy * scaleCoords * refractStrength;
vec2 tx2 = rCoord - normal.xy * scaleCoords * reflectStrength;
vec3 c1 = texture(refractionMap, tx1).rgb * refractTint;
vec3 c2 = texture(reflectionMap, tx2).rgb * reflectTint;
pixel = vec4(mix(c2, c1, abs(pow(normal.z,mixCurve))), 1.0);
} else {
pixel = texture(refractionMap, rCoord);
}
}
`
var program, loc;
function normalMapText(text, font, size, bevel, smooth = 0, curve = 0.5, smoothNormals = true, corners = "round") {
const canvas = document.createElement("canvas");
const mask = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const ctxMask = mask.getContext("2d");
ctx.font = size + "px " + font;
const tw = ctx.measureText(text).width;
const cx = (mask.width = canvas.width = tw + bevel * 3) / 2;
const cy = (mask.height = canvas.height = size + bevel * 3) / 2;
ctx.font = size + "px " + font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.lineJoin = corners;
const step = 255 / (bevel + 1);
var j, i = 0, val = step;
while (i < bevel) {
ctx.lineWidth = bevel - i;
const v = ((val / 255) ** curve) * 255;
ctx.strokeStyle = `rgb(${v},${v},${v})`;
ctx.strokeText(text, cx, cy);
i++;
val += step;
}
ctx.fillStyle = "#FFF";
ctx.fillText(text, cx, cy);
if (smooth >= 1) {
ctxMask.drawImage(canvas, 0, 0);
ctx.filter = "blur(" + smooth + "px)";
ctx.drawImage(mask, 0, 0);
ctx.globalCompositeOperation = "destination-in";
ctx.filter = "none";
ctx.drawImage(mask, 0, 0);
ctx.globalCompositeOperation = "source-over";
}
const w = canvas.width, h = canvas.height, w4 = w << 2;
const imgData = ctx.getImageData(0,0,w,h);
const d = imgData.data;
const heightBuf = new Uint8Array(w * h);
j = i = 0;
while (i < d.length) {
heightBuf[j++] = d[i]
i += 4;
}
var x, y, xx, yy, zz, xx1, yy1, zz1, xx2, yy2, zz2, dist;
i = 0;
for(y = 0; y < h; y ++){
for(x = 0; x < w; x ++){
if(d[i + 3]) { // only pixels with alpha > 0
const idx = x + y * w;
const x1 = 1;
const z1 = heightBuf[idx - 1] === undefined ? 0 : heightBuf[idx - 1] - heightBuf[idx];
const y1 = 0;
const x2 = 0;
const z2 = heightBuf[idx - w] === undefined ? 0 : heightBuf[idx - w] - heightBuf[idx];
const y2 = -1;
const x3 = 1;
const z3 = heightBuf[idx - w - 1] === undefined ? 0 : heightBuf[idx - w - 1] - heightBuf[idx];
const y3 = -1;
xx = y3 * z2 - z3 * y2
yy = z3 * x2 - x3 * z2
zz = x3 * y2 - y3 * x2
dist = (xx * xx + yy * yy + zz * zz) ** 0.5;
xx /= dist;
yy /= dist;
zz /= dist;
xx1 = y1 * z3 - z1 * y3
yy1 = z1 * x3 - x1 * z3
zz1 = x1 * y3 - y1 * x3
dist = (xx1 * xx1 + yy1 * yy1 + zz1 * zz1) ** 0.5;
xx += xx1 / dist;
yy += yy1 / dist;
zz += zz1 / dist;
if (smoothNormals) {
const x1 = 2;
const z1 = heightBuf[idx - 2] === undefined ? 0 : heightBuf[idx - 2] - heightBuf[idx];
const y1 = 0;
const x2 = 0;
const z2 = heightBuf[idx - w * 2] === undefined ? 0 : heightBuf[idx - w * 2] - heightBuf[idx];
const y2 = -2;
const x3 = 2;
const z3 = heightBuf[idx - w * 2 - 2] === undefined ? 0 : heightBuf[idx - w * 2 - 2] - heightBuf[idx];
const y3 = -2;
xx2 = y3 * z2 - z3 * y2
yy2 = z3 * x2 - x3 * z2
zz2 = x3 * y2 - y3 * x2
dist = (xx2 * xx2 + yy2 * yy2 + zz2 * zz2) ** 0.5 * 2;
xx2 /= dist;
yy2 /= dist;
zz2 /= dist;
xx1 = y1 * z3 - z1 * y3
yy1 = z1 * x3 - x1 * z3
zz1 = x1 * y3 - y1 * x3
dist = (xx1 * xx1 + yy1 * yy1 + zz1 * zz1) ** 0.5 * 2;
xx2 += xx1 / dist;
yy2 += yy1 / dist;
zz2 += zz1 / dist;
xx += xx2;
yy += yy2;
zz += zz2;
}
dist = (xx * xx + yy * yy + zz * zz) ** 0.5;
d[i+0] = ((xx / dist) + 1.0) * 128;
d[i+1] = ((yy / dist) + 1.0) * 128;
d[i+2] = 255 - ((zz / dist) + 1.0) * 128;
}
i += 4;
}
}
ctx.putImageData(imgData, 0, 0);
return canvas;
}
function createChecker(size, width, height) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = width * size;
canvas.height = height * size;
for(var y = 0; y < size; y ++) {
for(var x = 0; x < size; x ++) {
const xx = x * width;
const yy = y * height;
ctx.fillStyle ="#888";
ctx.fillRect(xx,yy,width,height);
ctx.fillStyle ="#DDD";
ctx.fillRect(xx,yy,width/2,height/2);
ctx.fillRect(xx+width/2,yy+height/2,width/2,height/2);
}
}
return canvas;
}
const mouse = {x:0, y:0};
addEventListener("mousemove",e => {mouse.x = e.pageX; mouse.y = e.pageY });
var normMap = normalMapText("GLASSY", "Arial Black", 128, 24, 1, 0.1, true, "round");
canvas.width = normMap.width;
canvas.height = normMap.height;
const locations = {updates: []};
const fArr = arr => new Float32Array(arr);
const gl = canvas.getContext("webgl2", {premultipliedAlpha: false, antialias: false, alpha: false});
const textures = {};
setup();
function texture(gl, image, {min = "LINEAR", mag = "LINEAR", wrapX = "REPEAT", wrapY = "REPEAT"} = {}) {
const texture = gl.createTexture();
target = gl.TEXTURE_2D;
gl.bindTexture(target, texture);
gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl[min]);
gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl[mag]);
gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl[wrapX]);
gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl[wrapY]);
gl.texImage2D(target, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
return texture;
}
function bindTexture(texture, unit) {
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(gl.TEXTURE_2D, texture);
}
function Location(name, data, type = "fv", autoUpdate = true) {
const glUpdateCall = gl["uniform" + data.length + type].bind(gl);
const loc = gl.getUniformLocation(program, name);
locations[name] = {data, update() {glUpdateCall(loc, data)}};
autoUpdate && locations.updates.push(locations[name]);
return locations[name];
}
function compileShader(src, type, shader = gl.createShader(type)) {
gl.shaderSource(shader, src);
gl.compileShader(shader);
return shader;
}
function setup() {
program = gl.createProgram();
gl.attachShader(program, compileShader(vertSrc, gl.VERTEX_SHADER));
gl.attachShader(program, compileShader(fragSrc, gl.FRAGMENT_SHADER));
gl.linkProgram(program);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([0,1,2,0,2,3]), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, fArr([-1,-1,1,-1,1,1,-1,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc = gl.getAttribLocation(program, "verts"));
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
Location("scrolls", [0, 0]);
Location("normalMap", [0], "i", false).update();
Location("refractionMap", [1], "i", false).update();
Location("reflectionMap", [2], "i", false).update();
textures.norm = texture(gl,normMap);
textures.reflect = texture(gl,createChecker(8,128,128));
textures.refract = texture(gl,createChecker(8,128,128));
gl.viewport(0, 0, normMap.width, normMap.height);
bindTexture(textures.norm, 0);
bindTexture(textures.reflect, 1);
bindTexture(textures.refract, 2);
loop();
}
function draw() {
for(const l of locations.updates) { l.update() }
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
}
function loop() {
locations.scrolls.data[0] = -1 + mouse.x / canvas.width;
locations.scrolls.data[1] = -1 + mouse.y / canvas.height;
draw();
requestAnimationFrame(loop);
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Personally I find this FX more visually pleasing than simulations based on real lighting models. Though keep in mind THIS IS NOT Refraction or Reflections.

How to make a spiral rotate?

I have successfully coded a static spiral using lines, and now I'm supposed to make the spiral rotate from frame to frame. I tried incrementing the angle used for the x and y positions of the end of the lines with each frame, but the spiral doesn't move at all.
void draw() {
for (int i = 0; i < 15 * NUM_LINES; i++) {
float lineEndX = width / 2 + radius * cos(angle + startAngle);
float lineEndY = height / 2 + radius * sin(angle + startAngle);
line (lineStartX, lineStartY, lineEndX, lineEndY);
lineStartX = lineEndX;
lineStartY = lineEndY;
radius = radius + 0.047;
angle += 0.01 % (TWO_PI * NUM_TURNS);
}
startAngle += START_ANGLE_CHANGE;
angle = 0;
}
Add background(255); to your draw function. Also define lineStartX, lineStartY and radius there so their values are reset every time the function is called.
void draw() {
background(255);
float lineEndX = width / 2;
float lineEndY = height / 2;
float radius = 5;
for (int i = 0; i < 15 * NUM_LINES; i++) {
float lineEndX = width / 2 + radius * cos(angle + startAngle);
float lineEndY = height / 2 + radius * sin(angle + startAngle);
line (lineStartX, lineStartY, lineEndX, lineEndY);
lineStartX = lineEndX;
lineStartY = lineEndY;
radius = radius + 0.047;
angle += 0.01 % (TWO_PI * NUM_TURNS);
}
startAngle += START_ANGLE_CHANGE;
angle = 0;
}
Working example here.

Project Tango: Depthmap Transformation from XYZij data

I'm currently trying to filter the depth information using OpenCV. For that reason I need to transform Project Tango's depth information XYZij into a image like depthmap. (Like the output of Microsoft Kinect) Unfortunately the official APIs lacking the ij part of XYZij. That's why I'm trying to project the XYZ part using the camera intrinsics projection, wich is explained in the official C API Dokumentation. My current approach looks like this:
float fx = static_cast<float>(ccIntrinsics.fx);
float fy = static_cast<float>(ccIntrinsics.fy);
float cx = static_cast<float>(ccIntrinsics.cx);
float cy = static_cast<float>(ccIntrinsics.cy);
float k1 = static_cast<float>(ccIntrinsics.distortion[0]);
float k2 = static_cast<float>(ccIntrinsics.distortion[1]);
float k3 = static_cast<float>(ccIntrinsics.distortion[2]);
for (int k = 0; k < xyz_ij->xyz_count; ++k) {
float X = xyz_ij->xyz[k][0];
float Y = xyz_ij->xyz[k][1];
float Z = xyz_ij->xyz[k][2];
float ru = sqrt((pow(X, 2) + pow(Y, 2)) / pow(Z, 2));
float rd = ru + k1 * pow(ru, 3) + k2 * pow(ru, 5) + k3 * pow(ru, 7);
int x = X / Z * fx * rd / ru + cx;
int y = X / Z * fy * rd / ru + cy;
// drawing into OpenCV Mat in red
depth.at<cv::Vec3b>(x, y)[0] = 240;
}
The resulting depthmap can be seen in the lower right corner. But it seems that this calculation result in a linear representation ... Does anyone has already done something similar? Are the XYZ points already correct positioned for this projection?
I have actually found a solution ... Just skipped the distortion calculation like they do in the rgb-depth-sync-example. My code now looks like this:
float fx = static_cast<float>(ccIntrinsics.fx);
float fy = static_cast<float>(ccIntrinsics.fy);
float cx = static_cast<float>(ccIntrinsics.cx);
float cy = static_cast<float>(ccIntrinsics.cy);
int width = static_cast<int>(ccIntrinsics.width);
int height = static_cast<int>(ccIntrinsics.height);
for (int k = 0; k < xyz_ij->xyz_count; ++k) {
float X = xyz_ij->xyz[k * 3][0];
float Y = xyz_ij->xyz[k * 3][1];
float Z = xyz_ij->xyz[k * 3][2];
int x = static_cast<int>(fx * (X / Z) + cx);
int y = static_cast<int>(fy * (Y / Z) + cy);
uint8_t depth_value = UCHAR_MAX - ((Z * 1000) * UCHAR_MAX / 4500);
cv::Point point(y % height, x % width);
line(depth, point, point, cv::Scalar(depth_value, depth_value, depth_value), 4.5);
}
And the working OpenCV result looks like this:

How do I initialize the t-variables in "A Fast Voxel Traversal Algorithm for Ray Tracing"?

I am trying to implement the algorithm explained on this paper, used to traverse grid cells in order following a straight line, which is useful for ray tracing:
http://www.cse.yorku.ca/~amana/research/grid.pdf
The paper describes the algorithm as two parts: initialisation and iterative traversal. I can undersand the iterative traversal part, but I'm having trouble understanding how some of the variables in the initialisation part are calculated.
I need help initialising tMaxX, tMaxY, tDeltaX & tDeltaY. Their initialisation procedure is explained as follows:
Next, we determine the value of t at which the ray crosses the first
vertical voxel boundary and store it in variable tMaxX. We perform a
similar computation in y and store the result in tMaxY. The minimum of
these two values will indicate how much we can travel along the ray
and still remain in the current voxel.
Finally, we compute tDeltaX and tDeltaY. TDeltaX indicates how far
along the ray we must move (in units of t) for the horizontal
component of such a movement to equal the width of a voxel. Similarly,
store in tDeltaY the amount of movement along the ray which has a
vertical component equal to the height of a voxel.
I'm not able to deduce the code I need form the English description given above. Can someone translate it to a math/pseudocode expression for me?
Initialization for X-coordinate variables (the same for Y)
DX = X2 - X1
tDeltaX = GridCellWidth / DX
tMaxX = tDeltaX * (1.0 - Frac(X1 / GridCellWidth))
//Frac if fractional part of float, for example, Frac(1.3) = 0.3, Frac(-1.7)=0.3
Example:
GridCellWidth, Height = 20
X1 = 5, X2 = 105
Y1 = 5, Y2 = 55
DX = 100, DY = 50
tDeltaX = 0.2, tDeltaY = 0.4
tMaxX = 0.2 * (1.0 - 0.25) = 0.15 //ray will meet first vertical line at this param
tMaxY = 0.4 * (1.0 - 0.25) = 0.3 //ray will meet first horizontal line at this param
We can see that first cell border will be met at parameter t = 0.15
The one that worked for me in 3D for both positive and negative directions (in CUDA C):
#define SIGN(x) (x > 0 ? 1 : (x < 0 ? -1 : 0))
#define FRAC0(x) (x - floorf(x))
#define FRAC1(x) (1 - x + floorf(x))
float tMaxX, tMaxY, tMaxZ, tDeltaX, tDeltaY, tDeltaZ;
int3 voxel;
float x1, y1, z1; // start point
float x2, y2, z2; // end point
int dx = SIGN(x2 - x1);
if (dx != 0) tDeltaX = fmin(dx / (x2 - x1), 10000000.0f); else tDeltaX = 10000000.0f;
if (dx > 0) tMaxX = tDeltaX * FRAC1(x1); else tMaxX = tDeltaX * FRAC0(x1);
voxel.x = (int) x1;
int dy = SIGN(y2 - y1);
if (dy != 0) tDeltaY = fmin(dy / (y2 - y1), 10000000.0f); else tDeltaY = 10000000.0f;
if (dy > 0) tMaxY = tDeltaY * FRAC1(y1); else tMaxY = tDeltaY * FRAC0(y1);
voxel.y = (int) y1;
int dz = SIGN(z2 - z1);
if (dz != 0) tDeltaZ = fmin(dz / (z2 - z1), 10000000.0f); else tDeltaZ = 10000000.0f;
if (dz > 0) tMaxZ = tDeltaZ * FRAC1(z1); else tMaxZ = tDeltaZ * FRAC0(z1);
voxel.z = (int) z1;
while (true) {
if (tMaxX < tMaxY) {
if (tMaxX < tMaxZ) {
voxel.x += dx;
tMaxX += tDeltaX;
} else {
voxel.z += dz;
tMaxZ += tDeltaZ;
}
} else {
if (tMaxY < tMaxZ) {
voxel.y += dy;
tMaxY += tDeltaY;
} else {
voxel.z += dz;
tMaxZ += tDeltaZ;
}
}
if (tMaxX > 1 && tMaxY > 1 && tMaxZ > 1) break;
// process voxel here
}
Note, grid cell's width/height/depth are all equal to 1 in my case, but you can easily modify it for your needs.

2D bounding box of a sector?

I've googled till I'm blue in the face, and unless I'm missing something really obvious, I can't find any algorithms for calculating the bounding box of a 2D sector.
Given the centre point of the enclosing circle, the radius, and the angles of the extent of the sector, what's the best algorithm to calculate the axis-aligned bounding rectangle of that sector?
Generate the following points:
The circle's center
The positions of the start and end angles of the sector
Additionally, for the angles among 0, 90, 180, and 270 that are within the angle range of the sector, their respective points on the sector
Calculate the min and max x and y from the above points. This is your bounding box
I'm going to rephrase yairchu's answer so that it is clearer (to me, anyway).
Ignore the center coordinates for now and draw the circle at the origin. Convince yourself of the following:
Anywhere the arc intersects an axis will be a max or a min.
If the arc doesn't intersect an axis, then the center will be one corner of the bounding rectangle, and this is the only case when it will be.
The only other possible extreme points of the sector to consider are the endpoints of the radii.
You now have at most 4+1+2 points to find. Find the max and min of those coordinates to draw the rectangle.
The rectangle is easily translated to the original circle by adding the coordinates of the center of the original circle to the rectangle's coordinates.
First of all I apologize if I commit mistakes writing but english is not my first language, spanish is actually!
I faced this problem, and I think I found an efficient solution.
First of all let's see an image of the situation
So we have an ellipse (actually a circle) and two points (C, D) which indicates our sector.
We also have the center of our circle (B) and the angle of the Arc alpha.
Now, in this case I made it passing through 360º on porpouse to see if it would work.
Let's say alpha -> -251.1º (it negative cause its clockwise), lets transform it to positive value 360º - 251.1º = 108.9º now our goal is to find the angle of the bisection of that angle so we can find the max point for the bounding box (E in the image), actually as you may have realized, the length of the segment BE equals the radius of the circle but we must have the angle to obtain the actual coordinates of the E point.
So 108.9º / 2 -> 54.45º now we have the angle.
To find the coordinates of E we use polar coordinates so
x = r * Cos(theta)
y = r * Sin(theta)
we have r and theta so we can calculate x and y
in my example r = 2.82… (actually it's irational but I took the first two decimal digits as a matter of ease)
We know our first radii is 87.1º so theta would be 87.1 - 54.45º -> 32.65º
we know *theta * is 32.65º so let's do some math
x = 2.82 * Cos(32.65º) -> 2.37552
y = 2.82 * Sin(32.65º) -> 1.52213
Now we need to adjust these values to the actual center of the circle so
x = x + centerX
y = y + centerY
In the example, the circle is centered at (1.86, 4.24)
x -> 4.23552
y -> 5.76213
At this stage we should use some calculus. We know that one of the edges of the bounding box will be a tangent of the arc that passes through the point we just calculated so, lets find that tangent (the red line).
We know that the tangent passes through our point (4.23, 5.76) now we need a slope.
As you can see, the slope is the same as the slope of the rect that passes through our radii's so we have to find that slope.
For doing that we need to get the coordinates of our radii's (a fast conversion to cartessian coordinates from polar coordinates).
x = r * Cos(theta)
y = r * Sin(theta)
So
p0 = (centerX + 2.82 * Cos(87.1º), centerY + 2.82 * Sin(87.1º))
p1 = (centerX + 2.82 * Cos(-21.8º), centerY + 2.82 * Sin(-21.8º))
(21.8º is the angle measured clockwise from the horizontal axis to the radii that is below it and thus I put it negative)
p0 (2, 7.06)
p1 (4.48, 3.19)
now let's find the slope:
m = (y - y0) / (x - x0)
...
m = (3.19 - 7.06) / (4.48-2) = -3.87 / 2.48 = -1.56048
...
m = -1.56
having the slope we need to calculate the equation for the tangent, basically is a rect with an already known slope (m = -1.56) that passes through an already know point (E -> (4.23, 5.76))
So we have Y = mx + b where m = -1.56, y = 5.76 and x = 4.23 so b must be
b = 5.76 - (-1.56) * 4.23 = 12.36
Now we have the complete equation for our tangent -> Y = -1.56X + 12.36
All we must do know is project the points C and D over that rect.
We need the equations for the rects CH and DI so let's calculate 'em
Let's start with CH:
We know (from the tanget's equation) that our direction vector is (1.56, 1)
We need to find a rect that passes through the point C -> (2, 7.06)
(x - 2) / 1.56 = (y - 7.06) / 1
Doing some algebra -> y = 0.64x + 5.78
We know have the equation for the rect CH we must calculate the point H.
we have to solve a linear system as follows
y = -1.56x + 12.36
y = 1.56x + 5.78
Solving this we'll find the point H (3, 7.69)
We need to do the same with the rect DI so let's do it
Our direction vector is (1.56, 1) once again
D -> (4.48, 3.19)
(x - 4.48) / 1.56 = (y -3.19) / 1
Doing some algebra -> y = 0.64x + 0.32
Lets solve the linear system
y = -1.56x + 12.36
y = 0.64x + 0.32
I (5.47, 3.82)
At this stage we already have the four points that make our Bounding box -> C, H, D , I
Just in case you don't know or rememeber how to solve a linear system on a programming language, i'll give you a little example
It's pure algebra
Let's say we have the following system
Ax + By = C
Dx + Ey = F
then
Dx = F - Ey
x = (F - Ey) / D
x = F/D - (E/D)y
replacing on the other equation
A(F/D - (E/D)y) + By = C
AF/D - (AE/D)y + By = C
(AE/D)y + By = C - AF/D
y(-AE/D + B) = C - AF/D
y = (C - AF/D) / (-AE/D + B)
= ( (CD - AF) / D ) / ( (-AE + BD) / D) )
so
y = (CD - AF) / (BD - AE)
and for x we do the same
Dx = F - Ey
Dx - F = -Ey
Ey = F - Dx
y = F/E - (D/E)x
replacing on the other equation
Ax + B(F/E - (D/E)x) = C
Ax + (BF/E - (DB/E)x) = C
Ax - (DB/E)x = C - BF/E
x (A-(DB/E)) = C - BF/E
x = (C - BF/E)/(A-(DB/E))
= ((CE - BF) / E) / ((AE-DB) / E)
x = (CE - BF) / (AE - DB)
I apologize for the extent of my answer but I meant to be as clear as possible and thus, I made it almost step by step.
In C# code:
/// <summary>
/// The input parameters describe a circular arc going _clockwise_ from E to F.
/// The output is the bounding box.
/// </summary>
public Rect BoundingBox(Point E, Point F, Point C, double radius)
{
// Put the endpoints into the bounding box:
double x1 = E.X;
double y1 = E.Y;
double x2 = x1, y2 = y1;
if (F.X < x1)
x1 = F.X;
if (F.X > x2)
x2 = F.X;
if (F.Y < y1)
y1 = F.Y;
if (F.Y > y2)
y2 = F.Y;
// Now consider the top/bottom/left/right extremities of the circle:
double thetaE = Math.Atan2(E.Y - C.Y, E.X - C.X);
double thetaF = Math.Atan2(F.Y - C.Y, F.X - C.X);
if (AnglesInClockwiseSequence(thetaE, 0/*right*/, thetaF))
{
double x = (C.X + radius);
if (x > x2)
x2 = x;
}
if (AnglesInClockwiseSequence(thetaE, Math.PI/2/*bottom*/, thetaF))
{
double y = (C.Y + radius);
if (y > y2)
y2 = y;
}
if (AnglesInClockwiseSequence(thetaE, Math.PI/*left*/, thetaF))
{
double x = (C.X - radius);
if (x < x1)
x1 = x;
}
if (AnglesInClockwiseSequence(thetaE, Math.PI*3/2/*top*/, thetaF))
{
double y = (C.Y - radius);
if (y < y1)
y1 = y;
}
return new Rect(x1, y1, x2 - x1, y2 - y1);
}
/// <summary>
/// Do these angles go in clockwise sequence?
/// </summary>
private static bool AnglesInClockwiseSequence(double x, double y, double z)
{
return AngularDiffSigned(x, y) + AngularDiffSigned(y, z) < 2*Math.PI;
}
/// <summary>
/// Returns a number between 0 and 360 degrees, as radians, representing the
/// angle required to go clockwise from 'theta1' to 'theta2'. If 'theta2' is
/// 5 degrees clockwise from 'theta1' then return 5 degrees. If it's 5 degrees
/// anticlockwise then return 360-5 degrees.
/// </summary>
public static double AngularDiffSigned(double theta1, double theta2)
{
double dif = theta2 - theta1;
while (dif >= 2 * Math.PI)
dif -= 2 * Math.PI;
while (dif <= 0)
dif += 2 * Math.PI;
return dif;
}
I tried to implement jairchu's answer, but found some problems, which I would like to share:
My coordinate system for the circle starts with 0 degrees at the right side of the circle and runs counterclockwise through the top (90deg), the left(180deg) and the bottom (270deg). The angles can be between 0 and 359,9999 deg.
The center point should not be part of the list of points
You have to distinguish between clockwise and counterclockwise arcs in order to make the list of points that lie on 0,90,180,270 deg
It is tricky to determine if the angle span includes the angle 0,90,180 or 270 deg.
public override Rect Box()
{
List<Point> potentialExtrema = new List<Point>();
potentialExtrema.Add(StartPoint);
potentialExtrema.Add(EndPoint);
if (!ClockWise)
{
if (EndAngle < StartAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360)
potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
if ((StartAngle <= 90 || StartAngle > EndAngle) && EndAngle >= 90)
potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
if ((StartAngle <= 180 || StartAngle > EndAngle) && EndAngle >= 180)
potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
if ((StartAngle <= 270 || StartAngle > EndAngle) && EndAngle >= 270)
potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
}
else
{
if (StartAngle < EndAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360)
potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
if ((StartAngle >= 90 || StartAngle < EndAngle) && EndAngle <= 90)
potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
if ((StartAngle >= 180 || StartAngle < EndAngle) && EndAngle <= 180)
potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
if ((StartAngle >= 270 || StartAngle < EndAngle) && EndAngle <= 270)
potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
}
double maxX = double.NegativeInfinity;
double maxY = double.NegativeInfinity;
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
foreach (var point in potentialExtrema)
{
if (point.X > maxX)
maxX = point.X;
if (point.Y > maxY)
maxY = point.Y;
if (point.X < minX)
minX = point.X;
if (point.Y < minY)
minY = point.Y;
}
return new Rect(minX, minY, maxX - minX, maxY - minY);
}
}
There is a more elegant solution determining wether 0,90,180 or 270 deg lie within the angle span:
public override Rect Box()
{
List<Point> potentialExtrema = new List<Point>();
potentialExtrema.Add(StartPoint);
potentialExtrema.Add(EndPoint);
if (AngleProduct(0))
potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
if (AngleProduct(90))
potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
if (AngleProduct(180))
potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
if (AngleProduct(270))
potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
double maxX = double.NegativeInfinity;
double maxY = double.NegativeInfinity;
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
foreach (var point in potentialExtrema)
{
if (point.X > maxX)
maxX = point.X;
if (point.Y > maxY)
maxY = point.Y;
if (point.X < minX)
minX = point.X;
if (point.Y < minY)
minY = point.Y;
}
return new Rect(minX, minY, maxX - minX, maxY - minY);
}
private bool AngleProduct(int alpha)
{
if (StartAngle == EndAngle)
if (StartAngle == alpha)
return true;
else
return false;
double prod = 0;
if (ClockWise)
prod = -1 * (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle);
else
prod = (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle);
if (prod >= 0)
return true;
else
return false;
}

Resources