3d stereogram in zero lines of JavaScript
Stereograms are flat images that produce a 3d effect (illusion) when viewed in a certain way.
They were all the rage in the 1990's and it's time to bring them back!
1. Copy this text:
learncoding
2. Paste it 6 times into a notepad application (make sure you're using a monospaced font like Courier):
learncodinglearncodinglearncodinglearncodinglearncodinglearncoding
3. Delete the last 3 occurrences of the letter 'g':
learncodinglearncodinglearncodinglearncodinlearncodinlearncodin
Congratulations! You just created your first stereogram! And you did not even need any code to do that!
Admittedly, this one is very simple and a little difficult to see - still, you can see it in 3d with some practice. This one is better and below is the program that generated it:
learncodinglearncodinglearncodinglearncodinglearncodinglear
learncodinglearncodinglearncodinglearncodinglearncodinglear
learncodinglearncodinglearncodinglearncodinglearncodinglear
learnc%odinglearnc%odinglearnc%odinglearnc%odinglearncoding
learnc(odinglearnc(odinglearnc(odinglearnc(odinglearncoding
learnc(odinglearnc(odinglearnc(odinglearnc(odinglearncoding
learnc/odinglearnc/odinglearnc/odinglearnc/odinglearncoding
learnc"oding7learnc"oding7learnc"oding7learnc"odng7leanc"od
learnc8oding7learnc8oding7learnc8oding7learnc8odng7leanc8od
learnc&oding9learnc&oding9learnc&oding9learnc&odng9leanc&od
learnc7oding!learn0c7oding!learn0c7oding!larn0c7ding!lrn0c7
learnc5oding#learn#c5oding#learn#c5oding#larn#c5ding#lrn#c5
learnc*oding#learn/c*oding#learn/c*oding#larn/c*ding#lrn/c*
learnc,oding'learn%c,oding'learn%c,oding'larn%c,ding'lrn%c,
learnc(oding3learn8c(oding3learn8c(oding3larn8c(ding3lrn8c(
learnc*oding&learn'c*oding&learn'c*oding&larn'c*ding&lrn'c*
learnc3oding6learnc3oding6learnc3oding6learnc3odng6leanc3od
learnc4oding/learnc4oding/learnc4oding/learnc4odng/leanc4od
learnc+oding8learnc+oding8learnc+oding8learnc+odng8leanc+od
learnc1odinglearnc1odinglearnc1odinglearnc1odinglearncoding
learnc-odinglearnc-odinglearnc-odinglearnc-odinglearncoding
learnc7odinglearnc7odinglearnc7odinglearnc7odinglearncoding
learnc0odinglearnc0odinglearnc0odinglearnc0odinglearncoding
learncodinglearncodinglearncodinglearncodinglearncodinglear
learncodinglearncodinglearncodinglearncodinglearncodinglear
learncodinglearncodinglearncodinglearncodinglearncodinglear
Here's how to look at stereograms to actually see them in 3d (feel free to skip this section if you already how to do that):
Stare straight ahead as if you were looking at a point behind your screen (as if your monitor was transparent). Instead of having your eyes focus on one point on the screen, you want them to be relaxed and looking forward.
Let's imagine the picture below is the top view of two pairs of eyeballs (blue circles with green dots representing retinas) and two computer screens (blue boxes).
The person on the top is focusing on the screen, which is the normal way of viewing it. Both eyes are seeing the same part of the screen, eg. a word.
The person at the bottom is staring "through" the screen at a point far in the distance. This is parallel viewing which allows us to see stereograms in 3d. His or her left eye will see the left side of the screen and his right eye the right side.
Parallel viewing is very different from our natural way of looking at things. Our eyes and brains have been trained to focus on objects instead of trying to look through them, so it takes some practice to get it to work, often a couple of attempts over several minutes.
Make sure the stereogram is directly in front of you and it's far from other objects (cursor, buttons, scroll bars, any kind of text like the url line) because your eyes will tend to focus on those objects.
If you're having a hard time seeing the 3d effect, insert this as line [71]:
body.innerHTML = body.innerHTML + '<br><br>x x'; |
This will draw two x's below the stereogram. Now move your face very close to the screen so that yours noses is almoust touching it just below the x's (NSFW!).
Now look straight ahead and slowly move your face away from the screen. You should see one or two blurry 'phantom' x's in addition to the real ones, since your eyes can't focus properly. Keep moving back slowly and do not allow your eyes to focus. If the phantom x's disappear, you lost it and have to restart.
Once you're about 10 inches away from the screen, slowly move your eyes up to the stereogram, again not letting your eyes relax.
If you do it right, you should still be seeing the same characters, but now some of them appear to be deeper than others. The illusion is very strong - if you're not sure you're seeing it, you're not doing it correctly yet.
And here's how and why it works:
You're basically tricking your brain by making the left eye see something different than your eye. If both your eyes look straight ahead in our example, your left eye will see the full word ('learncoding'), whereas your right eye will see the one without the letter 'g' ('learncodin').
In real life this situation (each eye seeing something different) only happens if there is a 3d corner (and consequently two different depths/distances) if front of us, something like this:
Your left eye only sees 'learncodin', because the letter 'g' is hidden behind the corner:
Your right eye sees the full word, because the corner is not hiding the letter 'g'"
So in a nutshell, we have a repeating pattern (it can be letters or colored dots). At some point we add or remove one element from the pattern. Our brain will see a depth change in the place where the pattern changed - removing an element reduces depth and adding one increases it.
In this tutorial, the elements are characters. The original pattern is the word 'learncoding' (consisting only of letters). The additions to the pattern are special characters (#+% etc.) or numbers. We could still use letters for additions, but the difference makes it easier to understand what is happening to the pattern.
Also, you should avoid repeating elements in the pattern as it can weaken the effect.
Summary of the code:
go through each line of the source image:
reset the pattern to the original ('learncoding')
go from left to right through each character of the source line:
if the character of the source line is higher than the current depth, add a random character to the pattern
if the character of the source line is lower than the current depth, remove a character from the pattern
draw the next character from the pattern
<html> |
<style> |
body { |
font-family: courier; |
text-align: center; |
} |
</style> |
<body> |
<br><br><br><br><br><br> |
<script> |
let body = document.body; |
let sourcePattern = 'learncoding'; |
let source = [ |
'00000000000000000000000000000000000000000000000000000000000', |
'00000000000000000000000000000000000000000000000000000000000', |
'00000000000000000000000000000000000000000000000000000000000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222233333333333333333333333322222211111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111122222222222222222222222222222222222211111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000011111111111111111111111111111111111111111111111100000', |
'00000000000000000000000000000000000000000000000000000000000', |
'00000000000000000000000000000000000000000000000000000000000', |
'00000000000000000000000000000000000000000000000000000000000', |
]; |
|
for (let y = 0; y < source.length; y++) { |
let currentDepth = source[y][0]; |
let currentPattern = []; |
let length = sourcePattern.length; |
for (let i = 0; i < length; i++) |
currentPattern.push(sourcePattern[i]); |
let position = 0; |
for (let x = 0; x < source[y].length; x++) { |
let next = source[y][x]; |
if (next < currentDepth) { |
currentPattern.splice(position, 1); |
length--; |
if (position == length) |
position = 0; |
} |
if (next > currentDepth) { |
currentPattern.splice(position, 0, String.fromCharCode(Math.random() * 25 + 33)); |
length++; |
} |
currentDepth = next; |
body.innerHTML = body.innerHTML + currentPattern[position]; |
position++; |
if (position == length) |
position = 0; |
} |
body.innerHTML = body.innerHTML + '<br>'; |
} |
</script> |
</body> |
</html> |
Details:
[4] - make sure you're using a monospaced (fixed-width) font
[12] - the original pattern
[13-40] - source image. Higher number = deeper
[42] For each row:
[43] Take depth from the first column
[44-47] convert the souce pattern to an array (currentPattern)
[49] For each column:
[50] get the next character from the source image
[51-53] remove a character from the pattern if depth decreases
[54-55] restart the pattern if we reached the end
[57-60] add a character to the pattern if depth increases
[61] update the current depth
[62] write the next character from the pattern
[63] move to the right in the pattern
[64-65] restart the pattern if we reached the end
[67] draw next line
There is nothing magical about the source image, the characters or the pattern that I picked, so feel free to experiment with different ones, for example replace the source with this fine example of programmer's art:
'000000000000000000000000000000000000000000000000000000000', |
'000000000000000000000000000000000000000000000000000000000', |
'000000000000111111111111111111111111111111111000000000000', |
'000000000001111111111111111111111111111111111100000000000', |
'000000000011111111111111111111111111111111111100000000000', |
'000000000111110000001111111111111111000000111110000000000', |
'000000000111110000001111111111111111000000111110000000000', |
'000000001111110000001111111111111111000000111111000000000', |
'000000011111111111111111111111111111111111111111100000000', |
'000000011111111111111111111111111111111111111111100000000', |
'000000011111111111111111111111111111111111111111100000000', |
'000000001111111111111111111111111111111111111111000000000', |
'000000000111111110000111111111111110000111111110000000000', |
'000000000111111110000111111111111110000111111110000000000', |
'000000000011111111100000000000000000011111111100000000000', |
'000000000001111111110000000000000000111111111100000000000', |
'000000000000111111111111111111111111111111111000000000000', |
'000000000000111111111111111111111111111111111000000000000', |
'000000000000000000000000000000000000000000000000000000000', |
'000000000000000000000000000000000000000000000000000000000', |
It is quite easy to modify the code to use randomly colored dots (pixels) instead of characters as the pattern. This produces a Random Dot Stereogram (RDS):
With some more code, you can create a stereogram where the initial pattern is different in each row. This pattern can be taken from another small image, for example:
Check out these programming tutorials:
JavaScript:
Optical illusion (18 lines)
Turtle graphics (18 lines)
Particle constellations (42 lines)
Spinning squares - visual effect (25 lines)
Oldschool fire effect (20 lines)
Fireworks (60 lines)
Animated fractal (32 lines)
Minesweeper game (100 lines)
Physics engine for beginners
Physics engine - interactive sandbox
Physics engine - silly contraption
Starfield (21 lines)
Yin Yang with a twist (4 circles and 20 lines)
Tile map editor (70 lines)
Sine scroller (30 lines)
Interactive animated sprites
Image transition effect (16 lines)
Wholla lotta quadratic curves (50 lines)
Your first program in JavaScript: you need 5 minutes and a notepad
Fractals in Excel
Python in Blender 3d:
Domino effect (10 lines)
Wrecking ball effect (14 lines)
3d fractal in Blender Python