I am trying to make an animated 3D wave with Three.js and Points. It is kind of working. It is starting slow and then the applitude is increasing. But after some time it gets too high and unsteady.
This is how it should be looking. However after some time it is loosing it's shape. The problem is the decreasing period of the sine and increasing amplitude. But I am failing to fix it.
Here is some code.
Creating of the points mesh.
this.particleGeometry = new Geometry()
for (let ix = 0; ix < this.WIDTH; ix++) {
for (let iz = 0; iz < this.HEIGHT; iz++) {
let vert = new Vector3()
vert.x = ix * this.SEPERATION - ((this.WIDTH * this.SEPERATION) / 2)
vert.y = (Math.cos((ix / this.WIDTH) * Math.PI * 6) + Math.sin((iz / this.HEIGHT) * Math.PI * 6))
vert.z = iz * this.SEPERATION - ((this.HEIGHT * this.SEPERATION) / 2)
this.particleGeometry.vertices.push(vert)
}
}
this.particleCloud = new Points(this.particleGeometry, this.material)
this.scene.add(this.particleCloud)
The initial generation is pretty good. But the updating is buggy.
animate() code:
render () {
let index = 0
let time = Date.now() * 0.00005
let h = (360 * (1.0 + time) % 360) / 360
this.theta += 0.0008
this.material.color.setHSL(h, 0.5, 0.5)
for (let ix = 0; ix < this.WIDTH; ix++) {
for (let iz = 0; iz < this.HEIGHT; iz++) {
this.particleCloud.geometry.vertices[index].y = (Math.cos((ix * this.theta / this.WIDTH) * Math.PI * 6) + Math.sin((iz * this.theta / this.HEIGHT) * Math.PI * 6))
index++
}
}
this.particleCloud.geometry.verticesNeedUpdate = true
this.updateGuiSettings()
this.renderer.render(this.scene, this.camera)
},
this.theta starts at 0 and then slowly increasing.
Okay, got it working with (Math.cos((ix / this.WIDTH * PI * 8 + this.theta)) + Math.sin((iz / this.HEIGHT * PI * 8 + this.theta)))
Now it is steady. However will tweak it maybe more.
Related
I've been writing a raycaster in C++ and to render stuff I use GDI/GDI+. I know that using WGDI to render graphics is not the best idea in the world and I should probably use OpenGL, SFML and etc. but this raycaster does not involve any super-high-level real-time graphics, so in this case WGDI does the job. Besides I probably will be showing this in my school and installing OpenGL there would be a huge pain.
Okay, so the actual problem I wanted to talk about is that whenever I change the map grid from 8x8 to e.g. 8x16, the way that some walls are rendered is pretty bizzarre:
If someone can explain why such issue occurrs I would be very happy to discover what's wrong with my code.
main.cpp
/*
* Pseudo-code of the void renderer():
* Horizontal gridline check:
* Set horizontal distance to a pretty high value, horizontal coordinates to camera coordinates
* Calculate negative inverse of tangent
* Set DOF variable to 0
* If ray angle is bigger than PI calculate ray Y-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray X-coordinate and offset coordinates for the ray moovement over the gridline
* If ray angle is smaller than PI do the same as if ray angle < PI but add whatever the size of the map is to ray Y-coordinate
* If ray angle is straight up or down set ray coordinates to camera coordinates and DOF to map size
* Loop only if DOF is smaller than map size:
* Calculate actual gridline coordinates
* If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
* Else update ray coordinates with the earlier calculated offsets
*
* Vertical gridline check:
* Set vertical distance to a pretty high value, vertical coordinates to camera coordinates
* Calculate inverse of tangent
* Set DOF variable to 0
* If ray angle is bigger than PI / 2 and smaller than 3 * PI / 2 calculate ray X-coordinate to be as close as possible to the gridline position and subtract 0.0001 for precision, calculate ray Y-coordinate and offset coordinates for the ray moovement over the gridline
* If ray angle is smaller than PI / 2 or bigger than 3 * PI / 2 do the same as if ray angle > PI / 2 && < 3 * PI / 2 but add whatever the size of the map is to ray X-coordinate
* If ray angle is straight left or right set ray coordinates to camera coordinates and DOF to map size
* Loop only if DOF is smaller than map size:
* Calculate actual gridline coordinates
* If the grid cell at [X, Y] is a wall break out from the loop, save the current ray coordinates, calculate the distance between the camera and the wall
* Else update ray coordinates with the earlier calculated offsets
*
* If the vertical distance is smaller than the horizontal one update ray coordinates to the horizontal ones and set final distance to the horizontal one
* Else update ray coordinates to the vertical ones and set final distance to the vertical one
* Fix fisheye effect
* Add one radian to the ray angle
* Calculate line height by multiplying constant integer 400 by the map size and dividing that by the final distance
* Calculate line offset (to make it more centered) by subtracting half of the line height from constant integer 400
* Draw 8-pixels wide column at [ray index * 8, camera Z-offset + line offset] and [ray index * 8, camera Z-offset + line offset + line height] (the color doesn't matter i think)
*/
#include "../../LIB/wsgl.hpp"
#include "res/maths.hpp"
#include <memory>
using namespace std;
const int window_x = 640, window_y = 640;
float camera_x = 256, camera_y = 256, camera_z = 75;
float camera_a = 0.001;
int camera_fov = 80;
int map_x;
int map_y;
int map_s;
shared_ptr<int[]> map_w;
void controls()
{
if(wsgl::is_key_down(wsgl::key::w))
{
int mx = (camera_x + 30 * cos(camera_a)) / map_s;
int my = (camera_y + 30 * sin(camera_a)) / map_s;
int mp = my * map_x + mx;
if(mp >= 0 && mp < map_s && !map_w[mp])
{camera_x += 15 * cos(camera_a); camera_y += 15 * sin(camera_a);}
}
if(wsgl::is_key_down(wsgl::key::s))
{
int mx = (camera_x - 30 * cos(camera_a)) / map_s;
int my = (camera_y - 30 * sin(camera_a)) / map_s;
int mp = my * map_x + mx;
if(mp >= 0 && mp < map_s && !map_w[mp])
{camera_x -= 5 * cos(camera_a); camera_y -= 5 * sin(camera_a);}
}
if(wsgl::is_key_down(wsgl::key::a_left))
{camera_a = reset_ang(camera_a - 5 * RAD);}
if(wsgl::is_key_down(wsgl::key::a_right))
{camera_a = reset_ang(camera_a + 5 * RAD);}
if(wsgl::is_key_down(wsgl::key::a_up))
{camera_z += 15;}
if(wsgl::is_key_down(wsgl::key::a_down))
{camera_z -= 15;}
}
void renderer()
{
int map_x_pos, map_y_pos, map_cell, dof;
float ray_x, ray_y, ray_a = reset_ang(camera_a - deg_to_rad(camera_fov / 2));
float x_offset, y_offset, tangent, distance_h, distance_v, h_x, h_y, v_x, v_y;
float final_distance, line_height, line_offset;
wsgl::clear_window();
for(int i = 0; i < camera_fov; i++)
{
distance_h = 1000000, h_x = camera_x, h_y = camera_y;
tangent = -1 / tan(ray_a);
dof = 0;
if(ray_a > PI)
{ray_y = (((int)camera_y / map_s) * map_s) - 0.0001; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = -map_s; x_offset = -y_offset * tangent;}
if(ray_a < PI)
{ray_y = (((int)camera_y / map_s) * map_s) + map_s; ray_x = (camera_y - ray_y) * tangent + camera_x; y_offset = map_s; x_offset = -y_offset * tangent;}
if(ray_a == 0 || ray_a == PI)
{ray_x = camera_x; ray_y = camera_y; dof = map_s;}
for(dof; dof < map_s; dof++)
{
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
{dof = map_s; h_x = ray_x; h_y = ray_y; distance_h = distance(camera_x, camera_y, h_x, h_y);}
else
{ray_x += x_offset; ray_y += y_offset;}
}
distance_v = 1000000, v_x = camera_x, v_y = camera_y;
tangent = -tan(ray_a);
dof = 0;
if(ray_a > PI2 && ray_a < PI3)
{ray_x = (((int)camera_x / map_s) * map_s) - 0.0001; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = -map_s; y_offset = -x_offset * tangent;}
if(ray_a < PI2 || ray_a > PI3)
{ray_x = (((int)camera_x / map_s) * map_s) + map_s; ray_y = (camera_x - ray_x) * tangent + camera_y; x_offset = map_s; y_offset = -x_offset * tangent;}
if(ray_a == PI2 || ray_a == PI3)
{ray_x = camera_x; ray_y = camera_y; dof = map_s;}
for(dof; dof < map_s; dof++)
{
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
if(map_cell >= 0 && map_cell < map_s && map_w[map_cell])
{dof = map_s; v_x = ray_x; v_y = ray_y; distance_v = distance(camera_x, camera_y, v_x, v_y);}
else
{ray_x += x_offset; ray_y += y_offset;}
}
if(distance_v < distance_h)
{ray_x = v_x; ray_y = v_y; final_distance = distance_v;}
else
{ray_x = h_x; ray_y = h_y; final_distance = distance_h;}
final_distance *= cos(reset_ang(camera_a - ray_a));
ray_a = reset_ang(ray_a + RAD);
line_height = (map_s * 400) / final_distance;
line_offset = 200 - line_height / 2;
wsgl::draw_line({i * 8, camera_z + line_offset}, {i * 8, camera_z + line_offset + line_height}, {0, 255 / (final_distance / 250 + 1), 0}, 8);
if(i == camera_fov / 2)
{wsgl::draw_text({0, 0}, {255, 255, 255}, L"Final distance: " + to_wstring(final_distance) + L" Line height: " + to_wstring(line_height) + L" X: " + to_wstring(camera_x) + L" Y: " + to_wstring(camera_y));}
}
wsgl::render_frame();
}
void load_map(wsgl::wide_str wstr, int cell_size = 1)
{
shared_ptr<wsgl::bmp> map = shared_ptr<wsgl::bmp>(wsgl::bmp::FromFile(wstr.c_str(), true));
map_x = map->GetWidth();
map_y = map->GetHeight();
map_s = map_x * map_y;
map_w = shared_ptr<int[]>(new int[map_s]);
wsgl::color color;
for(int y = 0; y < map_y; y += cell_size)
{
for(int x = 0; x < map_x; x += cell_size)
{
map->GetPixel(x, y, &color);
if(color.GetR() == 255 && color.GetG() == 255 && color.GetB() == 255)
{*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 0;}
else
{*(map_w.get() + ((y / cell_size) * map_x + (x / cell_size))) = 1;}
}
}
}
int main()
{
wsgl::session sess = wsgl::startup(L"raycaster", {window_x, window_y});
load_map(L"res/map.png");
while(true)
{controls(); renderer();}
}
maths.hpp
#include <cmath>
const float PI = 3.14159265359;
const float PI2 = PI / 2;
const float PI3 = 3 * PI2;
const float RAD = PI / 180;
float deg_to_rad(float deg)
{return deg * RAD;}
float distance(float ax, float ay, float bx, float by)
{
float dx = bx - ax;
float dy = by - ay;
return sqrt(dx * dx + dy * dy);
}
float reset_ang(float ang)
{
if(ang < 0)
{ang += 2 * PI;}
if(ang > 2 * PI)
{ang -= 2 * PI;}
return ang;
}
If someone asks whats wsgl.hpp thats just my wrapper library over some WGDI routines and etc.
I think the problem lies here:
map_x_pos = (int)(ray_x) / map_s;
map_y_pos = (int)(ray_y) / map_s;
map_cell = map_y_pos * map_x + map_x_pos;
You need to change the order of operations:
map_x_pos = (int)(ray_x / map_s);
map_y_pos = (int)(ray_y / map_s);
map_cell = map_y_pos * map_x + map_x_pos;
With your current implementation, you first truncate ray_x and ray_y, then divide by map_s (which should probably be a floating point value, but is an integer in your current implementation), then truncate again to integer values. Your current implementation needlessly sacrifices precision and will be unpredictable for small map_s values.
Additionally, map_s seems incorrect. You set map_s to represent the total area of your map, but in the above code, you use it like it was the side length of the map.
To be correct, you would need something like
#include <cmath>
map_x_pos = (int)(ray_x / sqrtf(map_s));
map_y_pos = (int)(ray_y / sqrtf(map_s));
map_cell = map_y_pos * map_x + map_x_pos;
I hope you have an excellent day, it happens that in desktop mode it works perfectly but on mobile devices it only moves on a single axis and also does not detect gyroscopic sensors, the same thing happens in the latest version 1.0.4, which can it be and how can I solve it?
I was checking and not only happens with me, but also with all the examples from the official website and other people's recent projects...
Edit: I found the solution thanks to the first answer, if someone else has the same problem add the following code,
AFRAME.components["look-controls"].Component.prototype.onTouchMove = function (t) {
var PI_2 = Math.PI/2,
e,
o = this.el.sceneEl.canvas,
i = this.yawObject,
j = this.pitchObject;
this.touchStarted && this.data.touchEnabled && (e = 2 * Math.PI * (t.touches[0].pageX - this.touchStart.x) / o.clientWidth, f = 2 * Math.PI * (t.touches[0].pageY - this.touchStart.y) / o.clientHeight, j.rotation.x += .3 * f, i.rotation.y += .5 * e, j.rotation.x = Math.max(-PI_2, Math.min(PI_2, j.rotation.x)), this.touchStart = {
x: t.touches[0].pageX,
y: t.touches[0].pageY
})
}
AFRAME.components["look-controls"].Component.prototype.onTouchMove = function (t) {
var PI_2 = Math.PI/2,
e,
o = this.el.sceneEl.canvas,
i = this.yawObject,
j = this.pitchObject;
this.touchStarted && this.data.touchEnabled && (e = 2 * Math.PI * (t.touches[0].pageX - this.touchStart.x) / o.clientWidth, f = 2 * Math.PI * (t.touches[0].pageY - this.touchStart.y) / o.clientHeight, j.rotation.x += .3 * f, i.rotation.y += .5 * e, j.rotation.x = Math.max(-PI_2, Math.min(PI_2, j.rotation.x)), this.touchStart = {
x: t.touches[0].pageX,
y: t.touches[0].pageY
})
}
I have 3D array (height, width, depth). My global worksize is (height * width * depth). and local work size is 1. In kernel code how I can get row offset and column offset?
I am doing the convolution operation in opencl. In C we do as follow,
// iterating through number of filters
for(c = 0; c < number_of_filters; c++)
{
for(h = 0; h < out_height; h++)
{
for(w = 0; w < out_width; w++)
{
vert_start = h * stride;
vert_end = vert_start + f_size ;
hor_start = w * stride;
hor_end = hor_start + f_size;
sum = 0;
for(c_f = 0; c_f < input_channel; c_f++)
{
for(h_f = vert_start; h_f < vert_end; h_f++)
{
for(w_f = hor_start; w_f < hor_end; w_f++)
{
// computing convolution
sum = sum +
(INPUT[(c_f * input_height * input_width) + (h * input_width) + w] *
FILTER[(c_f * filt_height* filt_width) + (h_f * filt_width) + w_f)]);
}
}
}
// storing result in output
OUTPUT[(c * out_height * out_width) + (h * out_width) + w] = sum;
}
}
}
I am not getting how to get that row offset and column offset from image for convolution in opencl?
calculate_distance(lat1: number, lat2: number, long1: number, long2: number) {
console.log("Inside getting calculate_distance");
console.log(lat1);
console.log(lat2);
console.log(long1);
console.log(long2);
let p = 0.017453292519943295; // Math.PI / 180
let c = Math.cos;
let a = 0.5 - c((lat1 - lat2) * p) / 2 + c(lat2 * p) * c((lat1) * p) * (1 - c(((long1 - long2) * p))) / 2;
let dis = (12742 * Math.asin(Math.sqrt(a))); // 2 * R; R = 6371 km
return dis.toFixed(2);
}
calling this function inside other function and i am not getting the distance value.
i just added a promise return type to my function. So that it will return me the value.
calculateDistance(lat1: number, lat2: number, long1: number, long2: number):Promise<any> {
console.log("Inside getting calculate_distance");
console.log(lat1);
console.log(lat2);
console.log(long1);
console.log(long2);
let p = 0.017453292519943295; // Math.PI / 180
let c = Math.cos;
let a = 0.5 - c((lat1 - lat2) * p) / 2 + c(lat2 * p) * c((lat1) * p) * (1 - c(((long1 - long2) * p))) / 2;
let dis = (12742 * Math.asin(Math.sqrt(a))); // 2 * R; R = 6371 km
return dis.toFixed(2);
}
and called calculateDistance like this
this.calculateDistance(this.my_lat, this.proj_lat, this.my_long, this.proj_long).then(result=>{
this.distance = String(result)
})
Updates
Updated fiddle to simplify what is going on:
added four buttons to move the stick, each button increments the value by 30 in the direction
plotted x and y axis
red line is the stick, with bottom end coordinates at (ax,ay) and top end coordinates at (bx,by)
green line is (presumably) previous position of the stick, with bottom end coordinates at (ax, ay) and top end coordinates at (bx0, by0)
So, after having my ninja moments. I'm still nowhere near understanding the sorcery behind unknownFunctionA and unknownFunctionB
For the sake of everyone (all two of you) here is what I've sort of learnt so far
function unknownFunctionB(e) {
var t = e.b.x - e.a.x
, n = e.b.y - e.a.y
, a = t * t + n * n;
if (a > 0) {
if (a == e.lengthSq)
return;
var o = Math.sqrt(a)
, i = (o - e.length) / o
, s = .5;
e.b.x -= t * i * .5 * s,
e.b.y -= n * i * .5 * s
}
}
In the unknownFunctionB above, variable o is length of the red sitck.
Still don't understand
What is variable i and how is (bx,by) calculated? essentially:
bx = bx - (bx - ax) * 0.5 * 0.5
by = by - (by - ay) * 0.5 * 0.5
In unknownFunctionA what are those magic numbers 1.825 and 0.825?
Below is irrelevant
I'm trying to deconstruct marker drag animation used on smartypins
I've managed to get the relevant code for marker move animation but I'm struggling to learn how it all works, especially 2 functions (that I've named unknownFunctionA and unknownFunctionB)
Heres the StickModel class used on smartypins website, unminified to best of my knowledge
function unknownFunctionA(e) {
var t = 1.825
, n = .825
, a = t * e.x - n * e.x0
, o = t * e.y - n * e.y0 - 5;
e.x0 = e.x,
e.y0 = e.y,
e.x = a,
e.y = o;
}
function unknownFunctionB(e) {
var t = e.b.x - e.a.x
, n = e.b.y - e.a.y
, a = t * t + n * n;
if (a > 0) {
if (a == e.lengthSq)
return;
var o = Math.sqrt(a)
, i = (o - e.length) / o
, s = .5;
e.b.x -= t * i * .5 * s,
e.b.y -= n * i * .5 * s
}
}
function StickModel() {
this._props = function(e) {
return {
length: e,
lengthSq: e * e,
a: {
x: 0,
y: 0
},
b: {
x: 0,
y: 0 - e,
x0: 0,
y0: 0 - e
},
angle: 0
}
}
(60)
}
var radianToDegrees = 180 / Math.PI;
StickModel.prototype = {
pos: {
x: 0,
y: 0
},
angle: function() {
return this._props.angle
},
reset: function(e, t) {
var n = e - this._props.a.x
, a = t - this._props.a.y;
this._props.a.x += n,
this._props.a.y += a,
this._props.b.x += n,
this._props.b.y += a,
this._props.b.x0 += n,
this._props.b.y0 += a
},
move: function(e, t) {
this._props.a.x = e,
this._props.a.y = t
},
update: function() {
unknownFunctionA(this._props.b),
unknownFunctionB(this._props),
this.pos.x = this._props.a.x,
this.pos.y = this._props.a.y;
var e = this._props.b.x - this._props.a.x
, t = this._props.b.y - this._props.a.y
, o = Math.atan2(t, e);
this._props.angle = o * radianToDegrees;
}
}
StickModel.prototype.constructor = StickModel;
Fiddle link with sample implementation on canvas: http://jsfiddle.net/vff1w82w/3/
Again, Everything works as expected, I'm just really curious to learn the following:
What could be the ideal names for unknownFunctionA and unknownFunctionB and an explanation of their functionality
What are those magic numbers in unknownFunctionA (1.825 and .825) and .5 in unknownFunctionB.
Variable o in unknownFunctionB appears to be hypotenuse. If that's the case, then what exactly is i = (o - e.length) / o in other words, i = (hypotenuse - stickLength) / hypotenuse?
First thing I'd recommend is renaming all those variables and methods until they start making sense. I also removed unused code.
oscillator
adds wobble to the Stick model by creating new position values for the Stick that follows the mouse
Exaggerates its movement by multiplying its new position by 1.825 and also subtracting the position of an "echo" of its previous position multiplied by 0.825. Sort of looking for a middle point between them. Helium makes the stick sit upright.
overshooter minus undershooter must equal 1 or you will have orientation problems with your stick. overshooter values above 2.1 tend to make it never settle.
seekerUpdate
updates the seeker according to mouse positions.
The distance_to_cover variable measures the length of the total movement. You were right: hypothenuse (variable o).
The ratio variable calculates the ratio of the distance that can be covered subtracting the size of the stick. The ratio is then used to limit the adjustment of the update on the seeker in both directions (x and y). That's how much of the update should be applied to prevent overshooting the target.
easing slows down the correct updates.
There are lots of interesting info related to vectors on the book The nature of code.
function oscillator(seeker) {
var overshooter = 1.825;
var undershooter = .825;
var helium = -5;
var new_seeker_x = overshooter * seeker.x - undershooter * seeker.echo_x;
var new_seeker_y = overshooter * seeker.y - undershooter * seeker.echo_y + helium;
seeker.echo_x = seeker.x;
seeker.echo_y = seeker.y;
seeker.x = new_seeker_x;
seeker.y = new_seeker_y;
}
function seekerUpdate(stick) {
var dX = stick.seeker.x - stick.mouse_pos.x;
var dY = stick.seeker.y - stick.mouse_pos.y;
var distance_to_cover = Math.sqrt(dX * dX + dY * dY);
var ratio = (distance_to_cover - stick.length) / distance_to_cover;
var easing = .25;
stick.seeker.x -= dX * ratio * easing;
stick.seeker.y -= dY * ratio * easing;
}
function StickModel() {
this._props = function(length) {
return {
length: length,
lengthSq: length * length,
mouse_pos: {
x: 0,
y: 0
},
seeker: {
x: 0,
y: 0 - length,
echo_x: 0,
echo_y: 0 - length
}
}
}(60)
}
StickModel.prototype = {
move: function(x, y) {
this._props.mouse_pos.x = x;
this._props.mouse_pos.y = y;
},
update: function() {
oscillator(this._props.seeker);
seekerUpdate(this._props);
}
};
StickModel.prototype.constructor = StickModel;
// Canvas to draw stick model coordinates
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.width = window.outerWidth;
canvas.height = window.outerHeight;
var canvasCenterX = Math.floor(canvas.width / 2);
var canvasCenterY = Math.floor(canvas.height / 2);
context.translate(canvasCenterX, canvasCenterY);
var stickModel = new StickModel();
draw();
setInterval(function() {
stickModel.update();
draw();
}, 16);
$(window).mousemove(function(e) {
var mouseX = (e.pageX - canvasCenterX);
var mouseY = (e.pageY - canvasCenterY);
stickModel.move(mouseX, mouseY);
stickModel.update();
draw();
});
function draw() {
context.clearRect(-canvas.width, -canvas.height, canvas.width * 2, canvas.height * 2);
// red line from (ax, ay) to (bx, by)
context.beginPath();
context.strokeStyle = "#ff0000";
context.moveTo(stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.lineTo(stickModel._props.seeker.x, stickModel._props.seeker.y);
context.fillText('mouse_pos x:' + stickModel._props.mouse_pos.x + ' y: ' + stickModel._props.mouse_pos.y, stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.fillText('seeker x:' + stickModel._props.seeker.x + ' y: ' + stickModel._props.seeker.y, stickModel._props.seeker.x - 30, stickModel._props.seeker.y);
context.lineWidth = 1;
context.stroke();
context.closePath();
// green line from (ax, ay) to (bx0, by0)
context.beginPath();
context.strokeStyle = "#00ff00";
context.moveTo(stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.lineTo(stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y);
context.fillText('echo x:' + stickModel._props.seeker.echo_x + ' y: ' + stickModel._props.seeker.echo_y, stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y - 20);
context.lineWidth = 1;
context.stroke();
context.closePath();
// blue line from (bx0, by0) to (bx, by)
context.beginPath();
context.strokeStyle = "#0000ff";
context.moveTo(stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y);
context.lineTo(stickModel._props.seeker.x, stickModel._props.seeker.y);
context.stroke();
context.closePath();
}
body {
margin: 0px;
padding: 0px;
}
canvas {
display: block;
}
p {
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<p>Move your mouse to see the stick (colored red) follow</p>
<canvas id="myCanvas"></canvas>