manifest, skybox test

This commit is contained in:
Evert Prants 2022-04-11 21:11:31 +03:00
parent 6618225170
commit 3948a2f386
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
19 changed files with 179 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

View File

@ -0,0 +1,25 @@
{
"worldWidth": 1,
"worldHeight": 1,
"worldChunkSize": 256,
"worldHeightScale": 16,
"textureBombingNoise": "simplex-noise.png",
"textureSplattingSources": [
"grass-flowers.png",
"grassy.png",
"mud.png",
"path.png"
],
"regionMap": [
{
"x": 0,
"y": 0,
"splat": [
"grassy.png",
"mud.png",
"grass-flowers.png",
"path.png"
]
}
]
}

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 554 KiB

After

Width:  |  Height:  |  Size: 554 KiB

View File

@ -6,12 +6,14 @@ import { IcyNetUser } from '../common/types/user';
import { ThirdPersonCamera } from './object/camera';
import { Chat } from './object/chat';
import { Joystick } from './object/joystick';
import { CubeMap } from './object/other/cubemap';
import { VideoPlayer } from './object/other/video-player';
import { Player } from './object/player';
import { PlayerEntity } from './object/player-entity';
import modelLoaderInstance from './object/pony-loader';
import { ClientWorld } from './object/world/ClientWorld';
import { ClientWorldLoader } from './object/world/ClientWorldLoader';
import { ClientWorldManifest } from './object/world/ClientWorldManifest';
import { Renderer } from './renderer';
export class Game {
@ -24,7 +26,7 @@ export class Game {
private character: CharacterPacket = {};
private party: string[] = [];
public world = new ClientWorld(new ClientWorldLoader());
public world!: ClientWorld;
public renderer = new Renderer();
private videoTest = new VideoPlayer(24, 12);
@ -32,6 +34,11 @@ export class Game {
constructor(public socket: Socket) {}
async initialize(): Promise<void> {
const worldManifest = await ClientWorldManifest.loadManifest();
this.world = new ClientWorld(new ClientWorldLoader(), worldManifest);
const cube = await CubeMap.load('/assets/skybox/default');
await modelLoaderInstance.loadPonyModel();
await this.world.initialize();
@ -43,7 +50,8 @@ export class Game {
// experimental
this.videoTest.initialize();
this.videoTest.mesh.position.set(0, 6, -20);
this.videoTest.mesh.position.set(0, 14, 20);
this.videoTest.mesh.rotateY(Math.PI / 2);
this.renderer.scene.add(this.videoTest.mesh);
this.party = (localStorage.getItem('party')?.split('|') || []).filter(
(item) => item,
@ -64,6 +72,7 @@ export class Game {
});
this.renderer.scene.add(this.world.world);
this.renderer.scene.background = cube.texture;
}
public dispose() {

View File

@ -0,0 +1,25 @@
import { CubeTextureLoader, CubeTexture } from 'three';
const loader = new CubeTextureLoader();
export class CubeMap {
constructor(public source: string, public texture: CubeTexture) {}
public static async load(base: string): Promise<CubeMap> {
return new Promise((resolve, reject) =>
loader.load(
[
`${base}/left.png`, // pos-x
`${base}/right.png`, // neg-x
`${base}/top.png`, // pos-y
`${base}/bottom.png`, // neg-y
`${base}/front.png`, // pos-z
`${base}/back.png`, // neg-z
],
(texture) => resolve(new CubeMap(base, texture)),
undefined,
reject,
),
);
}
}

View File

@ -8,6 +8,8 @@ import { ClientWorldTexture } from './ClientWorldTexture';
// TODO: distance loading
// TODO: LOD
const BASE = '/assets/terrain/';
export class ClientWorld extends WorldManager {
public world = new Object3D();
private _mesher = new ClientWorldMesher();
@ -28,19 +30,19 @@ export class ClientWorld extends WorldManager {
async initialize() {
await this.loadWorld();
const noiseFile = `${BASE}/texture/${this.manifest.textureBombingNoise}`;
await this.loadTextureList([
noiseFile,
...this._chunks.map(
(chunk) => `/assets/terrain/splat-${chunk.x}-${chunk.y}.png`,
(chunk) => `${BASE}/region/splat-${chunk.x}-${chunk.y}.png`,
),
'/assets/terrain/texture/simplex-noise.png',
'/assets/terrain/texture/grassy.png',
'/assets/terrain/texture/mud.png',
'/assets/terrain/texture/grass-flowers.png',
'/assets/terrain/texture/path.png',
...this.gatherManifestTextures(),
]);
this._shader.initialize(
this._worldTextures.get('/assets/terrain/texture/simplex-noise.png'),
);
this._shader.initialize();
this._shader.setNoise(this._worldTextures.get(noiseFile));
this.createMeshes();
}
@ -50,7 +52,6 @@ export class ClientWorld extends WorldManager {
}
const tex = await ClientWorldTexture.loadTexture(src);
// tex.texture.repeat.set(this.worldChunkSize, this.worldChunkSize);
this._worldTextures.set(src, tex);
return tex;
}
@ -66,13 +67,8 @@ export class ClientWorld extends WorldManager {
const chunk = this._chunkMeshQueue.shift();
const material = this._shader.getShader(
chunk,
`/assets/terrain/splat-${chunk.x}-${chunk.y}.png`,
[
'/assets/terrain/texture/grassy.png',
'/assets/terrain/texture/mud.png',
'/assets/terrain/texture/grass-flowers.png',
'/assets/terrain/texture/path.png',
],
`${BASE}/region/splat-${chunk.x}-${chunk.y}.png`,
chunk.region.splat.map((file) => `${BASE}/texture/${file}`),
);
const mesh = this._mesher.createTerrainMesh(
@ -92,4 +88,11 @@ export class ClientWorld extends WorldManager {
this._chunkMeshQueue.push(chunk);
});
}
private gatherManifestTextures(): string[] {
return this.manifest.regionMap.reduce<string[]>((list, entry) => {
const paths = entry.splat.map((file) => `${BASE}/texture/${file}`);
return [...list, ...paths.filter((path) => !list.includes(path))];
}, []);
}
}

View File

@ -169,7 +169,7 @@ export class ClientWorldChunkShader {
constructor(public textureList: Map<string, ClientWorldTexture>) {}
initialize(noise: ClientWorldTexture) {
initialize() {
this.shader = new ShaderMaterial({
vertexShader: vertex,
fragmentShader: fragment,
@ -185,7 +185,7 @@ export class ClientWorldChunkShader {
UniformsLib.lights,
{
backgroundTex: { value: null, type: 't' },
noiseTex: { value: noise.texture, type: 't' },
noiseTex: { value: null, type: 't' },
rTex: { value: null, type: 't' },
gTex: { value: null, type: 't' },
bTex: { value: null, type: 't' },
@ -196,6 +196,10 @@ export class ClientWorldChunkShader {
});
}
public setNoise(noise: ClientWorldTexture) {
this.shader.uniforms.noiseTex.value = noise.texture;
}
public getShader(
chunk: WorldChunk,
splatMap: string,

View File

@ -3,14 +3,18 @@ import { to1D } from '../../../common/convert';
import { WorldLoader } from '../../../common/world/WorldLoader';
const loader = new ImageLoader();
const worldPath = '/assets/terrain/';
const worldPath = '/assets/terrain/region/';
export class ClientWorldLoader implements WorldLoader {
async loadHeightMap(chunkX: number, chunkY: number): Promise<number[]> {
async loadHeightMap(
chunkX: number,
chunkY: number,
scale: number,
): Promise<number[]> {
return new Promise((resolve, reject) => {
loader.load(
`${worldPath}/height-${chunkX}-${chunkY}.png`,
(data) => resolve(ClientWorldLoader.heightFromImage(data)),
(data) => resolve(ClientWorldLoader.heightFromImage(data, scale)),
undefined,
(err) => {
reject(err);
@ -19,7 +23,10 @@ export class ClientWorldLoader implements WorldLoader {
});
}
public static heightFromImage(image: HTMLImageElement): number[] {
public static heightFromImage(
image: HTMLImageElement,
scale: number,
): number[] {
const array = new Array(image.width * image.height);
const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = image.width;
@ -31,7 +38,7 @@ export class ClientWorldLoader implements WorldLoader {
for (let x = 0; x < image.width; x++) {
for (let y = 0; y < image.height; y++) {
const index = to1D(x, y, image.width);
array[index] = (data.data[index * 4] * 32) / 255;
array[index] = (data.data[index * 4] * scale) / 255;
}
}

View File

@ -0,0 +1,32 @@
import {
WorldManifest,
WorldManifestRegion,
} from '../../../common/world/WorldManifest';
export class ClientWorldManifest implements WorldManifest {
constructor(
public worldWidth: number,
public worldHeight: number,
public worldChunkSize: number,
public worldHeightScale: number,
public textureBombingNoise: string,
public textureSplattingSources: string[],
public regionMap: WorldManifestRegion[],
) {}
public static async loadManifest(): Promise<ClientWorldManifest> {
const file = await fetch('/assets/terrain/manifest.json');
const json = await file.json();
const manifest = new ClientWorldManifest(
json.worldWidth,
json.worldHeight,
json.worldChunkSize,
json.worldHeightScale,
json.textureBombingNoise,
json.textureSplattingSources,
json.regionMap,
);
return manifest;
}
}

View File

@ -1,6 +1,7 @@
import { Vector3, Vector2 } from 'three';
import { to1D } from '../convert';
import { barycentricPoint } from '../helper';
import { WorldManifestRegion } from './WorldManifest';
export class WorldChunk {
constructor(
@ -8,6 +9,7 @@ export class WorldChunk {
public x: number,
public y: number,
public size: number,
public region: WorldManifestRegion,
public scaledSize = size,
) {}

View File

@ -1,3 +1,7 @@
export interface WorldLoader {
loadHeightMap: (chunkX: number, chunkY: number) => Promise<number[]>;
loadHeightMap: (
chunkX: number,
chunkY: number,
scale: number,
) => Promise<number[]>;
}

View File

@ -1,25 +1,40 @@
import { to1D } from '../convert';
import { WorldChunk } from './WorldChunk';
import { WorldLoader } from './WorldLoader';
import { WorldManifest } from './WorldManifest';
export class WorldManager {
protected _chunks!: WorldChunk[];
constructor(
public loader: WorldLoader,
public worldWidth = 1,
public worldHeight = 1,
public worldChunkSize = 256,
) {
constructor(public loader: WorldLoader, public manifest: WorldManifest) {
this._chunks = new Array(this.worldWidth * this.worldHeight);
}
public get worldWidth() {
return this.manifest.worldWidth;
}
public get worldHeight() {
return this.manifest.worldHeight;
}
public get worldChunkSize() {
return this.manifest.worldChunkSize;
}
async loadHeightData(chunkX: number, chunkY: number) {
const heightData = await this.loader.loadHeightMap(chunkX, chunkY);
const heightData = await this.loader.loadHeightMap(
chunkX,
chunkY,
this.manifest.worldHeightScale,
);
this._chunks[to1D(chunkX, chunkY, this.worldWidth)] = new WorldChunk(
heightData,
chunkX, chunkY,
chunkX,
chunkY,
this.worldChunkSize,
this.manifest.regionMap.find(({ x, y }) => x === chunkX && y === chunkY),
);
}
@ -61,7 +76,9 @@ export class WorldManager {
return 0;
}
return this._chunks[to1D(chunkX, chunkY, this.worldWidth)].getInterpolatedPoint(
return this._chunks[
to1D(chunkX, chunkY, this.worldWidth)
].getInterpolatedPoint(
x - chunkX * this.worldChunkSize,
y - chunkY * this.worldChunkSize,
);

View File

@ -0,0 +1,15 @@
export interface WorldManifestRegion {
x: number;
y: number;
splat: string[];
}
export interface WorldManifest {
worldWidth: number;
worldHeight: number;
worldChunkSize: number;
worldHeightScale: number;
textureBombingNoise: string;
textureSplattingSources: string[];
regionMap: WorldManifestRegion[];
}