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; rgb: Uint8Array;
colors: Array<Color>; colors: Array<Color>;
abgr: Uint32Array; abgr: Uint32Array;
fl: Array<number>;
alpha: number = 0; alpha: number = 0;
constructor(colors: Array, alpha: number = 0) { constructor(colors: Array, alpha: number = 0) {
@ -20,6 +21,7 @@ class Palette {
this.rgb = new Uint8Array(this.length * 3); this.rgb = new Uint8Array(this.length * 3);
this.colors = new Array(this.length); this.colors = new Array(this.length);
this.abgr = new Uint32Array(this.length); this.abgr = new Uint32Array(this.length);
this.fl = new Array(this.length);
let cnt = 0; let cnt = 0;
for (let index = 0; index < colors.length; index++) { for (let index = 0; index < colors.length; index++) {
@ -31,6 +33,7 @@ class Palette {
this.rgb[cnt++] = b; this.rgb[cnt++] = b;
this.colors[index] = `rgb(${r}, ${g}, ${b})`; this.colors[index] = `rgb(${r}, ${g}, ${b})`;
this.abgr[index] = (0xFF000000) | (b << 16) | (g << 8) | (r); 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 chunk.image;
} }
return loadingTiles.getTile(canvasId); return loadingTiles.getTile(canvasId);
} if (fetch) { }
if (fetch) {
// fetch chunk // fetch chunk
const chunkRGB = new ChunkRGB(this.palette, chunkKey); const chunkRGB = new ChunkRGB(this.palette, chunkKey);
this.chunks.set(chunkKey, chunkRGB); 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 // if true => chunk got requested from api/chunk and
// receives websocket pixel updates // receives websocket pixel updates
// if false => chunk is an zoomed png tile // if false => chunk is an zoomed png tile
this.isBasechunk = true; this.isBasechunk = false;
this.palette = palette; this.palette = palette;
this.image = document.createElement('canvas'); this.image = document.createElement('canvas');
this.image.width = TILE_SIZE; this.image.width = TILE_SIZE;
@ -69,7 +69,6 @@ class ChunkRGB {
*/ */
fromBuffer(chunkBuffer: Uint8Array) { fromBuffer(chunkBuffer: Uint8Array) {
this.ready = true;
const imageData = new ImageData(TILE_SIZE, TILE_SIZE); const imageData = new ImageData(TILE_SIZE, TILE_SIZE);
const imageView = new Uint32Array(imageData.data.buffer); const imageView = new Uint32Array(imageData.data.buffer);
const colors = this.palette.buffer2ABGR(chunkBuffer); const colors = this.palette.buffer2ABGR(chunkBuffer);
@ -78,6 +77,7 @@ class ChunkRGB {
}); });
const ctx = this.image.getContext('2d'); const ctx = this.image.getContext('2d');
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
this.ready = true;
} }
fromImage(img: Image) { 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 * as THREE from 'three';
import VoxelPainterControls from '../controls/VoxelPainterControls'; import VoxelPainterControls from '../controls/VoxelPainterControls';
import ChunkLoader from './ChunkLoader3D';
import {
getChunkOfPixel,
getOffsetOfPixel,
} from '../core/utils';
import {
THREE_TILE_SIZE,
} from '../core/constants';
import { import {
setHover, setHover,
} from '../actions'; } from '../actions';
@ -23,6 +31,7 @@ class Renderer {
voxel: Object; voxel: Object;
voxelMaterials: Array<Object>; voxelMaterials: Array<Object>;
objects: Array<Object>; objects: Array<Object>;
loadedChunks: Array<Object>;
plane: Object; plane: Object;
//-- //--
controls: Object; controls: Object;
@ -31,20 +40,25 @@ class Renderer {
mouse; mouse;
raycaster; raycaster;
pressTime: number; pressTime: number;
//--
chunkLoader: ChunkLoader = null;
forceNextRender: boolean = false;
constructor(store) { constructor(store) {
this.store = store; this.store = store;
const state = store.getState(); const state = store.getState();
this.objects = []; this.objects = [];
this.loadedChunks = new Map();
this.chunkLoader = new ChunkLoader(store);
// camera // camera
const camera = new THREE.PerspectiveCamera( const camera = new THREE.PerspectiveCamera(
45, 45,
window.innerWidth / window.innerHeight, window.innerWidth / window.innerHeight,
1, 1,
2000, 200,
); );
camera.position.set(100, 160, 260); camera.position.set(10, 16, 26);
camera.lookAt(0, 0, 0); camera.lookAt(0, 0, 0);
this.camera = camera; this.camera = camera;
@ -52,9 +66,11 @@ class Renderer {
const scene = new THREE.Scene(); const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0); scene.background = new THREE.Color(0xf0f0f0);
this.scene = scene; this.scene = scene;
window.scene = scene;
window.THREE = THREE;
// hover helper // hover helper
const rollOverGeo = new THREE.BoxBufferGeometry(10, 10, 10); const rollOverGeo = new THREE.BoxBufferGeometry(1, 1, 1);
const rollOverMaterial = new THREE.MeshBasicMaterial({ const rollOverMaterial = new THREE.MeshBasicMaterial({
color: 0xff0000, color: 0xff0000,
opacity: 0.5, opacity: 0.5,
@ -64,11 +80,11 @@ class Renderer {
scene.add(this.rollOverMesh); scene.add(this.rollOverMesh);
// cubes // cubes
this.voxel = new THREE.BoxBufferGeometry(10, 10, 10); this.voxel = new THREE.BoxBufferGeometry(1, 1, 1);
this.initCubeMaterials(state); this.initCubeMaterials(state);
// grid // grid
const gridHelper = new THREE.GridHelper(1000, 100, 0x555555, 0x555555); const gridHelper = new THREE.GridHelper(100, 10, 0x555555, 0x555555);
scene.add(gridHelper); scene.add(gridHelper);
// //
@ -76,7 +92,7 @@ class Renderer {
this.mouse = new THREE.Vector2(); this.mouse = new THREE.Vector2();
// Plane Floor // Plane Floor
const geometry = new THREE.PlaneBufferGeometry(5000, 5000); const geometry = new THREE.PlaneBufferGeometry(500, 500);
geometry.rotateX(-Math.PI / 2); geometry.rotateX(-Math.PI / 2);
const plane = new THREE.Mesh( const plane = new THREE.Mesh(
geometry, geometry,
@ -106,8 +122,8 @@ class Renderer {
controls.enableDamping = true; controls.enableDamping = true;
controls.dampingFactor = 0.75; controls.dampingFactor = 0.75;
controls.maxPolarAngle = Math.PI / 2; controls.maxPolarAngle = Math.PI / 2;
controls.minDistance = 100.00; controls.minDistance = 10.00;
controls.maxDistance = 1000.00; controls.maxDistance = 100.00;
this.controls = controls; this.controls = controls;
const { domElement } = threeRenderer; const { domElement } = threeRenderer;
@ -120,6 +136,8 @@ class Renderer {
domElement.addEventListener('mousedown', this.onDocumentMouseDown, false); domElement.addEventListener('mousedown', this.onDocumentMouseDown, false);
domElement.addEventListener('mouseup', this.onDocumentMouseUp, false); domElement.addEventListener('mouseup', this.onDocumentMouseUp, false);
window.addEventListener('resize', this.onWindowResize, false); window.addEventListener('resize', this.onWindowResize, false);
this.forceNextRender = true;
} }
destructor() { destructor() {
@ -147,10 +165,50 @@ class Renderer {
this.voxelMaterials = cubeMaterials; 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() { render() {
if (!this.threeRenderer) { if (!this.threeRenderer) {
return; return;
} }
if (this.forceNextRender) {
this.reloadChunks();
this.forceNextRender = false;
}
this.controls.update(); this.controls.update();
this.threeRenderer.render(this.scene, this.camera); this.threeRenderer.render(this.scene, this.camera);
} }
@ -190,16 +248,13 @@ class Renderer {
const intersect = intersects[0]; const intersect = intersects[0];
rollOverMesh.position rollOverMesh.position
.copy(intersect.point) .copy(intersect.point)
.add(intersect.face.normal); .add(intersect.face.normal.multiplyScalar(0.5));
rollOverMesh.position rollOverMesh.position
.divideScalar(10)
.floor() .floor()
.multiplyScalar(10) .addScalar(0.5);
.addScalar(5);
} }
const hover = rollOverMesh.position const hover = rollOverMesh.position
.toArray() .toArray().map((u) => Math.floor(u));
.map((u) => Math.floor(u / 10));
this.store.dispatch(setHover(hover)); this.store.dispatch(setHover(hover));
} }
@ -247,27 +302,55 @@ class Renderer {
case 0: { case 0: {
// left mouse button // left mouse button
const state = store.getState(); 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( const newVoxel = new THREE.Mesh(
voxel, voxel,
voxelMaterials[selectedColor], voxelMaterials[selectedColor],
); );
newVoxel.position.copy(intersect.point) newVoxel.position
.add(intersect.face.normal); .copy(intersect.point)
newVoxel.position.divideScalar(10) .add(intersect.face.normal.multiplyScalar(0.5));
newVoxel.position
.floor() .floor()
.multiplyScalar(10) .addScalar(0.5);
.addScalar(5);
scene.add(newVoxel); scene.add(newVoxel);
objects.push(newVoxel); objects.push(newVoxel);
*/
} }
break; break;
case 2: case 2:
// right mouse button // 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) { if (intersect.object !== plane) {
scene.remove(intersect.object); scene.remove(intersect.object);
objects.splice(objects.indexOf(intersect.object), 1); objects.splice(objects.indexOf(intersect.object), 1);
} }
*/
break; break;
default: default:
break; break;