CSS3D matrix generation - javascript

I am trying to achieve following effect using css3 and javascript when we move mouse to center div (MouseOver effect)
I have created small library which accepts 3 arguments element,sourcePoints,destination points and returns css3D matrix and update element. here is my javascript code.
var BLEND = BLEND || {};
BLEND.Util = BLEND.Util || {};
function print(msg) {
console.log(msg);
}
BLEND.Util.VendorPrefix = "";
BLEND.Util.DetectVendorPrefix = function() {
var styles = window.getComputedStyle(document.documentElement, ''),
pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1];
BLEND.Util.VendorPrefix = pre[0].toUpperCase() + pre.substr(1) + "Transform";
}
BLEND.Util.DetectVendorPrefix();
BLEND.TransformElement = function(elm, src, dest) {
var L = [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]];
var R = [0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < 4; i++) {
L[i] = [];
L[i][0] = L[i + 4][3] = src[i].x;
L[i][2] = L[i + 4][4] = src[i].y;
L[i][2] = L[i + 4][5] = 1;
L[i][3] = L[i][4] = L[i][5] = L[i + 4][0] = L[i + 4][3] = L[i + 4][2] = 0;
L[i][6] = -src[i].x * dest[i].x;
L[i][7] = -src[i].y * dest[i].x;
L[i + 4][6] = -src[i].x * dest[i].y;
L[i + 4][7] = -src[i].y * dest[i].y;
R[i] = dest[i].x;
R[i + 4] = dest[i].y;
}
var RM = [];
for (i = 0; i < R.length; i++) {
RM[i] = [R[i]];
}
var Left = Matrix.create(L);
var Right = Matrix.create(R);
var res = Matrix.calculate(Left, Right);
print (res);
if (BLEND.Util.VendorPrefix == 'WebkitTransform') {
var matrix3D = new CSSMatrix();
matrix3D.m11 = res.get(0,0);
matrix3D.m12 = res.get(3,0);
matrix3D.m13 = 0;
matrix3D.m14 = res.get(6,0);
matrix3D.m21 = res.get(1,0);
matrix3D.m22 = res.get(4,0);
matrix3D.m23 = 0;
matrix3D.m24 = res.get(7,0);
matrix3D.m31 = 0;
matrix3D.m32 = 0;
matrix3D.m33 = 1;
matrix3D.m34 = 0;
matrix3D.m41 = res.get(2,0);
matrix3D.m42 = res.get(5,0);
matrix3D.m43 = 0;
matrix3D.m44 = 1;
elm.style.webkitTransform = matrix3D;
} else {
if (BLEND.Util.VendorPrefix === "")
BLEND.Util.DetectVendorPrefix();
elm.style[BLEND.Util.VendorPrefix] = "matrix3d(" + res.get(0,0) + "," + res.get(3,0) + ", 0," + res.get(6,0) + "," + res.get(1,0) + "," + res.get(4,0) + ", 0," + res.get(7,0) + ",0, 0, 1, 0," + res.get(2,0) + "," + res.get(5,0) + ", 0, 1)";
}
}
UPDATE: Here is JSFiddle
I am calling TransformElement method for each of 9 div with proper source and destination coordinates. But its not working as expected. Please suggest the possible solution.
can we do it using three.js in anyway (just asking may be its silly idea)?
UPDATE: Can we do it with CSS3D renderer and Three.js.
Idea is to create plane and slice it in 3x3 grid and on mouse over of each face of plane we can scale that div upside and respectivly we have to scale others div according to current div? Is it possible?

I didn't try to use your library but here is my solution to your problem: http://codepen.io/bali_balo/pen/87b980a2acf722b1c9feb35f3fcb1c65/ (I used Haml and SCSS, you can see compiled code by clicking the little eye on the top right corner of each panel)
I used this article to write this code.
9 matrices are computed first (corresponding to the hovered tile and every surrounding ones), using numericjs and fomulas available on the article linked before. Then on mouseover these matrices are applied to the corresponding tiles. Here is the code to get the transform matrix knowing the locations of 4 points before and after transformation:
//from and to are arrays of 4 objects containing a property x and a property y
//describing the 4 points of the quadrilateral to transform
function getTransform(from, to)
{
var A = [], i;
for(i = 0; i < 4; i++)
{
A.push([from[i].x, from[i].y, 1, 0, 0, 0, -from[i].x * to[i].x, -from[i].y * to[i].x]);
A.push([0, 0, 0, from[i].x, from[i].y, 1, -from[i].x * to[i].y, -from[i].y * to[i].y]);
}
var b = [];
for(i = 0; i < 4; i++)
{
b.push(to[i].x);
b.push(to[i].y);
}
//Uses numericjs to solve matrices equations
//returns h knowing A and b where A.h = b
var h = numeric.solve(A, b);
//Column major representation
return [[h[0], h[3], 0, h[6]],
[h[1], h[4], 0, h[7]],
[ 0 , 0 , 1, 0 ],
[h[2], h[5], 0, 1 ]];
}
Note that, as mentionned in my code, if you want to animate transitions, you can't just use CSS (as it would just transition each number of the matrix, but this would rarely give an appropriate result). You could try doing it using javascript but it might be a bit slow (I didn't test it), because you could not cache matrices and would have to compute them at every frame.

Related

2D array replacing values according conditions

I got a 10 x 10 array with the following values:
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
In this 2D array, I have to randomly choose six values of 0, either horizontally or vertically (also random) and replace them with the value 6.
I've done this with the following code:
function tekenGrootstSchip(bord) {
rand = Math.floor((Math.random() * 10));
rand2 = Math.floor((Math.random() * 10));
directie = Math.floor((Math.random() * 2));
counter = 0;
if (directie == 0) {
for(y = rand2; y < 10; y++) {
if(counter < 6) {
bord[rand][y] = 6;
counter++;
}
}
for(y = rand2; y > 0; y--) {
if(counter < 6) {
bord[rand][y] = 6;
counter++;
}
}
} else {
for(x = rand; x < 10; x++) {
if(counter < 6) {
bord[x][rand2] = 6;
counter++;
}
}
for(x = rand; x > 0; x--) {
if(counter < 6) {
bord[x][rand2] = 6;
counter++;
}
}
}
}
After doing this for the value 6, I also have to do this for the value 4. The rules for value 4 are a bit different however. You can't place a 4 on a 6, neither can you place a 4 next to a 6. And the value 4 only takes four places (so 4x1, while a 6 is 6x1)
So if my randomly generated direction is horizontal, my bord[x-1][y], bord[x][y] and bord[x+1][y], with x and y initialized at the random value, with y going up to y+1, y+2, y+3, y+4 (4x1). All these values have to be checked against == 0, if true, replacing the zeroes with fours can be initialized otherwise not. If so, I have to generate a new [x][y] and check these conditions again until I can change four zeroes in my 2D array succesfully.
Although I have a general idea of implementing this, I would have a bug that, if for instance one null-value would be replaced with a four, but the one next to it can't be replaced since it's next to a != 0 value, I would be stuck with a "illegal" four.
If anyone could help me out in the right direction I would appreciate it.
feasibility
We can (trivially) show that whatever the 6 configuration chosen, we can always put the 4s.
Indeed, the maximal cover area of a 6 is 9 (a 3x3 square)
000
060
000
(we can't put a 4 in the border surrounding the 6).
So a bound for the maximal 6 configuration covered area is 6*9=54
000000xxxx
060060xxxx
000000xxxx
000000xxxx
060060xxxx
000000xxxx
000000xxxx
060060xxxx
000000xxxx
xxxxxxxxxx
and we can put as many 4 in the squares containing 'x' (which is way more than 4)
6-generation
Let's assume the board is indiced as follow:
0 1 2 3 ... 9
10 11 12 ... 19
...
90... 99
let x towards the bottom and y to the right
A square holding value id can be found at (x,y)=((id - id%10)/10, id%10) and reciprocally
id:(x,y)->x*10+y
So we will only consider numbers between 0 and 99 (since we can find back their associated position(x,y) in the grid)
I will use the copy-pasted below getRandomInt taken shamelessly from mdn
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
Finally we can draw between 0 and 99. If number already exists then discard it, otherwise take it
let indices = new Set
while(indices.size < 6){
let idx = getRandomInt(0,99)
if(!indices.has(idx)){
indices.add(idx)
}
}
return [...indices]
4-generation
We can apply the same stragy as before: draw a random number between 0 and 99 and discard it as long as it is not valid, until 4 valid positions are found
build the invalid set due to the 6 positionned
let invalids = new Set
indices.forEach(idx=>{
let [x,y] = [(idx - idx%10)/10, idx%10]
//add the adjacent squares
for(let i = -1; i<=1; ++i){
for(let j = -1; j<= 1; ++j){
if( 0<=x+i<10 && 0 <= y+j < 10){//if the cell in the board....
invalids.add( (x+i)*10 + y+j )
}
}
}
})
draw except from the invalid set
let out = []
while(out.length < 4){
let idx = getRandomInt(0,99)
if(!invalids.has(idx)){
invalids.add(idx)
out.push(idx)
}
}
return out
That approach may be not that efficient in worst case: we would have about 54% probability of drawing an invalid number!
We can thus consider an array of only the valid numbers, and draw from it
let valids = Array(100).fill(0).reduce((oks, x,i)=>{
if(invalids.has(i)) return oks
return oks.push(i),oks
},[])
//take numbers from that array
let out = []
for(let i = 0; i<4; ++i){
let idx = getRandomInt(0,valids.length)
//notice that here we take the elem from valids
//not just the idx from getRandomInt
out.push(valids[idx])
//and we takeout the element from valids
valids.splice(idx, 1);
}
return out

Javascript : Phaser call a method inside another method class

I am facing a problem during my development in javascript with Phaser. I try to call a method inside a method of a class but I have an error.
Here is my class :
import Enemy from '../classes/Enemy';
import Turret from '../classes/Turret';
import Bullet from '../classes/Bullet';
class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload()
{
this.load.image('turret', './www/assets/tour_simple.png');
}
drawGrid(graphics)
{
graphics.lineStyle(1, 0x0000ff, 0.8);
for(var i = 0; i < 8; i++)
{
graphics.moveTo(0, i * 64);
graphics.lineTo(640, i * 64);
}
for(var j = 0; j < 10; j++)
{
graphics.moveTo(j * 64, 0);
graphics.lineTo(j * 64, 512);
}
graphics.strokePath();
}
canPlaceTurret(i, j)
{
return this.map[i][j] === 0;
}
placeTurret(pointer)
{
var i = Math.floor(pointer.y/64);
var j = Math.floor(pointer.x/64);
if(this.canPlaceTurret(i, j)) {
var turret = this.turrets.get();
if (turret)
{
turret.setActive(true);
turret.setVisible(true);
turret.place(i, j);
}
}
}
damageEnemy(enemy, bullet)
{
// only if both enemy and bullet are alive
if (enemy.active === true && bullet.active === true) {
// we remove the bullet right away
bullet.setActive(false);
bullet.setVisible(false);
//todo mettre dans une classe constante
var BULLET_DAMAGE = 25;
// decrease the enemy hp with BULLET_DAMAGE
enemy.receiveDamage(BULLET_DAMAGE);
}
}
create() {
this.add.text(200, 230, 'good!', { fill: '#0f0' });
var graphics = this.add.graphics();
this.map = [[ 0,-1, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0,-1, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0,-1,-1,-1,-1,-1,-1,-1, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0,-1, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0,-1, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0,-1, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0,-1, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0,-1, 0, 0]];
//TODO creer une classe qui dessine le path
// the path for our enemies
// parameters are the start x and y of our path
this.path = this.add.path(96, -32);
this.path.lineTo(96, 164);
this.path.lineTo(480, 164);
this.path.lineTo(480, 544);
graphics.lineStyle(3, 0xffffff, 1);
// visualize the path
this.path.draw(graphics);
this.enemies = this.physics.add.group({ classType: Enemy, runChildUpdate: true });
this.nextEnemy = 0;
var graphics = this.add.graphics();
this.drawGrid(graphics);
this.turrets = this.add.group({ classType: Turret, runChildUpdate: true });
this.input.on('pointerdown', this.placeTurret);
this.bullets = this.physics.add.group({ classType: Bullet, runChildUpdate: true });
this.physics.add.overlap(this.enemies, this.bullets, this.damageEnemy);
}
update(time, delta) {
if (time > this.nextEnemy)
{
var enemy = this.enemies.get();
console.log(enemy);
if (enemy)
{
enemy.setActive(true);
enemy.setVisible(true);
// place the enemy at the start of the path
enemy.startOnPath();
this.nextEnemy = time + 2000;
}
}
}
}
export default GameScene;
And an error occurs during my game :
TypeError: this.canPlaceTurret is not a function
The error is from the placeTurret method where I call the canPlaceTurret method.
I have tried some things like adding an attribute to my class : self = this; and call my function with self.canPlaceTurret but there is always the problem. I think it is a scope problem but I don't know how to solve that.
Another important information : we are using Webpack
Thanks for reading :).
Try replacing:
this.input.on('pointerdown', this.placeTurret);
with:
this.input.on('pointerdown', pointer => this.placeTurret(pointer));
(slighthy edited following your comment below)
Basically, when you pass your method as a callback, it becomes a simple function reference. When that function is called later, this won't refer to the class instance you're expecting, because it's no longer called in the context of your class.
By using the fat arrow syntax, you create a specific callback instead, and explicitly tells it to call the method in the context of the current (as in, at the time said callback is defined) this.
Alternatively, you can also do:
this.input.on('pointerdown', this.placeTurret.bind(this));
Instead of creating a new anonymous function, this takes your method as a reference (like it was doing to begin with) but manually binds the current this to it.
Please refer to this post for a more detailed explanation.
Won't it work without using the this keyword at all in your placeTurret() method when calling the canPlaceTurret()?
placeTurret(pointer)
{
var i = Math.floor(pointer.y/64);
var j = Math.floor(pointer.x/64);
if(canPlaceTurret(i, j)) {
var turret = this.turrets.get();
if (turret)
{
turret.setActive(true);
turret.setVisible(true);
turret.place(i, j);
}
}
}

webgl simple image effect

i am trying to create image filters using WebGL. i have found this library WebGLImageFilter.The filter effect I am trying to get is Rise Effect
from CSSGram. Here is the source code for that filter.
it basically does this:
apply brightness(1.05)
apply sepia(0.2)
apply contrast(0.9)
apply saturate(0.9)
In the give order, so i replicate this using the library i mentioned.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',1.05);
filter.addFilter('sepia',0.2);
filter.addFilter('contrast',0.9);
filter.addFilter('saturation',0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
But this gives a very different image effect than the mentioned one.so I tried to apply each effect individually and each of them works perfectly but when I combine them I get a different effect than the one I am trying to achieve. what could be the reason?
The library you're using doesn't take the same values as input.
For example CSS saturation takes a value 0 to 1 for how saturated. In other words 0 = no saturation, 1 = full saturation where as the library you linked to takes a positive or negative value for how much to change the saturation. In other words 0 = don't change the saturation. 1 = apply 1 amount of a saturation (amount being however much the library uses by default) and -1 remove one unit of saturation
Also AFAIK the sepia filter doesn't take an input. No matter what value you pass in for sepia it just makes the picture the same amount of sepia.
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i < 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('sepia', i);
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Looking inside the library most of the filters are based on a 5x4 color matrix where the identity (the matrix that leaves the colors as they are) is
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
For sepia it's just hard coded to
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0
The shader being used looks like this
precision highp float;
varying vec2 vUv;
uniform sampler2D texture;
uniform float m[20];
void main(void) {
vec4 c = texture2D(texture, vUv);
gl_FragColor.r = m[0] * c.r + m[1] * c.g + m[2] * c.b + m[3] * c.a + m[4];
gl_FragColor.g = m[5] * c.r + m[6] * c.g + m[7] * c.b + m[8] * c.a + m[9];
gl_FragColor.b = m[10] * c.r + m[11] * c.g + m[12] * c.b + m[13] * c.a + m[14];
gl_FragColor.a = m[15] * c.r + m[16] * c.g + m[17] * c.b + m[18] * c.a + m[19];
}
Which if I understand correctly, reading cross the rows for sepia, means
new red = 39% red, 77% green, 19% blue
new green = 35% red, 69% green, 17% blue
new blue = 27% red, 53% green, 13% blue
new alpha = alpha
So to actually be able to set the amount you need have it be the identity matrix when amount = 0 and the sepia matrix when amount = 1. Fortunately it looks like there's a colorMatrix filter where you can pass in your own matrix. Let's try it
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://i.imgur.com/TSiyiJv.jpg";
image.onload = function() {
for (var i = 0; i <= 1; i += 0.25) {
var filter = new WebGLImageFilter();
filter.addFilter('colorMatrix', mix(identity, sepia, i));
var filteredImage = filter.apply(image);
document.body.appendChild(filteredImage);
}
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
canvas { width: 100px; margin: 5px }
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>
Which seems to work?
const identity = [
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
];
const sepia = [
0.393, 0.7689999, 0.18899999, 0, 0,
0.349, 0.6859999, 0.16799999, 0, 0,
0.272, 0.5339999, 0.13099999, 0, 0,
0,0,0,1,0,
];
const image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://cdn.rawgit.com/una/CSSgram/6f21810a/site/img/atx.jpg";
image.onload = function() {
try {
var filter = new WebGLImageFilter();
}
catch( err ) {
console.log(err)
}
filter.addFilter('brightness',-0.05); // 1.05);
// filter.addFilter('sepia',0.2);
filter.addFilter('colorMatrix', mix(identity, sepia, 0.2));
filter.addFilter('contrast', -0.1); // 0.9);
filter.addFilter('saturation', -0.1); //0.9);
var filteredImage = filter.apply(image);
document.body.appendChild(image);
document.body.appendChild(filteredImage);
}
function mix(m1, m2, amount) {
return m1.map((a, ndx) => {
const b = m2[ndx];
return a + (b - a) * amount;
});
}
img, canvas {
width: 300px;
margin: 5px;
}
<script src="https://cdn.rawgit.com/phoboslab/WebGLImageFilter/e0eee0cd/webgl-image-filter.js"></script>

javascript 2d array tile map srcolling

I have searched this for weeks but just can't find the right tutorial.
Lets say we have a canvas that is 800x800.
<canvas id='draw' width=800 height=800></canvas>
And we have a tile map(0 will be square barriers and 1 will be air).
var tileMap = [ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
How do I make the map scroll so that only 3x3 of the squares be seen whenever the player moves?
For example:
canvas screen--> [0,0,0]
[0,1,1] <-- just this part to be seen
[0,0,0]
when player moves:
canvas screen--> [0,0,0]
[1,1,1] <-- now this part will be seen
[0,0,0]
So how do I make the tile map move to give the illusion that the player is moving?
tileMap should not be modify instead you create some object that represents center of current view e.g. player and use it in your display function. Whenever you want to scroll you just move center of view.
var tileMap = [ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
var hero = {
position: {
x: 0,
y: 1
}
};
const air = 1;
const barrier = 0;
display(hero,tileMap);
// move player instead of scrolling data
// tileMap is untouched
hero.position.x += 3;
display(hero,tileMap);
// use player position to display only portion of map
function display(player,map) {
var result = [
"",
"",
""
];
for(var y = 0, i = player.position.y - 1; y < 3; i++,y++) {
if (i >= 0 && i < map.length) {
for(var x = 0, j = player.position.x - 1; x < 3; j++,x++) {
if ( j >= 0 && j < map[i].length) {
result[y] += map[i][j] + ",";
}
else {
// outside map only ait
result[y] += air+ ",";
}
}
}
else {
// outside map only ait
result[y] += air +","+ air +","+air+",";
}
}
console.log(result);
}
You did not explained how is your array correlated with your canvas and animations - therefore a clear guess is that you're concerned about your Arrays only.
You need a viewport Array viewMap dictated by the camera cam position and size values.
In the example below it's anchor is left/top (you might want to change the logic later to use center/center instead, up to you).
on keyboard event, change the camera x y position and prevent going out of map boundaries
Populate your viewMap array and print it:
var tileMap = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 4, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 8, 8, 1, 0],
[0, 6, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 9, 9, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
var cam = {
x: 0,
y: 0,
width: 3,
height: 3
};
// Create empty 2D viewMap of viewport
var viewMap = [];
for (var i = 0; i < cam.height; i++) viewMap[i] = new Array(cam.width);
function tileViewport() {
for (var y = 0; y < cam.height; y++)
for (var x = 0; x < cam.width; x++)
viewMap[y][x] = tileMap[y + cam.y][x + cam.x];
// PRINT
console.clear(); console.log(viewMap.map(a => a.join(" ")).join("\n"))
}
document.body.addEventListener("keydown", function(e) {
var key = e.which;
if( /^(37|38|39|40)$/.test(key) ) e.preventDefault(); // prevent browser default stuff
if (key === 38) --cam.y;
if (key === 40) ++cam.y;
if (key === 37) --cam.x;
if (key === 39) ++cam.x;
// Fix movement to tileMap area boundary
cam.y = Math.max(0, Math.min(cam.y, tileMap.length - cam.height));
cam.x = Math.max(0, Math.min(cam.x, tileMap[0].length - cam.width));
tileViewport();
});
// INITIALIZE
tileViewport();
Click here and user your keyboard arrows!
Now that the above works correctly you can:
Add logic for obstacles behavior by using the new viewMap Array
prefetch new tiles for your canvas,
animate the canvas depending on the movement

HTML5 Canvas, replacing colors in an image not working on some machines

I have a 2d RTS HTML5 / Javascript game. I use images to display the player's units and buildings. I provide the image and then use a script to replace certain colors in the images with other color, to get different versions of an image with different colors (so the soldier of player 1 has a red sword and the soldier of player 2 has a blue sword and so on...).
The problem is, for maybe ~20% of the users this replacing thing doesnt work and they see all units in the same (default) color. Im now wondering why this is. Heres the function i use to replayce the colors:
// returns a image with some colors replaced, specified by search and replace, which are arrays of color arrays ([[255, 255, 255], [...], ...], )
ImageTransformer.replaceColors = function(img, search, replace)
{
var canv = document.createElement('canvas');
canv.height = img.height;
canv.width = img.width
var ctx = canv.getContext('2d');
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, canv.width, canv.height);
for(var i = 0; i < imgData.data.length; i += 4)
for(var k = 0; k < search.length; k++)
if(imgData.data[i] == search[k][0] && imgData.data[i + 1] == search[k][1] && imgData.data[i + 2] == search[k][2])
{
imgData.data[i] = replace[k][0];
imgData.data[i + 1] = replace[k][1];
imgData.data[i + 2] = replace[k][2];
}
ctx.putImageData(imgData, 0, 0);
return canv;
}
Browsers may or may not apply a gamma to the image prior to drawing them, the intent is to have more natural colors (...).
I bet this is the Browsers which apply a gama that fool your algorithm.
Rather than test for strict equality, you might use a color distance, and decide of a threshold to decide wether to switch or not :
var imgData = ctx.getImageData(0, 0, canv.width, canv.height);
var data = imgData.data, length = imgData.data.length ;
for(var k = 0; k < search.length; k++) {
var thisCol = search[k];
for(var i = 0; i < length; i += 4) {
var colDist = Math.abs(data[i] - thisCol[0] )
+ Math.abs(data[i+1] - thisCol[1] )
+ Math.abs(data[i+2] - thisCol[2] );
if( colDist < 5 )
{
data[i] = thisCol[0];
data[i + 1] = thisCol[1];
data[i + 2] = thisCol[2];
}
}
}
ctx.putImageData(imgData, 0, 0);
return canv;
(here i used as distance the sum of absolute differences in between r,g,b ; as #MarkE suggest, you can choose others, euclidian being this:
var colDist = sq(data[i] - thisCol[0] )
+ sq(data[i+1] - thisCol[1] )
+ sq(data[i+2] - thisCol[2] );
// notice this is the squared euclidian distance.
// whith function sq(x) { return x*x }
test several pictures / distances, and see what fits.
test several threshold also.
).

Categories

Resources