I need to draw a 3d house model (walls only) from a 2d path or array (explained later) I receive from FabricJS editor I've built. The type of data sent from 2d to 3d views doesn't matter.
My first (and only quite close to what I want to get) attempt was to create the array of 1s and zeros based on the room I want to draw, and then render it in ThreeJS as one cuboid per 'grid'. I based this approach on this ThreeJS game demo. So if the array look like this:
var map = [ //1 2 3 4 5 6 7 8
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
[1, 1, 0, 0, 0, 0, 0, 1, 1, 1,], // 1
[1, 1, 0, 0, 1, 0, 0, 0, 0, 1,], // 2
[1, 0, 0, 0, 1, 1, 0, 0, 0, 1,], // 3
[1, 0, 0, 1, 1, 1, 1, 0, 0, 1,], // 4
[1, 0, 0, 0, 1, 1, 0, 0, 1, 1,], // 5
[1, 1, 1, 0, 0, 0, 0, 1, 1, 1,], // 6
[1, 1, 1, 0, 0, 1, 0, 0, 1, 1,], // 7
[1, 1, 1, 1, 1, 1, 0, 0, 1, 1,], // 8
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1,],
];
I iterate through the array and render one block for every 1, and calculate it's position from indexes from the 2d 'map' (my array).
var UNITSIZE = 250, units = mapW;
for (var i = 0; i < mapW; i++) {
for (var j = 0, m = map[i].length; j < m; j++) {
if (map[i][j]) {
var wall = new t.Mesh(cube, material);
wall.position.x = (i - units/2) * UNITSIZE;
wall.position.y = WALLHEIGHT/2;
wall.position.z = (j - units/2) * UNITSIZE;
scene.add(wall);
}
}
}
It worked great till I wanted to place other models (.obj, but it doesn't matter. Let's call them furniture) near the walls. Each piece of furniture has it's (x=0, y=0, z=0) point in the center of the model, and since walls are cubes (with the same coord system, with 0 point in the center), furniture are rendered in the center of the wall (when we place it in the corner, only 1/4 of the model is visible). This is more/less how it looks like:
(black - how the walls should look like, blue - each cuboid of the wall, red - piece of furniture)
Thats why I would like to render walls as planes, probably from a 2d closed patch (I can export it from Fabric without a problem). I don't need walls to be thick nor to be visible "from behind", when camera moves through the wall. Any clues on how to achieve something like this?
"Help me StackOverflow, your my only hope."
You can manually populate the vertex and face arrays of a THREE.js mesh, so if you can export the closed path you need for example as an array of coordinates, you can iterate over it, and push needed information to your wall object.
Something like this
var coordArray = [...]; //Array of corner points of a closed shape from your source. Here assumed to be THREE.Vector2() for simplicity.
var walls = new THREE.Geometry();
for(var i = 0; i < coordArray.length(); i++){ //iterate over the coordinate array, pushing vertices to the geometry
var coordinates = coordArray[i];
walls.vertices.push(new THREE.Vector3(coordinates.x, coordinates.y, 0)); //vertex at floor level
walls.vertices.push(new THREE.Vector3(coordinates.x, coordinates.y, 10)); //vertex at the top part of the wall, directly above the last
}
var previousVertexIndex = walls.vertices.length - 2; // index of the vertex at the bottom of the wall, in the segment we are creating faces for
for(var i = 0; i < walls.vertices.length; i += 2){
walls.faces.push(new THREE.Face3(i, i + 1, previousVertexIndex));
walls.faces.push(new THREE.Face3(i + 1, previousVertexIndex + 1, previousVertexIndex));
previousVertexIndex = i;
}
walls.computeVertexNormals();
walls.computeFaceNormals();
scene.add(new THREE.Mesh(walls, new THREE.MeshLambertMaterial());
Related
I've recently seen a challenge picture on brillant.org's Instagram account:
The instructions:
The robot takes 4 random steps (can't go diagonal).
In which area is it most likely to land?
Obviously there are 44 = 256 possible paths for the robot.
I tried to write a program (Javascript) to solve that problem but my approaches didn't work.
Actually I don't have useful code to show here because I got stuck pretty early on.
So my question:
How would you write a program that:
Checks all 256 possible paths and
Tells me how many (%) landed in which area
This is a very cool question!
And thanks for letting me discover brillant.org's Instagram account.
So, I would proceed as following:
Write a function to calculate all possible permutation with repetition (n^k)
Generate a map where to execute all possible moves calculated in the step 1
Check the area where the robot would land on with the final step and store it
Calculate the percentage based on the counting in step 3
The first step is a problem by itself, and it's not part of this scope. You can use or adapt the code here: https://rosettacode.org/wiki/Permutations_with_repetitions
Then, to generate the map, I simply used an array:
const map = [
0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 2, 1, 1, 0, 0,
0, 1, 1, 2, 2, 2, 1, 1, 0,
1, 1, 3, 3, 2, 3, 3, 1, 1,
0, 1, 1, 3, 3, 3, 1, 1, 0,
0, 0, 1, 1, 3, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0,
];
This is a representation of the image you gave, each area is marked with a different number, that we will reuse later.
At this point I defined an array of the 4 possible moves:
const moves = [
-1, // left
1, // right,
-9, // top
9, // bottom
];
The values indicates the offset needed to move in the direction wrote in in the comment: left and right I guess are self explanatory. For the top and bottom, since we're using an array as "matrix", we basically need to translate the y value to a index value in the array. The formula is simple: index = x + y * width there fore it means if you want to specify a y to move up by one cell you have -1 * 9, and to move down is 1 * 9.
For the same reason the robot's starting position (at the center of the map) is calculate as follow: 4 + 4 * 9.
Now I calculate all the possible moves combination with the permutation function:
const allmoves = permutationsWithRepetition(4, moves);
And create an array to store the results:
let results = [0, 0, 0, 0];
After that, I just iterate all the possible moves array, and calculate the position at the end of the moves:
for (let j = 0; j < allmoves.length; j++) {
// set the robot's initial position at the center
// before executing any moves' list
let pos = 4 + 4 * 9;
// calculate the new position using the current moves
for (let i = 0; i < moves.length; i++) {
let move = allmoves[j][i];
pos += move;
}
// now `map[pos]` points to a number between 1 and 3
// that identify the area where the robot is.
// we use that number as index of the results
// to increment its value.
// Notice that it's impossible land to any 0 area
// if we start from the center and we can only perform
// 4 moves.
// We use that number as `index` for our `results`
results[map[pos]]++;
}
Now in results you will have how many times the robot ended up in which area:
console.log(results); // [0, 60, 100, 96]
As mentioned is impossible given the starting position and the number of moves for the robot to land in any of the 0 area, so the first index would have 0 as value.
You can see that it landed in the area 1 (the orange one) 60 times, in the area 2 100 times (the smallest area, the green / aqua one), and in the area 3, 96 times (the blue / purple one).
At this point you can calculate the percentage (times / total * 100) and display it with a proper formatting:
// we skip the first element of results since
// it's not an area (and we'll be always 0)
for (let k = 1; k < results.length; k++) {
console.log(k, `${(results[k] / allmoves.length * 100).toFixed(2)}%`)
}
And you'll get:
1 "23.44%"
2 "39.06%"
3 "37.50%"
You can also do an empiric check, and actually generate ten thousands of moves randomly and make the program apply those instead of allmoves, and you'll see that you end always with similar number (obviously, but that also the fun part of math, verify that is actually what you will expect!).
Here the a working code that also implement the permutation code mentioned at the beginning, from rosettacode.org, and the code explained in this post: https://codepen.io/zer0/pen/RwWPZmE
(You need to open the console to see the results)
I would create different objects representing the different possibilities like below:
function Path(x, y, movesLeft) {
this.x = x;
this.y = y;
this.movesLeft = movesLeft;
this.paths = [];
if (movesLeft > 0) {
this.paths.push(new Path(x - 1, y, movesLeft - 1));
this.paths.push(new Path(x + 1, y, movesLeft - 1));
this.paths.push(new Path(x, y - 1, movesLeft - 1));
this.paths.push(new Path(x, y + 1, movesLeft - 1));
}
this.getArray = function() {
if (this.movesLeft > 0) {
var output = [];
for (var i = 0; i < this.paths.length; i++) {
output = output.concat(this.paths[i].getArray());
}
return output;
}
return [this];
}
}
Now, you can create an object and test the results:
var endPosArray = new Path(0, 0, 4).getArray();
All you need to do is loop through the array and calculate the chances.
I am currently attempting to display a map in processing using a 2d array.
Currently I have this down:
var start_map = [
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 1, 1, 1, 1]
];
function drawMap(map) {
for (var x = 0; x < 5; x++) {
for (var y = 0; y < 5; y++) {
if (map[x][y] == 0) {
fill(51, 153, 51);
rect((10 + 50*x), (10 + 50*y), 50, 50);
}
else if (map[x][y] == 1) {
fill(0, 102, 0);
rect((10 + 50*x), (10 + 50*y), 50, 50);
}
}
}
}
But, while I do get a map displayed, it appears to be rotated 90 degrees clockwise. What is causing this and how can I fix this?
Think about how the indexes of a 2D array work. Let's look at a simpler example:
var map = [
[1, 2],
[3, 4]
];
Where is map[0][1]? Where is map[1][0]?
A 2D array is an array of arrays. The first index selects the subarray at that index, and the second index selects the element in that subarray.
So in the simple example above, map[0] selects the subarray at index 0, which is [1, 2]. Then map[0][1] selects the element in that subarray at index 1, which is 2.
This might seem a bit surprising if you were treating the indexes as an x, y pair. In that case, you'd expect 0, 1 to give you 3, right? But it's not an x, y pair. It's an index into the outer array, then an index into the subarray.
In other words, it's actually a y, x pair. So to fix your problem, you can actually just swap the order of your indexes:
`map[y][x]`
Now you use the y value to select which subarray you want (which row you want), and the x value to select which element in that subarray you want (which column you want).
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
I'm trying to do a Tilemap system, so I went through a tutorial. Here's the code :
// Possible tile types
const TILE_TYPES = {
0: { name: 'Sea', color: 'lightBlue'},
1: { name: 'Land', color: 'wheat' },
2: { name: 'House', color: 'black'}
}
// Map tile data
let mapData = <?php echo $mapData ?>
/**
Tile class
*/
class Tile {
constructor (size, type, ctx) {
this.size = size
this.type = type
this.ctx = ctx
}
draw (x, y) {
// Store positions
const xPos = x * this.size
const yPos = y * this.size
// Draw tile
this.ctx.fillStyle = this.type.color
this.ctx.fillRect(xPos, yPos, this.size, this.size)
}
}
/**
Map class
*/
class Map {
constructor (selector, data, opts) {
this.canvas = document.getElementById(selector)
this.ctx = this.canvas.getContext('2d')
this.data = data
this.tileSize = opts.tileSize
}
}
/**
OrthogonalMap class
*/
class OrthogonalMap extends Map {
constructor (selector, data, opts) {
super(selector, data, opts)
this.draw()
}
draw () {
const numCols = this.data[0].length
const numRows = this.data.length
// Iterate through map data and draw each tile
for (let y = 0; y < numRows; y++) {
for (let x = 0; x < numCols; x++) {
// Get tile ID from map data
const tileId = this.data[y][x]
// Use tile ID to determine tile type from TILE_TYPES (i.e. Sea or Land)
const tileType = TILE_TYPES[tileId]
// Create tile instance and draw to our canvas
new Tile(this.tileSize, tileType, this.ctx).draw(x, y)
}
}
}
}
// Init canvas tile map on document ready
document.addEventListener('DOMContentLoaded', function () {
// Init orthogonal map
const map = new OrthogonalMap('orthogonal-map', mapData, { tileSize: 64 })
})
Here's the call :
<?php
$mapData = '[
[1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2],
[1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1],
[1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
]';
include "" . $_SERVER['DOCUMENT_ROOT'] . "/includes/maptiles.php";
?>
<canvas id="orthogonal-map" class="canvas-map" width="704" height="576"> </canvas>
Here's my question : How can I replace the color by an image ?
My first bet would be to replace the 'color' attribute to the TILE_TYPES constant and to replace this.ctx.fillStyle by something like this.ctx.drawimage.
I'm a beginner in Javascript so I would love some explaination to your process if you have the time. Thanks !
Drawing the images on the canvas will be straightforward, however, there is a step you need to do before that requires explanation. You need to load the images and wait for them to be ready before starting executing the Javascript code. I'm going to show you the easiest way to achieve this, but there are other methods and this one has several problems. So, it is ok by now, but at some point you will learn the other ways to do it.
First, we will add at html image tags to load the images:
<img id="Sea" src="Sea.jpg">
<img id="Land" src="Land.jpg">
<img id="House" src="House.jpg">
<script>
...
The <script> is the start of your current code and with the ... I mean that the code continues there, you must not write the dots into your code.
If you check the result, you will see that they appear at the top of your page, on top of the canvas. We will now tell Javascript to wait for them to load and remove them from the top of the canvas:
First lets change this:
document.addEventListener('DOMContentLoaded', function () {
by this:
window.addEventListener('load', function () {
With what you had, you were waiting for the DOM content to be loaded to start the execution. With the new code, it will wait for everything to be loaded, including the DOM content and the images.
So, now lets create some references for the images:
const images = {};
window.addEventListener('load', function () {
images.Sea = document.getElementById("Sea");
images.Land = document.getElementById("Land");
images.House = document.getElementById("House");
...
And now, lets remove them from the top of the canvas:
const images = {};
window.addEventListener('load', function () {
images.Sea = document.getElementById("Sea");
images.Land = document.getElementById("Land");
images.House = document.getElementById("House");
images.Sea.parentNode.removeChild(images.Sea);
images.Land.parentNode.removeChild(images.Land);
images.House.parentNode.removeChild(images.House);
...
Now the only part missing is drawing them on the canvas. So, lets replace this:
// Draw tile
this.ctx.fillStyle = this.type.color
this.ctx.fillRect(xPos, yPos, this.size, this.size)
By this:
// Draw tile
this.ctx.drawImage(images[this.type.name], xPos, yPos);
I am trying to develop a 2 player checkers game, for my college, but I am stuck at getting the index of the 2D array when I click on the piece.
I divided my HTML code in:
table - the game board
row - each row is the height of the array
cell - each cell is a piece and the width of the array
Then I setted a default array to start the game:
var board = [
[0, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[2, 0, 2, 0, 2, 0, 2, 0],
[0, 2, 0, 2, 0, 2, 0, 2],
[2, 0, 2, 0, 2, 0, 2, 0],
];
Where the:
- 0 - empty cell
- 1 - Player 1 pieces
- 2 - Player 2 pieces
To get the position I am using this code
function getPosition() {
$('.row').on('click', function() {
console.log( $('.row').index(this) );
});
$('.cell').on('click', function() {
console.log( $('.cell').index(this) );
});
}
Get the height array position which should be between 0 and 7 are ok, but the cell from the row should be between 0 and 7 too, but using this I am getting from 0 to 63, using this parameters I have no idea how to start the next comparisons of the game.
Here is the code from codepen
http://codepen.io/michaelthompson/pen/jVdrav
In each instance you can simply use $(this).index() which will return the index within the element's siblings
But since clicking on a row always means clicking a cell you could combine them and do
$('.cell').on('click', function() {
var $cell = $(this),
columnIndex = $cell.index(),
rowIndex = $cell.parent().index();
});
What $('.cell').index(this) is doing is taking the whole collection of the class within the page and that's why you are getting 0-63