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 { ThirdPersonCamera } from './object/camera';
import { Chat } from './object/chat'; import { Chat } from './object/chat';
import { Joystick } from './object/joystick'; import { Joystick } from './object/joystick';
import { CubeMap } from './object/other/cubemap';
import { VideoPlayer } from './object/other/video-player'; import { VideoPlayer } from './object/other/video-player';
import { Player } from './object/player'; import { Player } from './object/player';
import { PlayerEntity } from './object/player-entity'; import { PlayerEntity } from './object/player-entity';
import modelLoaderInstance from './object/pony-loader'; import modelLoaderInstance from './object/pony-loader';
import { ClientWorld } from './object/world/ClientWorld'; import { ClientWorld } from './object/world/ClientWorld';
import { ClientWorldLoader } from './object/world/ClientWorldLoader'; import { ClientWorldLoader } from './object/world/ClientWorldLoader';
import { ClientWorldManifest } from './object/world/ClientWorldManifest';
import { Renderer } from './renderer'; import { Renderer } from './renderer';
export class Game { export class Game {
@ -24,7 +26,7 @@ export class Game {
private character: CharacterPacket = {}; private character: CharacterPacket = {};
private party: string[] = []; private party: string[] = [];
public world = new ClientWorld(new ClientWorldLoader()); public world!: ClientWorld;
public renderer = new Renderer(); public renderer = new Renderer();
private videoTest = new VideoPlayer(24, 12); private videoTest = new VideoPlayer(24, 12);
@ -32,6 +34,11 @@ export class Game {
constructor(public socket: Socket) {} constructor(public socket: Socket) {}
async initialize(): Promise<void> { 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 modelLoaderInstance.loadPonyModel();
await this.world.initialize(); await this.world.initialize();
@ -43,7 +50,8 @@ export class Game {
// experimental // experimental
this.videoTest.initialize(); 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.renderer.scene.add(this.videoTest.mesh);
this.party = (localStorage.getItem('party')?.split('|') || []).filter( this.party = (localStorage.getItem('party')?.split('|') || []).filter(
(item) => item, (item) => item,
@ -64,6 +72,7 @@ export class Game {
}); });
this.renderer.scene.add(this.world.world); this.renderer.scene.add(this.world.world);
this.renderer.scene.background = cube.texture;
} }
public dispose() { 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: distance loading
// TODO: LOD // TODO: LOD
const BASE = '/assets/terrain/';
export class ClientWorld extends WorldManager { export class ClientWorld extends WorldManager {
public world = new Object3D(); public world = new Object3D();
private _mesher = new ClientWorldMesher(); private _mesher = new ClientWorldMesher();
@ -28,19 +30,19 @@ export class ClientWorld extends WorldManager {
async initialize() { async initialize() {
await this.loadWorld(); await this.loadWorld();
const noiseFile = `${BASE}/texture/${this.manifest.textureBombingNoise}`;
await this.loadTextureList([ await this.loadTextureList([
noiseFile,
...this._chunks.map( ...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', ...this.gatherManifestTextures(),
'/assets/terrain/texture/grassy.png',
'/assets/terrain/texture/mud.png',
'/assets/terrain/texture/grass-flowers.png',
'/assets/terrain/texture/path.png',
]); ]);
this._shader.initialize(
this._worldTextures.get('/assets/terrain/texture/simplex-noise.png'), this._shader.initialize();
); this._shader.setNoise(this._worldTextures.get(noiseFile));
this.createMeshes(); this.createMeshes();
} }
@ -50,7 +52,6 @@ export class ClientWorld extends WorldManager {
} }
const tex = await ClientWorldTexture.loadTexture(src); const tex = await ClientWorldTexture.loadTexture(src);
// tex.texture.repeat.set(this.worldChunkSize, this.worldChunkSize);
this._worldTextures.set(src, tex); this._worldTextures.set(src, tex);
return tex; return tex;
} }
@ -66,13 +67,8 @@ export class ClientWorld extends WorldManager {
const chunk = this._chunkMeshQueue.shift(); const chunk = this._chunkMeshQueue.shift();
const material = this._shader.getShader( const material = this._shader.getShader(
chunk, chunk,
`/assets/terrain/splat-${chunk.x}-${chunk.y}.png`, `${BASE}/region/splat-${chunk.x}-${chunk.y}.png`,
[ chunk.region.splat.map((file) => `${BASE}/texture/${file}`),
'/assets/terrain/texture/grassy.png',
'/assets/terrain/texture/mud.png',
'/assets/terrain/texture/grass-flowers.png',
'/assets/terrain/texture/path.png',
],
); );
const mesh = this._mesher.createTerrainMesh( const mesh = this._mesher.createTerrainMesh(
@ -92,4 +88,11 @@ export class ClientWorld extends WorldManager {
this._chunkMeshQueue.push(chunk); 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>) {} constructor(public textureList: Map<string, ClientWorldTexture>) {}
initialize(noise: ClientWorldTexture) { initialize() {
this.shader = new ShaderMaterial({ this.shader = new ShaderMaterial({
vertexShader: vertex, vertexShader: vertex,
fragmentShader: fragment, fragmentShader: fragment,
@ -185,7 +185,7 @@ export class ClientWorldChunkShader {
UniformsLib.lights, UniformsLib.lights,
{ {
backgroundTex: { value: null, type: 't' }, backgroundTex: { value: null, type: 't' },
noiseTex: { value: noise.texture, type: 't' }, noiseTex: { value: null, type: 't' },
rTex: { value: null, type: 't' }, rTex: { value: null, type: 't' },
gTex: { value: null, type: 't' }, gTex: { value: null, type: 't' },
bTex: { 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( public getShader(
chunk: WorldChunk, chunk: WorldChunk,
splatMap: string, splatMap: string,

View File

@ -3,14 +3,18 @@ import { to1D } from '../../../common/convert';
import { WorldLoader } from '../../../common/world/WorldLoader'; import { WorldLoader } from '../../../common/world/WorldLoader';
const loader = new ImageLoader(); const loader = new ImageLoader();
const worldPath = '/assets/terrain/'; const worldPath = '/assets/terrain/region/';
export class ClientWorldLoader implements WorldLoader { 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) => { return new Promise((resolve, reject) => {
loader.load( loader.load(
`${worldPath}/height-${chunkX}-${chunkY}.png`, `${worldPath}/height-${chunkX}-${chunkY}.png`,
(data) => resolve(ClientWorldLoader.heightFromImage(data)), (data) => resolve(ClientWorldLoader.heightFromImage(data, scale)),
undefined, undefined,
(err) => { (err) => {
reject(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 array = new Array(image.width * image.height);
const ctx = document.createElement('canvas').getContext('2d'); const ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = image.width; ctx.canvas.width = image.width;
@ -31,7 +38,7 @@ export class ClientWorldLoader implements WorldLoader {
for (let x = 0; x < image.width; x++) { for (let x = 0; x < image.width; x++) {
for (let y = 0; y < image.height; y++) { for (let y = 0; y < image.height; y++) {
const index = to1D(x, y, image.width); 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 { Vector3, Vector2 } from 'three';
import { to1D } from '../convert'; import { to1D } from '../convert';
import { barycentricPoint } from '../helper'; import { barycentricPoint } from '../helper';
import { WorldManifestRegion } from './WorldManifest';
export class WorldChunk { export class WorldChunk {
constructor( constructor(
@ -8,6 +9,7 @@ export class WorldChunk {
public x: number, public x: number,
public y: number, public y: number,
public size: number, public size: number,
public region: WorldManifestRegion,
public scaledSize = size, public scaledSize = size,
) {} ) {}

View File

@ -1,3 +1,7 @@
export interface WorldLoader { 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 { to1D } from '../convert';
import { WorldChunk } from './WorldChunk'; import { WorldChunk } from './WorldChunk';
import { WorldLoader } from './WorldLoader'; import { WorldLoader } from './WorldLoader';
import { WorldManifest } from './WorldManifest';
export class WorldManager { export class WorldManager {
protected _chunks!: WorldChunk[]; protected _chunks!: WorldChunk[];
constructor( constructor(public loader: WorldLoader, public manifest: WorldManifest) {
public loader: WorldLoader,
public worldWidth = 1,
public worldHeight = 1,
public worldChunkSize = 256,
) {
this._chunks = new Array(this.worldWidth * this.worldHeight); 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) { 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( this._chunks[to1D(chunkX, chunkY, this.worldWidth)] = new WorldChunk(
heightData, heightData,
chunkX, chunkY, chunkX,
chunkY,
this.worldChunkSize, this.worldChunkSize,
this.manifest.regionMap.find(({ x, y }) => x === chunkX && y === chunkY),
); );
} }
@ -61,7 +76,9 @@ export class WorldManager {
return 0; return 0;
} }
return this._chunks[to1D(chunkX, chunkY, this.worldWidth)].getInterpolatedPoint( return this._chunks[
to1D(chunkX, chunkY, this.worldWidth)
].getInterpolatedPoint(
x - chunkX * this.worldChunkSize, x - chunkX * this.worldChunkSize,
y - chunkY * 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[];
}