change 3d scaling and add ChunkLoader dummy

This commit is contained in:
HF 2020-01-25 21:55:40 +01:00
parent 66662e49c3
commit d97e375b6d
6 changed files with 488 additions and 23 deletions

View File

@ -12,6 +12,7 @@ class Palette {
rgb: Uint8Array;
colors: Array<Color>;
abgr: Uint32Array;
fl: Array<number>;
alpha: number = 0;
constructor(colors: Array, alpha: number = 0) {
@ -20,6 +21,7 @@ class Palette {
this.rgb = new Uint8Array(this.length * 3);
this.colors = new Array(this.length);
this.abgr = new Uint32Array(this.length);
this.fl = new Array(this.length);
let cnt = 0;
for (let index = 0; index < colors.length; index++) {
@ -31,6 +33,7 @@ class Palette {
this.rgb[cnt++] = b;
this.colors[index] = `rgb(${r}, ${g}, ${b})`;
this.abgr[index] = (0xFF000000) | (b << 16) | (g << 8) | (r);
this.fl[index] = [r / 256, g / 256, b / 256];
}
}

View File

@ -85,7 +85,8 @@ class ChunkLoader {
return chunk.image;
}
return loadingTiles.getTile(canvasId);
} if (fetch) {
}
if (fetch) {
// fetch chunk
const chunkRGB = new ChunkRGB(this.palette, chunkKey);
this.chunks.set(chunkKey, chunkRGB);

86
src/ui/ChunkLoader3D.js Normal file
View File

@ -0,0 +1,86 @@
/*
* Loading 3D chunks
*
* @flow
*/
import * as THREE from 'three';
import {
THREE_CANVAS_HEIGHT,
THREE_TILE_SIZE,
} from '../core/constants';
import {
receiveBigChunk,
} from '../actions';
import Chunk from './ChunkRGB3D';
class ChunkLoader {
store = null;
canvasId: number;
palette;
chunks: Map<string, Chunk>;
constructor(store) {
console.log("Created Chunk loader");
this.store = store;
const state = store.getState();
const {
canvasId,
palette,
} = state.canvas;
this.canvasId = canvasId;
this.palette = palette;
this.chunks = new Map();
}
getVoxelUpdate(
xc: number,
zc: number,
offset: number,
color: number,
) {
const key = `${xc}:${zc}`;
const chunk = this.chunks.get(key);
if (chunk) {
/*
const offsetXZ = offset % (THREE_TILE_SIZE ** 2);
const iy = (offset - offsetXZ) / (THREE_TILE_SIZE ** 2);
const ix = offsetXZ % THREE_TILE_SIZE;
const iz = (offsetXZ - ix) / THREE_TILE_SIZE;
*/
chunk.setVoxelByOffset(offset, color);
//this.store.dispatch(receiveBigChunk(key));
}
}
getChunk(xc, zc, fetch: boolean) {
const chunkKey = `${xc}:${zc}`;
console.log(`Get chunk ${chunkKey}`);
let chunk = this.chunks.get(chunkKey);
if (chunk) {
if (chunk.ready) {
return chunk.mesh;
}
return null;
}
if (fetch) {
// fetch chunk
chunk = new Chunk(this.palette, chunkKey);
this.chunks.set(chunkKey, chunk);
this.fetchChunk(xc, zc, chunk);
}
return null;
}
async fetchChunk(xc: number, zc: number, chunk) {
const { key } = chunk;
console.log(`Fetch chunk ${key}`);
await chunk.generateSin();
this.store.dispatch(receiveBigChunk(key));
}
}
export default ChunkLoader;

View File

@ -19,7 +19,7 @@ class ChunkRGB {
// if true => chunk got requested from api/chunk and
// receives websocket pixel updates
// if false => chunk is an zoomed png tile
this.isBasechunk = true;
this.isBasechunk = false;
this.palette = palette;
this.image = document.createElement('canvas');
this.image.width = TILE_SIZE;
@ -69,7 +69,6 @@ class ChunkRGB {
*/
fromBuffer(chunkBuffer: Uint8Array) {
this.ready = true;
const imageData = new ImageData(TILE_SIZE, TILE_SIZE);
const imageView = new Uint32Array(imageData.data.buffer);
const colors = this.palette.buffer2ABGR(chunkBuffer);
@ -78,6 +77,7 @@ class ChunkRGB {
});
const ctx = this.image.getContext('2d');
ctx.putImageData(imageData, 0, 0);
this.ready = true;
}
fromImage(img: Image) {

292
src/ui/ChunkRGB3D.js Normal file
View File

@ -0,0 +1,292 @@
/*
* 3D Chunk
*
* @flow
*/
import * as THREE from 'three';
import {
THREE_TILE_SIZE,
THREE_CANVAS_HEIGHT,
} from '../core/constants';
const faceDirs = [
[-1, 0, 0],
[1, 0, 0],
[0, -1, 0],
[0, 1, 0],
[0, 0, -1],
[0, 0, 1],
];
const faceCorners = [
// left
[
[0, 1, 0],
[0, 0, 0],
[0, 1, 1],
[0, 0, 1],
],
// right
[
[1, 1, 1],
[1, 0, 1],
[1, 1, 0],
[1, 0, 0],
],
// bottom
[
[1, 0, 1],
[0, 0, 1],
[1, 0, 0],
[0, 0, 0],
],
// top
[
[0, 1, 1],
[1, 1, 1],
[0, 1, 0],
[1, 1, 0],
],
// back
[
[1, 0, 0],
[0, 0, 0],
[1, 1, 0],
[0, 1, 0],
],
// front
[
[0, 0, 1],
[1, 0, 1],
[0, 1, 1],
[1, 1, 1],
],
];
const material = new THREE.MeshLambertMaterial({
vertexColors: THREE.VertexColors,
});
class Chunk {
key: string;
ready: boolean = false;
palette: Object;
buffer: Uint8Array;
mesh: THREE.Mesh = null;
faceCnt: number;
constructor(palette, key) {
this.key = key;
this.palette = palette;
}
getVoxel(x: number, y: number, z: number) {
const { buffer } = this;
if (!buffer) return 0;
if (x < 0 || x >= THREE_TILE_SIZE || y >= THREE_CANVAS_HEIGHT
|| z < 0 || z >= THREE_TILE_SIZE)
return 0;
if (y < 0)
return 1;
// z and y are swapped in api/pixel for compatibility
// with 2D canvas
const offset = Chunk.getOffsetOfVoxel(x, y, z)
return this.buffer[offset];
}
async generateSin() {
let cnt = 0;
this.buffer = new Uint8Array(THREE_TILE_SIZE * THREE_TILE_SIZE * THREE_CANVAS_HEIGHT);
const cellSize = 64;
for (let y = 0; y < THREE_CANVAS_HEIGHT; ++y) {
for (let z = 0; z < THREE_TILE_SIZE; ++z) {
for (let x = 0; x < THREE_TILE_SIZE; ++x) {
const height = (Math.sin(x / cellSize * Math.PI * 2) + Math.sin(z / cellSize * Math.PI * 3)) * (cellSize / 6) + (cellSize / 2);
if (y < height) {
const offset = x
+ z * THREE_TILE_SIZE
+ y * THREE_TILE_SIZE * THREE_TILE_SIZE;
const clr = 1 + Math.floor(Math.random() * 31);
this.buffer[offset] = clr;
cnt += 1;
}
}
}
}
console.log(`Created buffer with ${cnt} voxels`);
this.faceCnt = Chunk.estimateNeededFaces(this.buffer);
this.renderChunk();
this.ready = true;
}
static estimateNeededFaces(buffer: Uint8Array) {
let totalCnt = 0;
let u = 0;
for (let y = 0; y < THREE_CANVAS_HEIGHT; ++y) {
for (let z = 0; z < THREE_TILE_SIZE; ++z) {
for (let x = 0; x < THREE_TILE_SIZE; ++x) {
if (buffer[u] !== 0) {
if (x === 0
|| buffer[u - 1] === 0) {
totalCnt += 1;
}
if (x === THREE_TILE_SIZE - 1
|| buffer[u + 1] === 0) {
totalCnt += 1;
}
if (z === 0
|| buffer[u - THREE_TILE_SIZE] === 0) {
totalCnt += 1;
}
if (z === THREE_TILE_SIZE - 1
|| buffer[u + THREE_TILE_SIZE] === 0) {
totalCnt += 1;
}
if (y !== 0
&& buffer[u - (THREE_TILE_SIZE ** 2)] === 0) {
totalCnt += 1;
}
if (y === THREE_CANVAS_HEIGHT - 1
|| buffer[u + (THREE_TILE_SIZE ** 2)] === 0) {
totalCnt += 1;
}
}
u += 1;
}
}
}
return totalCnt;
}
static getOffsetOfVoxel(x: number, y: number, z: number) {
return x + z * THREE_TILE_SIZE + y * THREE_TILE_SIZE * THREE_TILE_SIZE;
}
setVoxelByOffset(offset: number, clr: number) {
this.buffer[offset] = clr;
this.faceCnt += 6;
this.renderChunk();
}
setVoxel(x: number, y: number, z: number, clr: number) {
const offset = Chunk.getOffsetOfVoxel(x, y, z);
this.setVoxelByOffset(offset, clr);
}
async fromBuffer(chunkBuffer: Uint8Array) {
this.buffer = chunkBuffer;
this.renderChunk();
this.ready = true;
}
renderChunk() {
let time1 = Date.now();
let cnt = 0;
let cntv = 0;
let voxel;
const faceCnt = this.faceCnt;
const positions = new Float32Array(faceCnt * 4 * 3);
const normals = new Float32Array(faceCnt * 4 * 3);
const colors = new Uint8Array(faceCnt * 4 * 3);
const indices = new Uint32Array(faceCnt * 6);
const { rgb } = this.palette;
// just render faces that do not have an adjescent voxel
for (let y = 0; y < THREE_CANVAS_HEIGHT; ++y) {
for (let z = 0; z < THREE_TILE_SIZE; ++z) {
for (let x = 0; x < THREE_TILE_SIZE; ++x) {
voxel = this.getVoxel(x, y, z);
if (voxel !== 0) {
voxel *= 3;
cntv += 1;
for (let i = 0; i < 6; ++i) {
const dir = faceDirs[i];
const corners = faceCorners[i];
const neighbor = this.getVoxel(
x + dir[0],
y + dir[1],
z + dir[2],
);
if (neighbor === 0) {
// this voxel has no neighbor in this direction
// so we need a face
let ndx = cnt * 4 * 3;
for (let c = 0; c < 4; ++c) {
const pos = corners[c];
positions[ndx] = pos[0] + x;
normals[ndx] = dir[0];
colors[ndx++] = rgb[voxel];
positions[ndx] = pos[1] + y;
normals[ndx] = dir[1];
colors[ndx++] = rgb[voxel + 1];
positions[ndx] = pos[2] + z;
normals[ndx] = dir[2];
colors[ndx++] = rgb[voxel + 2];
}
const idx = cnt * 4;
ndx = cnt * 6;
indices[ndx++] = idx;
indices[ndx++] = idx + 1;
indices[ndx++] = idx + 2;
indices[ndx++] = idx + 2;
indices[ndx++] = idx + 1;
indices[ndx] = idx + 3;
cnt += 1;
}
}
}
}
}
}
let time2 = Date.now();
const geometry = (this.mesh)
? this.mesh.geometry
: new THREE.BufferGeometry();
geometry.setAttribute(
'position',
new THREE.BufferAttribute(
positions,
3,
),
);
geometry.setAttribute(
'normal',
new THREE.BufferAttribute(
normals,
3,
),
);
geometry.setAttribute(
'color',
new THREE.BufferAttribute(
colors,
3,
true,
),
);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.computeBoundingSphere();
geometry.setDrawRange(0, cnt * 6);
this.faceCnt = cnt;
if (!this.mesh) {
this.mesh = new THREE.Mesh(geometry, material);
this.mesh.name = this.key;
}
let time3 = Date.now();
console.log(`Created mesh for ${cntv} voxels with ${cnt} faces in ${time3 - time2}ms webgl and ${time2 - time1}ms data creation`);
}
}
export default Chunk;

View File

@ -7,6 +7,14 @@
import * as THREE from 'three';
import VoxelPainterControls from '../controls/VoxelPainterControls';
import ChunkLoader from './ChunkLoader3D';
import {
getChunkOfPixel,
getOffsetOfPixel,
} from '../core/utils';
import {
THREE_TILE_SIZE,
} from '../core/constants';
import {
setHover,
} from '../actions';
@ -23,6 +31,7 @@ class Renderer {
voxel: Object;
voxelMaterials: Array<Object>;
objects: Array<Object>;
loadedChunks: Array<Object>;
plane: Object;
//--
controls: Object;
@ -31,20 +40,25 @@ class Renderer {
mouse;
raycaster;
pressTime: number;
//--
chunkLoader: ChunkLoader = null;
forceNextRender: boolean = false;
constructor(store) {
this.store = store;
const state = store.getState();
this.objects = [];
this.loadedChunks = new Map();
this.chunkLoader = new ChunkLoader(store);
// camera
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
2000,
200,
);
camera.position.set(100, 160, 260);
camera.position.set(10, 16, 26);
camera.lookAt(0, 0, 0);
this.camera = camera;
@ -52,9 +66,11 @@ class Renderer {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
this.scene = scene;
window.scene = scene;
window.THREE = THREE;
// hover helper
const rollOverGeo = new THREE.BoxBufferGeometry(10, 10, 10);
const rollOverGeo = new THREE.BoxBufferGeometry(1, 1, 1);
const rollOverMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000,
opacity: 0.5,
@ -64,11 +80,11 @@ class Renderer {
scene.add(this.rollOverMesh);
// cubes
this.voxel = new THREE.BoxBufferGeometry(10, 10, 10);
this.voxel = new THREE.BoxBufferGeometry(1, 1, 1);
this.initCubeMaterials(state);
// grid
const gridHelper = new THREE.GridHelper(1000, 100, 0x555555, 0x555555);
const gridHelper = new THREE.GridHelper(100, 10, 0x555555, 0x555555);
scene.add(gridHelper);
//
@ -76,7 +92,7 @@ class Renderer {
this.mouse = new THREE.Vector2();
// Plane Floor
const geometry = new THREE.PlaneBufferGeometry(5000, 5000);
const geometry = new THREE.PlaneBufferGeometry(500, 500);
geometry.rotateX(-Math.PI / 2);
const plane = new THREE.Mesh(
geometry,
@ -106,8 +122,8 @@ class Renderer {
controls.enableDamping = true;
controls.dampingFactor = 0.75;
controls.maxPolarAngle = Math.PI / 2;
controls.minDistance = 100.00;
controls.maxDistance = 1000.00;
controls.minDistance = 10.00;
controls.maxDistance = 100.00;
this.controls = controls;
const { domElement } = threeRenderer;
@ -120,6 +136,8 @@ class Renderer {
domElement.addEventListener('mousedown', this.onDocumentMouseDown, false);
domElement.addEventListener('mouseup', this.onDocumentMouseUp, false);
window.addEventListener('resize', this.onWindowResize, false);
this.forceNextRender = true;
}
destructor() {
@ -147,10 +165,50 @@ class Renderer {
this.voxelMaterials = cubeMaterials;
}
reloadChunks() {
console.log('Reload Chunks');
const renderDistance = 50;
const state = this.store.getState();
const { canvasSize, view } = state.canvas;
// const [x,, z] = view;
const [x, z] = [0, 0];
const {
scene,
loadedChunks,
chunkLoader,
} = this;
const [xcMin, zcMin] = getChunkOfPixel(canvasSize, x - 50, z - 50, 0);
const [xcMax, zcMax] = getChunkOfPixel(canvasSize, x + 50, z + 50, 0);
console.log(`Get ${xcMin} - ${xcMax} - ${zcMin} - ${zcMax}`);
for (let zc = zcMin; zc <= zcMax; ++zc) {
for (let xc = xcMin; xc <= xcMax; ++xc) {
const chunkKey = `${xc}:${zc}`;
const chunk = chunkLoader.getChunk(xc, zc, true);
if (chunk) {
console.log(`Got Chunk ${chunkKey}`);
loadedChunks.set(chunkKey, chunk);
this.objects.push(chunk);
chunk.position.fromArray([
xc * THREE_TILE_SIZE - canvasSize / 2,
0,
zc * THREE_TILE_SIZE - canvasSize / 2,
]);
window.chunk = chunk;
scene.add(chunk);
console.log(`added chunk`);
}
}
}
}
render() {
if (!this.threeRenderer) {
return;
}
if (this.forceNextRender) {
this.reloadChunks();
this.forceNextRender = false;
}
this.controls.update();
this.threeRenderer.render(this.scene, this.camera);
}
@ -190,16 +248,13 @@ class Renderer {
const intersect = intersects[0];
rollOverMesh.position
.copy(intersect.point)
.add(intersect.face.normal);
.add(intersect.face.normal.multiplyScalar(0.5));
rollOverMesh.position
.divideScalar(10)
.floor()
.multiplyScalar(10)
.addScalar(5);
.addScalar(0.5);
}
const hover = rollOverMesh.position
.toArray()
.map((u) => Math.floor(u / 10));
.toArray().map((u) => Math.floor(u));
this.store.dispatch(setHover(hover));
}
@ -247,27 +302,55 @@ class Renderer {
case 0: {
// left mouse button
const state = store.getState();
const { selectedColor } = state.gui;
const { selectedColor, hover } = state.gui;
const { canvasSize } = state.canvas;
//const pos = new THREE.Vector3();
const [x, y, z] = hover;
/*
const [x, y, z] = pos.copy(intersect.point)
.add(intersect.face.normal.multiplyScalar(0.5))
.floor()
.addScalar(0.5)
.toArray();
*/
const offset = getOffsetOfPixel(canvasSize, x, z, y);
const [xc, zc] = getChunkOfPixel(canvasSize, x, z, y);
this.chunkLoader.getVoxelUpdate(xc, zc, offset, selectedColor);
/*
const newVoxel = new THREE.Mesh(
voxel,
voxelMaterials[selectedColor],
);
newVoxel.position.copy(intersect.point)
.add(intersect.face.normal);
newVoxel.position.divideScalar(10)
newVoxel.position
.copy(intersect.point)
.add(intersect.face.normal.multiplyScalar(0.5));
newVoxel.position
.floor()
.multiplyScalar(10)
.addScalar(5);
.addScalar(0.5);
scene.add(newVoxel);
objects.push(newVoxel);
*/
}
break;
case 2:
// right mouse button
const state = store.getState();
const { hover } = state.gui;
const { canvasSize } = state.canvas;
const normal = intersect.face.normal;
let [x, y, z] = hover;
x -= normal.x;
y -= normal.y;
z -= normal.z;
const offset = getOffsetOfPixel(canvasSize, x, z, y);
const [xc, zc] = getChunkOfPixel(canvasSize, x, z, y);
this.chunkLoader.getVoxelUpdate(xc, zc, offset, 0);
/*
if (intersect.object !== plane) {
scene.remove(intersect.object);
objects.splice(objects.indexOf(intersect.object), 1);
}
*/
break;
default:
break;