I'm trying to create a Toroid mesh, however I cant create the faces properly...
here is my code:
public static Group createToroidMesh(float radius, float tRadius, int tDivs, int rDivs) {
final Group root = new Group();
int numVerts = tDivs * rDivs;
float[] points = new float[numVerts * POINT_SIZE],
texCoords = new float[numVerts * TEXCOORD_SIZE];
int[] faces = new int[numVerts * FACE_SIZE],
smoothingGroups;
int pointIndex = 0, texIndex = 0, faceIndex = 0, smoothIndex = 0;
float tFrac = 1.0f / tDivs;
float rFrac = 1.0f / rDivs;
float x, y, z;
int p0 = 0, p1 = 0, p2 = 0, p3 = 0, t0 = 0, t1 = 0, t2 = 0, t3 = 0;
// create points
for (int vertIndex = 0; vertIndex < tDivs; vertIndex++) {
float radian = tFrac * vertIndex * 2.0f * 3.141592653589793f;
for (int crossSectionIndex = 0; crossSectionIndex < rDivs; crossSectionIndex++) {
float localRadian = rFrac * crossSectionIndex * 2.0f * 3.141592653589793f;
points[pointIndex] = x = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.cos(localRadian));
points[pointIndex + 1] = y = (radius + tRadius * ((float) Math.cos(radian))) * ((float) Math.sin(localRadian));
points[pointIndex + 2] = z = (tRadius * (float) Math.sin(radian));
pointIndex += 3;
float r = crossSectionIndex < tDivs ? tFrac * crossSectionIndex * 2.0F * 3.141592653589793f : 0.0f;
texCoords[texIndex] = (0.5F + (float) (Math.sin(r) * 0.5D));;
texCoords[texIndex + 1] = ((float) (Math.cos(r) * 0.5D) + 0.5F);
texIndex += 2;
}
}
//create faces
for (int y1 = 0; y1 < tDivs - 1 ; y1++) {
float radian = tFrac * y1 * 2.0f * 3.141592653589793f;
for (int x1 = 0; x1 < rDivs - 1; x1++) {
float localRadian = rFrac * x1 * 2.0f * 3.141592653589793f;
p0 = y1 * rDivs + x1;
p1 = p0 + 1;
p2 = p0 + rDivs;
p3 = p2 + 1;
t0 = y1 * rDivs + x1;
t1 = t0 + 1;
t2 = t0 + rDivs;
t3 = t1 + 1;
try {
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p0);
faces[faceIndex + 3] = (t2);
faces[faceIndex + 4] = (p1);
faces[faceIndex + 5] = (t0);
faceIndex += FACE_SIZE;
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p1);
faces[faceIndex + 3] = (t0);
faces[faceIndex + 4] = (p3);
faces[faceIndex + 5] = (t1);
//faceIndex += FACE_SIZE;
} catch (Exception e) {
break;
}
}
}
TriangleMesh localTriangleMesh = new TriangleMesh();
localTriangleMesh.getPoints().setAll(points);
localTriangleMesh.getTexCoords().setAll(texCoords);
localTriangleMesh.getFaces().setAll(faces);
MeshView view = new MeshView(localTriangleMesh);
view.setMaterial(new PhongMaterial(Color.BLUEVIOLET));
view.setCullFace(CullFace.BACK);
root.getChildren().clear();
root.getChildren().add(view);
//return localTriangleMesh;
System.out.println("objs in group: " + root.getChildren().size()
+ ", \nnum of points in array: " + points.length / POINT_SIZE
+ ", \nnum TexCoords: " + texCoords.length / TEXCOORD_SIZE
+ ", \nface count: " + faces.length / FACE_SIZE);
root.setRotationAxis(Rotate.X_AXIS);
root.setRotate(90);
return root;
}
and here is the result:
can someone help me iterate through this a little better?
my constructor for the mesh is : Group verts = MeshUtils.createToroidMesh(100,15,4,4);
OK, I figured the problem out... The problem was in my faces loop. It was adding values outside the range of points, and texCoords, here is the corrected loop:
//create faces
for (int point = 0; point < (tubeDivisions) ; point++) {
for (int crossSection = 0; crossSection < (radiusDivisions) ; crossSection++) {
p0 = point * radiusDivisions + crossSection;
p1 = p0 >= 0 ? p0 + 1 : p0 - (radiusDivisions);
p1 = p1 % (radiusDivisions) != 0 ? p0 + 1 : p0 - (radiusDivisions - 1);
p2 = (p0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? p0 + radiusDivisions : p0 - (tubeDivisions * radiusDivisions) + radiusDivisions ;
p3 = p2 < ((tubeDivisions * radiusDivisions) - 1) ? p2 + 1 : p2 - (tubeDivisions * radiusDivisions) + 1;
p3 = p3 % (radiusDivisions) != 0 ? p2 + 1 : p2 - (radiusDivisions - 1);
t0 = point * (radiusDivisions) + crossSection;
t1 = t0 >= 0 ? t0 + 1 : t0 - (radiusDivisions);
t1 = t1 % (radiusDivisions) != 0 ? t0 + 1 : t0 - (radiusDivisions - 1);
t2 = (t0 + radiusDivisions) < ((tubeDivisions * radiusDivisions)) ? t0 + radiusDivisions : t0 - (tubeDivisions * radiusDivisions) + radiusDivisions ;
t3 = t2 < ((tubeDivisions * radiusDivisions) - 1) ? t2 + 1 : t2 - (tubeDivisions * radiusDivisions) + 1;
t3 = t3 % (radiusDivisions) != 0 ? t2 + 1 : t2 - (radiusDivisions - 1);
try {
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p0);
faces[faceIndex + 3] = (t2);
faces[faceIndex + 4] = (p1);
faces[faceIndex + 5] = (t0);
faceIndex += FACE_SIZE;
faces[faceIndex] = (p2);
faces[faceIndex + 1] = (t3);
faces[faceIndex + 2] = (p1);
faces[faceIndex + 3] = (t0);
faces[faceIndex + 4] = (p3);
faces[faceIndex + 5] = (t1);
faceIndex += FACE_SIZE;
} catch (Exception e) {
e.printStackTrace();
}
//System.out.println(" :: " +p0 + " : " + p1 + " : " + p2 + " : " + p3);
}
}
and the results: (square toroid)
(Round toroid)
Hope this can help if you struggle as well!
Please Note I did not assign UV textures as they should have been(if using Image material) since Only Color is used. TexCoords can be anything (0 - 1.0) if only using colors
Related
T = 0.2;
A = [1 T; 0 1];
B = [T^2 / 2 T];
H = [1 0];
G = [0 1]';
Q = 0.00005;
R = 0.006;
x1(1) = 0;
x2(1) = 0;
x1e(1) = 0;
x2e(1) = 0;
xest = [x1e(1) x2e(1)]';
x1p(1) = 0;
x2p(1) = 0;
PE = [R 0; 0 0];
PP = A * PE(1) * A' + Q;
for i= 1:25
if i < 10
u = 0.25;
else
u = 0;
end
x1(i+1) = x1(i) + T * x2(i) + (T^2 / 2) * u;
x2(i+1) = x2(i) + T * u + sqrt(Q) * randn;
y(i+1) = x1(i+1) + sqrt(R) * randn;
PP = A * PE * A' + G * Q * G';
K = PP * H' * inv(H * PP * H' + R);
PE = [eye(2) - K * H] * PP;
xpredict = A * xest + B * u;
xest = xpredict + K * (y(i+1) -H * xpredict);
x1e(i+1) = [1 0] * xest;
x2e(i+1) = [0 1] * xest;
end
Unable to perform assignment because the left and right sides have a different number of elements.
Error in rrrr (line 34)
x1e(i+1) = [1 0] * xest;
how i can solve the error
xpredict must be a 2x1 vector. To solve it, you need to transpose B in line 5 i.e., B = [T^2 / 2 T]',
Since from Newton's laws of motion with constant velocity, we have
Hello guys I would like to "straighten" some contours using opencv/python. Is there anyway to accomplish this?
I have attached two images:
in the current stage
and how I would like to see it .
Bounding boxes resolve the majority of the problem, but there are some exceptions that do not produce the desired outcome (see the top right contour in the image).
Thank you very much!
Current Contours
Squared Contours
def approximate_contours(image: np.ndarray, eps: float, color=(255, 255, 255)):
contours, _ = cv.findContours(image, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)
approx_contours = []
for cnt in contours:
epsilon = eps * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
cv.drawContours(image, [approx], -1, color=color)
approx_contours.append(approx)
return image, approx_contours
def get_angle(pts: np.ndarray):
a = np.array([pts[0][0][0], pts[0][0][1]])
b = np.array([pts[1][0][0], pts[1][0][1]])
c = np.array([pts[2][0][0], pts[2][0][1]])
ba = a - b
bc = c - b
unit_vector_ba = ba / np.linalg.norm(ba)
unit_vector_bc = bc / np.linalg.norm(bc)
dot_product = np.dot(unit_vector_ba, unit_vector_bc)
angle_rad = np.arccos(dot_product)
angle_deg = degrees(angle_rad)
try:
int(angle_deg)
except Exception as e:
raise Exception("nan value detected")
return int(angle_deg)
def move_points(contour:np.ndarray, pts: np.ndarray, angle: int, ext: list, weight=1):
(ext_left, ext_right, ext_bot, ext_top) = ext
a = np.array([pts[0][0][0], pts[0][0][1]])
b = np.array([pts[1][0][0], pts[1][0][1]])
c = np.array([pts[2][0][0], pts[2][0][1]])
right_angle = False
if 45 < angle < 135:
right_angle = True
diff_x_ba = abs(b[0] - a[0])
diff_y_ba = abs(b[1] - a[1])
diff_x_bc = abs(b[0] - c[0])
diff_y_bc = abs(b[1] - c[1])
rap_ba = diff_x_ba / max(diff_y_ba, 1)
rap_bc = diff_x_bc / max(diff_y_bc, 1)
if rap_ba < rap_bc:
a[0] = int((a[0] * weight + b[0]) / (2 + weight - 1))
b[0] = a[0]
c[1] = int((c[1] + b[1]) / 2)
b[1] = c[1]
else:
c[0] = int((c[0] + b[0]) / 2)
b[0] = c[0]
a[1] = int((a[1] * weight + b[1]) / (2 + weight - 1))
b[1] = a[1]
else:
diff_x_ba = abs(b[0] - a[0])
diff_y_ba = abs(b[1] - a[1])
diff_x_bc = abs(b[0] - c[0])
diff_y_bc = abs(b[1] - c[1])
if (diff_x_ba + diff_x_bc) > (diff_y_ba + diff_y_bc):
a[1] = int((a[1] * weight + b[1] + c[1]) / (3 + weight - 1))
b[1] = a[1]
c[1] = a[1]
else:
a[0] = int((a[0] * weight + b[0] + c[0]) / (3 + weight - 1))
b[0] = a[0]
c[0] = a[0]
return a, b, c, right_angle
def straighten_contours(contours: list, image: np.ndarray, color=(255, 255, 255)):
image = cv.cvtColor(image, cv.COLOR_GRAY2BGR)
for cnt in contours:
idx = 0
ext_left = cnt[cnt[:, :, 0].argmin()][0]
ext_right = cnt[cnt[:, :, 0].argmax()][0]
ext_top = cnt[cnt[:, :, 1].argmin()][0]
ext_bot = cnt[cnt[:, :, 1].argmax()][0]
while idx != int(cnt.size / 2):
try:
angle = get_angle(cnt[idx:idx + 3])
except Exception:
idx += 1
continue
(a, b, c, right_angle) = move_points(cnt, cnt[idx:idx + 3], angle, [ext_left, ext_right, ext_bot, ext_top])
cnt[idx][0] = a
cnt[idx + 1][0] = b
cnt[idx + 2][0] = c
idx += 1
if not right_angle:
idx -= 1
cnt = np.delete(cnt, (idx + 1), 0)
if idx == 1:
cnt = np.append(cnt, cnt[:2], axis=0)
cnt = np.delete(cnt, [0, 1], 0)
cv.drawContours(image, [cnt], -1, color=color)
return image
I managed to do some workarounds. The straighten contours function is applied onto the approximate_contours result (the first image in the question). Is not as good as I would have wanted it to be but it works.
Consider the following code.
Wx = zeros(N, N);
for ii = 1 : 1 : N
x_ref = X(ii); y_ref = Y(ii);
nghlst_Local = nghlst(ii, find(nghlst(ii, :))); Nl = length(nghlst_Local);
x_Local = X(nghlst_Local, 1); y_Local = Y(nghlst_Local, 1);
PhiU = ones(Nl+1, Nl+1); PhiU(end, end) = 0;
Phi = ones(Nl+1, Nl+1); Phi(end, end) = 0;
Bx = zeros(Nl+1,1);
for jj = 1 : 1 : Nl
for kk = 1 : 1 : Nl
rx = x_Local(jj,1) - x_Local(kk,1);
ry = y_Local(jj,1) - y_Local(kk,1);
PhiU(jj, kk) = (1 - U(1,1))) / sqrt(rx^2 + ry^2 + c^2);
end
rx = x_ref - x_Local(jj);
ry = y_ref - y_Local(jj);
Bx(jj, 1) = ( (Beta * pi * U(1,1)/(2*r_0*norm(U))) * cos( (pi/2) * (-rx * U(1,1) - ry * U(2,1)) / (r_0 * norm(U)) ) ) / sqrt(rx^2 + ry^2 + c^2) - rx * (1 - Beta * sin( (pi/2) * (-rx * U(1,1) - ry * U(2,1)) / (r_0 * norm(U)) ))/ (rx^2 + ry^2 + c^2)^(3/2);
end
invPhiU = inv(PhiU);
CX = Bx' * invPhiU; CX = CX (1, 1:end-1); Wx (ii, nghlst_Local) = CX;
end
I want to convert the first for loop into parfor loop. The rest of the code works fine, but the following assignment statement does not work when I change for to parfor.
Wx (ii, nghlst_Local) = CX;
I want to know what is this is wrong and how to remove such errors. Thank you.
public double Sigmoid(double x)
{
return 2 / (1 + Math.Exp(-2 * x)) - 1;
}
public double Derivative(double x)
{
double s = Sigmoid(x) - (Sigmoid(x)* Sigmoid(x));
return s;
}
When i train the network it is giving output:
0,0 = 0 it is always 0 //I dont know
0,1 = 0,67 and it is going up //good but after 1000 repets it gets to
0.20 and it is goind down
1,0 = 0.50 and it is going up //good but after 1000 repets it gets to
0.20 and it is goind down
1,1 = 0.80 and it is going up //wrong it should go down.
Where is the mistake?
Neural network (XOR and back propagation)
int pw = Convert.ToInt32(textBox1.Text);
for (int i12 = 0; i12 < pw; i12++)
{
//i1 = Convert.ToDouble(textBox2.Text);
// i2 = Convert.ToDouble(textBox3.Text);
// desired = Convert.ToDouble(textBox1.Text);
for (int i = 0; i < 4; i++)
{
if (i == 0)
{
i1 = 1;
i2 = 1;
desired = 0;
}
else if (i == 1)
{
i1 = 1;
i2 = 0;
desired = 1;
}
else if (i == 2)
{
i1 = 0;
i2 = 1;
desired = 1;
}
else if (i == 3)
{
i1 = 0;
i2 = 0;
desired = 0;
}
// double[] questions = new double[2];
// questions[0] = 1;
// questions[1] = 0;
// Random rnd = new Random();
// double s = questions[rnd.Next(0, 2)];
// double s1 = questions[rnd.Next(0, 2)];
// i1 = s;
// i2 = s1;
//hidden layer hidden values
h1 = i1 * w1 + i2 * w2; //i1*w1+i2*w2
h2 = i1 * w3 + i2 * w4;//i1*w3+i2*w4
h3 = i1 * w5 + i2 * w6;//i1*w5+i2*w6;
//hidden layer hidden values
//VALUE OF HIDDEN LAYER
h1v = Sigmoid(h1);
h2v = Sigmoid(h2);
h3v = Sigmoid(h3);
//VALUE OF HIDDEN LAYER
//output final
output = h1v * w7 + h2v * w8 + h3v * w9;
outputS = Sigmoid(output);
//output final
//BACKPROPAGATION
//MARGIN ERROR
Error = desired - outputS; //desired-cena jaka ma byc OutputS-zgadnienta cena
//Margin Error
//DElta output sum
deltaoutputsum = Derivative(output) * Error * 0.05; //output bez sigmoida i error
//Delta output sum
//weight of w7,w8,w9.
w7b = w7; //0.3
w8b = w8; // 0.5
w9b = w9;// 0.9
w7 = w7 + deltaoutputsum * h1v; //waga w7
w8 = w8 + deltaoutputsum * h2v; //waga w8
w9 = w9 + deltaoutputsum * h3v; //waga w9
//weights of w7,w8,w9.
//DELTA HIDDEN SUm
h1 = deltaoutputsum * w7b * Derivative(h1);
h2 = deltaoutputsum * w8b * Derivative(h2);
h3 = deltaoutputsum * w9b * Derivative(h3);
//DELTA HIDDEN SUM
//weights 1,2,3,4,5,6
w1 = w1 - h1 * i1;
w2 = w2 - h1 * i2;
w3 = w3 - h2 * i1;
w4 = w4 - h2 * i2;
w5 = w5 - h3 * i1;
w6 = w6 - h3 * i2;
Why after training it give:
1.0 == close to 0, should be close to 1
1.1 == close to 1,should be 0
0.0 == it is good, close to 0
0.1 == close to 0,should be close to 1
This is the code to use after training(i1 and i1 are inputs 1 or 0 )
i1 = Convert.ToDouble(textBox4.Text);
i2 = Convert.ToDouble(textBox5.Text);
//hidden layer hidden values
h1 = i1 * w1 + i2 * w2; //i1*w1+i2*w2
h2 = i1 * w3 + i2 * w4;//i1*w3+i2*w4
h3 = i1 * w5 + i2 * w6;//i1*w5+i2*w6;
//hidden layer hidden values
//VALUE OF HIDDEN LAYER
h1v = Sigmoid(h1);
h2v = Sigmoid(h2);
h3v = Sigmoid(h3);
//VALUE OF HIDDEN LAYER
//output final
output = h1v * w7 + h2v * w8 + h3v * w9;
outputS = Sigmoid(output);
MessageBox.Show(outputS.ToString());
w1-w10 are weights. h1v are valuse of hidden layers. h1 are weights of hidden layers
I'm a iOS game developer and I saw an interesting physics & draw game "Sugar, Sugar" recently. In the game, there are lots of pixel particles (thousands of them) generated from the screen and free falling to the ground. Player can draw any shape of lines, which can guide those particles to certain cups. A image from google:
I'm trying to achieve similar effect using SpriteKit with Swift. Here's what I got:
Then I encounter a performance problem. Once the number of particles > 100. The CPU and energy costs are very high. (I use iPhone 6s). So I believe the Physics Engine in "Sugar, Sugar" is much simpler than the realistic SpriteKit. But I don't know what's the physics engine there and how can I achieve this in SpriteKit?
PS:
I use one single image as texture for all those particles, only loaded once to save performance. I only use SKSpriteNode, no ShapeNode is used for performance reason too.
I have not done a sand sim for a long time so I thought I would create a quick demo for you.
It is done in javascript, left mouse adds sand, right mouse draws lines. Depending on the machine it will handle thousands of grains of sand.
It works by creating an array of pixels, each pixel has a x,y position a delta x,y and a flag to indicate it is inactive (dead). Every frame I clear the display and then add the walls. Then for each pixel I check if there are pixels to the sides or below (depending on the direction of movement) and add sideways slippage, bounce of wall, or gravity. If a pixel has not moved for some time I set it as dead and only draw it to save time on the calculations.
The sim is very simple, the first pixel (grain) will never bump into another because it is drawn with a clear display, pixels can only see pixels created before them. But this works well as they self organize and will not overlap each other.
You can find the logic in the function display, (second function from bottom) there is some code for auto demo, then code for drawing the walls, displaying the walls, getting the pixel data and then doing the sim for each pixel.
Its not perfect (like the game you have mentioned) but it is just a quick hack to show how it is done. Also I made it to big for the inset window so best viewed full page.
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.width = cs.height = "100%";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = Math.floor(window.innerWidth/4);
canvas.height = Math.floor(window.innerHeight/4);
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
var simW = 200;
var simH = 200;
var wallCanvas = document.createElement("canvas");
wallCanvas.width = simW;
wallCanvas.height = simH;
var wallCtx = wallCanvas.getContext("2d");
var bounceDecay = 0.7;
var grav = 0.5;
var slip = 0.5;
var sandPerFrame = 5;
var idleTime = 50;
var pixels = [];
var inactiveCounter = 0;
var demoStarted;
var lastMouse;
var wallX;
var wallY;
function display(){ // Sim code is in this function
var blocked;
var obstructed;
w = canvas.width;
h = canvas.height;
var startX = Math.floor(w / 2) - Math.floor(simW / 2);
var startY = Math.floor(h / 2) - Math.floor(simH / 2);
if(lastMouse === undefined){
lastMouse = mouse.x + mouse.y;
}
if(lastMouse === mouse.x + mouse.y){
inactiveCounter += 1;
}else{
inactiveCounter = 0;
}
if(inactiveCounter > 10 * 60){
if(demoStarted === undefined){
wallCtx.beginPath();
var sy = simH / 6;
for(var i = 0; i < 4; i ++){
wallCtx.moveTo(simW * (1/6) - 10,sy * i + sy * 1);
wallCtx.lineTo(simW * (3/ 6) - 10,sy * i + sy * 2);
wallCtx.moveTo(simW * (5/6) + 10,sy * i + sy * 0.5);
wallCtx.lineTo(simW * (3/6) +10,sy * i + sy * 1.5);
}
wallCtx.stroke();
}
mouse.x = startX * 4 + (simW * 2);
mouse.y = startY * 4 + (simH * 2 )/5;
lastMouse = mouse.x + mouse.y;
mouse.buttonRaw = 1;
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
ctx.strokeRect(startX+1,startY+1,simW-2,simH-2)
ctx.drawImage(wallCanvas,startX,startY); // draws the walls
if(mouse.buttonRaw & 4){ // if right button draw walls
if(mouse.x/4 > startX && mouse.x/4 < startX + simW && mouse.y/4 > startY && mouse.y/4 < startY + simH){
if(wallX === undefined){
wallX = mouse.x/4 - startX
wallY = mouse.y/4 - startY
}else{
wallCtx.beginPath();
wallCtx.moveTo(wallX,wallY);
wallX = mouse.x/4 - startX
wallY = mouse.y/4 - startY
wallCtx.lineTo(wallX,wallY);
wallCtx.stroke();
}
}
}else{
wallX = undefined;
}
if(mouse.buttonRaw & 1){ // if left button add sand
for(var i = 0; i < sandPerFrame; i ++){
var dir = Math.random() * Math.PI;
var speed = Math.random() * 2;
var dx = Math.cos(dir) * 2;
var dy = Math.sin(dir) * 2;
pixels.push({
x : (Math.floor(mouse.x/4) - startX) + dx,
y : (Math.floor(mouse.y/4) - startY) + dy,
dy : dx * speed,
dx : dy * speed,
dead : false,
inactive : 0,
r : Math.floor((Math.sin(globalTime / 1000) + 1) * 127),
g : Math.floor((Math.sin(globalTime / 5000) + 1) * 127),
b : Math.floor((Math.sin(globalTime / 15000) + 1) * 127),
});
}
if(pixels.length > 10000){ // if over 10000 pixels reset
pixels = [];
}
}
// get the canvas pixel data
var data = ctx.getImageData(startX, startY,simW,simH);
var d = data.data;
// handle each pixel;
for(var i = 0; i < pixels.length; i += 1){
var p = pixels[i];
if(!p.dead){
var ind = Math.floor(p.x) * 4 + Math.floor(p.y) * 4 * simW;
d[ind + 3] = 0;
obstructed = false;
p.dy += grav;
var dist = Math.floor(p.y + p.dy) - Math.floor(p.y);
if(Math.floor(p.y + p.dy) - Math.floor(p.y) >= 1){
if(dist >= 1){
bocked = d[ind + simW * 4 + 3];
}
if(dist >= 2){
bocked += d[ind + simW * 4 * 2 + 3];
}
if(dist >= 3){
bocked += d[ind + simW * 4 * 3 + 3];
}
if(dist >= 4){
bocked += d[ind + simW * 4 * 4 + 3];
}
if( bocked > 0 || p.y + 1 > simH){
p.dy = - p.dy * bounceDecay;
obstructed = true;
}else{
p.y += p.dy;
}
}else{
p.y += p.dy;
}
if(d[ind + simW * 4 + 3] > 0){
if(d[ind + simW * 4 - 1] === 0 && d[ind + simW * 4 + 4 + 3] === 0 ){
p.dx += Math.random() < 0.5 ? -slip/2 : slip/2;
}else
if(d[ind + 4 + 3] > 0 && d[ind + simW * 4 - 1] === 0 ){
p.dx -= slip;
}else
if(d[ind - 1] + d[ind - 1 - 4] > 0 ){
p.dx += slip/2;
}else
if(d[ind +3] + d[ind + 3 + 4] > 0 ){
p.dx -= slip/2;
}else
if(d[ind + 1] + d[ind + 1] > 0 && d[ind + simW * 4 + 3] > 0 && d[ind + simW * 4 + 4 + 3] === 0 ){
p.dx += slip;
}else
if(d[ind + simW * 4 - 1] === 0 ){
p.dx += -slip/2;
}else
if(d[ind + simW * 4 + 4 + 3] === 0 ){
p.dx += -slip/2;
}
}
if(p.dx < 0){
if(Math.floor(p.x + p.dx) - Math.floor(p.x) <= -1){
if(d[ind - 1] > 0){
p.dx = -p.dx * bounceDecay;
}else{
p.x += p.dx;
}
}else{
p.x += p.dx;
}
}else
if(p.dx > 0){
if(Math.floor(p.x + p.dx) - Math.floor(p.x) >= 1){
if(d[ind + 4 + 3] > 0){
p.dx = -p.dx * bounceDecay;
}else{
p.x += p.dx;
}
}else{
p.x += p.dx;
}
}
var ind = Math.floor(p.x) * 4 + Math.floor(p.y) * 4 * simW;
d[ind ] = p.r;
d[ind + 1] = p.g;
d[ind + 2] = p.b;
d[ind + 3] = 255;
if(obstructed && p.dx * p.dx + p.dy * p.dy < 1){
p.inactive += 1;
if(p.inactive > idleTime){
p.dead = true;
}
}
}else{
var ind = Math.floor(p.x) * 4 + Math.floor(p.y) * 4 * simW;
d[ind ] = p.r;
d[ind + 1] = p.g;
d[ind + 2] = p.b;
d[ind + 3] = 255;
}
}
ctx.putImageData(data,startX, startY);
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
// continue until mouse right down
if (!(mouse.buttonRaw & 2)) { requestAnimationFrame(update); } else { done(); }
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
* { font-family: arial; }
canvas { image-rendering: pixelated; }
<p>Right click drag to draw walls</p>
<p>Left click hold to drop sand</p>
<p>Demo auto starts in 10 seconds is no input</p>
<p>Sim resets when sand count reaches 10,000 grains</p>
<p>Middle button quits sim</p>