I started learning Three js and I was looking for a way to convert a color map into a normal map. What I want to do is to try and make the normal map based on this color map [image 1], by changing the pixels based on their color so it looks like this normal map [image 2]. I don't want to simply upload the files since I'm trying to minimize the weight of the project as much as possible. Here is what I already tried :
let img = new Image();
img.src = './texture/color.jpg';
img.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
document.getElementById('body').appendChild(canvas)
const c = canvas.getContext('2d')
c.clearRect(0, 0, canvas.width, canvas.height);
c.fillStyle = '#EEEEEE';
c.fillRect(0,0,canvas.width, canvas.height);
//draw background image
c.drawImage(img, 0, 0);
//draw a box over the top
c.fillStyle = "rgba(200, 0, 0, 0)";
c.fillRect(0, 0, canvas.width, canvas.height);
draw(c, canvas);
};
function draw(c, canvas)
{
let img2 = c.getImageData(0, 0, canvas.width,canvas.height);
console.log(img2.data)
let d = img2.data;
for (let i=0; i<d.length; i+=4) {
let r = d[i];
let g = d[i+1];
let b = d[i+2];
v1 = r < 75 ? r / (50 - r) : r * (255 - r);
v2 = g > 75 ? g / (50 - g) : g * (255 - g);
v3 = b > 75 ? b / (50 - b) : b * (255 - b);
d[i] = v1;
d[i+1] = v2;
d[i+2] = v3;
}
console.log(img2.data)
c.putImageData(img2, 0, 0);
}
I can't say what Three.js can or can't do because all I really know of it is that it makes integrating 3d assets with canvases a breeze.
Aside from that, I wrote a pure javascript function that serves the purpose of generating normal maps from color maps quite effectively. Keep in mind, however, that this is a quick port to js of a function I wrote for C# winforms about 4 years ago, one that loops through all the pixels of a given image to extrapolate the data required for the conversion. It's slow. Seriously, painfully slow and it is so because getting nice, crisp, accurate normal maps from recursive algorithms is painfully slow.
But it does exactly what you want it to do; generate a very nice, clean, precise normal map from a given color map and it's simple enough to understand its functionality.
I've set this up as a live demo so you can see it / feel it in action.
There is, of course, a much faster solution involving making a single call for pixel data, iterating over its corresponding 1d array, saving calculated data back to that array then plopping the entire array, itself, down on the output canvas all at once but that involves some interesting virtual multi-dimensional trickery for Sobel but, for the sake of providing a clear, understandable example that works, I'm going with old-school nested recursion so you can see Sobel in action and how pixels are manipulated to arrive at normalization.
I did not implement any fancy asynchronous updates so you'll only know this is processing because, once initiated, the hand cursor used for the button won't return to the default arrow until map generation is complete.
I've also included 4 variations of your original image to play with, all in code with 3 of the 4 commented out. The app starts at 256x256 as the time it takes to generate a normal map from that with recursion is reasonable. Their sizes range from 128 to the original 1024, though I highly advise not engaging the full scale variant as your browser may whine about how long the operation takes.
As with the C# variant, you can implement a means by which client can control the intensity of the resulting normal calculations by adjusting the brightness parameters. Beyond the C# variant, this definitely can be a basis for generating normal maps to visualize in real-time as applied to geometry with Three.js. And by "real-time", I mean however long it takes to generate an x-scale map with nested recursion because the actual application of a completed map to geometry occurs in milliseconds.
Here's a screenshot of the results after processing the 256x256:
To accompany the live demo, here's the code:
normalize.htm
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Normalizer</title>
<link rel="stylesheet" href="css/normalize.css">
</head>
<body onload="startup()">
<canvas id="input" class="canvas"></canvas>
<canvas id="output" class="canvas"></canvas>
<div class="progress">
<input type="button" class="button" onclick="totallyNormal()" value="Normalize!" />
<span id="progress">Ready to rock and / or roll on your command!</span>
</div>
<script src="js/normalize.js"></script>
</body>
</html>
normalize.css
html {
margin: 0px;
padding: 0px;
width: 100vw;
height: 100vh;
}
body {
margin: 0px;
padding: 0px;
width: 100vw;
height: 100vh;
overflow: hidden;
font-family: "Arial Unicode MS";
background: linear-gradient(330deg, rgb(150, 150, 150), rgb(200, 200, 200));
background-color: rgb(200, 200, 200);
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
outline: 1px solid hsla(0, 0%, 0%, 0.25);
}
.progress {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 40px;
display: flex;
}
.progress span {
width: calc(100% - 160px);
height: 40px;
line-height: 40px;
color: hsl(0, 0%, 0%);
text-align: center;
}
input[type="button"] {
margin: 0px;
width: 120px;
height: 40px;
cursor: pointer;
display: inline;
}
normalize.js
// Javascript Normal Map Generator
// Copyright © Brian "BJS3D" Spencer 2022
// Incorporating W3C proposed algorithm to
// calculate pixel brightness in conjunction
// with the Sobel Operator.
var input, output, ctx_i, ctx_o, w, h;
function startup() {
var img;
input = document.getElementById("input");
ctx_i = input.getContext("2d");
ctx_i.clearRect(0, 0,input.width, input.height);
img = new Image();
img.crossOrigin = "Anonymous";
//img.src = "https://i.imgur.com/a4N2Aj4.jpg"; //128x128 - Tiny but fast.
img.src = "https://i.imgur.com/wFe4EG7.jpg"; //256x256 - Takes about a minute.
//img.src = "https://i.imgur.com/bm4pXrn.jpg"; //512x512 - May take 5 or 10 minutes.
//img.src = "https://i.imgur.com/aUIdxHH.jpg"; //original - Don't do it! It'll take hours.
img.onload = function () {
w = img.width - 1;
h = img.height - 1;
input.width = w + 1;
input.height = h + 1;
ctx_i.drawImage(img, 0, 0);
output = document.getElementById("output");
ctx_o = output.getContext("2d");
output.width = w + 1;
output.height = h + 1;
};
}
function totallyNormal() {
var pixel, x_vector, y_vector;
for (var y = 0; y < w + 1; y += 1) {
for (var x = 0; x < h + 1; x += 1) {
var data = [0, 0, 0, 0, x > 0, x < w, y > 1, y < h, x - 1, x + 1, x, x, y, y, y - 1, y + 1];
for (var z = 0; z < 4; z +=1) {
if (data[z + 4]) {
pixel = ctx_i.getImageData(data[z + 8], data[z + 12], 1, 1);
data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
} else {
pixel = ctx_i.getImageData(x, y, 1, 1);
data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
}
}
x_vector = parseFloat((Math.abs(data[0] - data[1]) + 1) * 0.5) * 255;
y_vector = parseFloat((Math.abs(data[2] - data[3]) + 1) * 0.5) * 255;
ctx_o.fillStyle = "rgba(" + x_vector + "," + y_vector + ",255,255)";
ctx_o.fillRect(x, y, 1, 1);
}
}
document.getElementById("progress").innerHTML = "Normal map generation complete.";
}
I am creating a game for coursework, two tanks placed on a canvas with input boxes for the initial velocity and angle of the turret, then a button to fire a projectile (currently a div element in the shape of a circle), which calls a function in this case it is fire1. I have messed around for a few days and can't seem to get it to work, "bullet" is my div element.
function fire1 () {
var canvas = document.getElementById("canvas")
var bullet = document.getElementById("bullet");
bullet.style.visibility = "visible"
var start = null;
var intialVelocity = velocity1.value
var angle = angle1.value
var g = 9.81;
var progress, x, y;
function step(timestamp) {
if(start === null) start = timestamp;
progress = (timestamp - start)/1000;
x = (turret1.x + 80) + (intialVelocity*progress)
y = (turret1.y - 400) + (intialVelocity*progress)*Math.sin(angle*toRadians) - (0.5*g*(progress^2));//)
bullet.style.left = x + "px";
bullet.style.bottom = y + "px";
requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
Below is my css bit of my bullet.
#bullet {
position: absolute;
left: 0;
bottom: 50%;
width: 1em;
height: 1em;
border-radius: 0.5em;
background: red;
visibility: hidden;
}
I am very new to javascript, css and html so help would be very appriciated, I'm trying to incorporate the trajectory formula will this work? I also want it to be animated so it follows a path when fired. Thanks
I fixed this a long time ago but forgot to update with solution, below is how x and y are calculated for the trajectory:
x = ((turret.anchorX + negative*(initialVelocity*progress*Math.cos(angle*toRadians)))); //x-coordinate for bullet calculated by using x=ut.
y = ((720 - turret.anchorY + (initialVelocity*progress*Math.sin(angle*toRadians)) + (0.5*g*(Math.pow(progress,2))))); //y-coordinate for bullet calculated by usnig ut+0.5at^2.
$(this).css({
position: 'absolute',
left: Math.random() * ($('.parentcontainer').width() - $(this).width()),
top: Math.random() * ($('.parentcontainer').height() - $(this).height())
});
I got this each loop that will randomly place elements within a div. The problem with this is that the elements will overlap each other sometimes because they are absolute positioned. Is there anyway to go around this in js to check position? or maybe rewrite this with margin values? Thanks a ton!
There's a few different ways you can do to achieve this. I find it easiest to try to define the problem in one sentence:
New square's position must be at least X distance from current square positions
Using this sentence, we can make some simple theories as to how the code will work.
Assuming all squares are 50x50 pixels, we can write some checks.
Here are some pseudo code steps we could follow:
Generate a random position for newSquare
Compare the x and y positions of newSquare to all existing squares
If either of the x and y positions of newSquare are further away from the other squares, newSquare can be placed
Otherwise, try again
var container = $('#container');
var squareSize = 50;
var containerSize = 500;
for (var i = 0; i < 20; i++) {
var foundSpace = false;
while (!foundSpace) {
// Generate random X and Y
var randX = Math.floor(Math.random() * (containerSize - squareSize));
var randY = Math.floor(Math.random() * (containerSize - squareSize));
var hitsSquare = false;
var squares = container.children();
squares.each(function(index, square) {
var square = $(square);
// parseInt() because .css() returns a string
var left = parseInt(square.css('left'));
var top = parseInt(square.css('top'));
// Check boundaries
var hitsSquareX = Math.abs(left - randX) < squareSize;
var hitsSquareY = Math.abs(top - randY) < squareSize;
// Will overlap a square
if (hitsSquareX && hitsSquareY) {
hitsSquare = true;
// jQuery break .each()
return false;
}
});
// If doesn't overlap any square
if (!hitsSquare) {
foundSpace = true;
var newSquare = $('<div class="square">');
newSquare.offset({
left: randX,
top: randY
});
container.append(newSquare);
}
}
}
#container {
position: relative;
}
.square {
position: absolute;
background-color: red;
width: 48px;
/* border adds 2px */
height: 48px;
/* border adds 2px */
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
</div>
You should look for collision detection.
In my opinion this is a great tutorial: https://www.youtube.com/watch?v=XYzA_kPWyJ8, but there are several other great ones out there.
Good luck :)
Those who solve this will get 150 reputation points once im eligible for a bounty.
https://jsfiddle.net/testopia/xzxe6y5k/
As you can see in the jsfiddle I did some trigonometric calculations to figure out the exact position for the adjacent placements.
The following formula gives me the exact positioning:
elem.offsetHeight * Math.cos(degrees converted into radians) + elem.offsetTop
elem.offsetWidth * Math.cos(degrees converted into radians) + elem.offsetLeft
Of course the same thing is also possible by getting the vertex points, the code would just be larger. Here a small example:
elem.offsetLeft + elem.offsetWidth
elem.offsetTop + elem.offsetHeight
Anyways, I figure that automatic placement is pretty hard. I mean I am trying to achieve something like in the image below: http://www.purplesquirrels.com.au/wp-content/uploads/2015/02/dg.png
Question: So how can I make the diamond grid spread to the full height and width of the screen / container from the center? Not a loop from left to right and top to bottom but starting from the center in a somewhat circular way.
I was able to get the screen filled with two while loops. For now I used some static margins, so the spacings are not perfect, but I guess your computePosition function can help with generating the right spacings between the diamonds.
https://jsfiddle.net/xzxe6y5k/3/
var wrapper = document.getElementById('grid'), diamond = wrapper.children, newDiamond, prevDiamond, evenRow = true;
function createDiamonds() {
while (!newDiamond || newDiamond.getBoundingClientRect().bottom < window.innerHeight) {
evenRow = !evenRow;
prevDiamond = newDiamond;
newDiamond = wrapper.appendChild(document.createElement('div'));
if (prevDiamond) {
newDiamond.style.top = prevDiamond.getBoundingClientRect().bottom + 10 - (newDiamond.getBoundingClientRect().height / 2) + 'px';
if (evenRow) {
newDiamond.style.left = diamond[0].getBoundingClientRect().left + newDiamond.getBoundingClientRect().width / 2 + 7 + 'px';
}
}
while (newDiamond.getBoundingClientRect().right < window.innerWidth) {
prevDiamond = newDiamond;
newDiamond = wrapper.appendChild(document.createElement('div'));
newDiamond.style.left = prevDiamond.getBoundingClientRect().right + 10 + 'px';
newDiamond.style.top = prevDiamond.style.top;
}
}
}
createDiamonds();
#grid div {
background: black;
height: 25px;
width: 25px;
position: absolute;
transform: rotate(45deg)
}
<div id="grid"></div>
Is there any way to improve the following html5 example, or is the browser
just to slow in handling mouse events?
Its a grid, and on the point you move the mouse to you see a red rectangle..
But this rectangle is a kind of lagging behind the mouse, so moving to slow to its position.
(if the mouse is moved pretty fast)
http://jsfiddle.net/191rmac8/
Here the code:
<body>
<canvas id="canvas" width="400" height="400">error or not supported.</canvas>
<script>
var lineSize = 10;
var rasterSize = 5;
var bx = 0;
var by = 0;
g2d = document.getElementById("canvas").getContext("2d");
g2d.setFillColor("rgb(10, 10, 10)");
g2d.fillRect(0, 0, g2d.canvas.width, g2d.canvas.height);
g2d.setStrokeColor("rgb(0, 0, 255)");
g2d.setLineWidth(lineSize);
function repaint(){
g2d.clearRect(0, 0, g2d.canvas.width, g2d.canvas.height);
g2d.beginPath();
for(i = 0; i < rasterSize + 1; i++){
g2d.moveTo(0, (lineSize / 2) + i * (g2d.canvas.height - lineSize) / (rasterSize));
g2d.lineTo(g2d.canvas.width, (lineSize / 2) + i * (g2d.canvas.height - lineSize) / (rasterSize));
g2d.moveTo((lineSize / 2) + i * (g2d.canvas.width - lineSize) / (rasterSize), 0);
g2d.lineTo((lineSize / 2) + i * (g2d.canvas.width - lineSize) / (rasterSize), g2d.canvas.height);
}
g2d.stroke();
g2d.setFillColor("red");
g2d.fillRect(bx - 5, by - 5, 11, 11);
}
repaint();
g2d.canvas.addEventListener("mousemove", function(e){
bx = e.offsetX;
by = e.offsetY;
repaint();
});
</script>
</body>
body {
margin: 0;
width: 100%;
height: 100%;
display: block;
background: black;
}
canvas {
margin: auto;
margin-top: 50px;
display: block;
}
You can separate the mouse events from the drawing to increase performance.
Create an array to hold mouse points
In mousemove, push the current mouse position into the array.
Depending on your design, you might use Aboca's idea of capping the capture rate of the points.
Create a loop using requestAnimationFrame.
In the loop, draw all points since the loop was last executed as 1 path.
The benefits are:
requestAnimationFrame is efficient at drawing.
You are drawing a polyline through a batch of points instead of 1 point at a time.
Changing context state is somewhat expensive, and this lets you change state only once.
You can cap the rate of the repaint like I did here:
http://jsfiddle.net/sh6o91g4/1/
Adjust as you see fit as it will fasten the perfomance but it will reduce the quality of the rendering too (skipping frames has it's drawbacks)
var now = new Date().getTime();
if(now - time > 10){
time = now;
bx = e.offsetX;
by = e.offsetY;
repaint();
}