diff --git a/assets/terrain/decoration/flowers01.png b/assets/terrain/decoration/flowers01.png new file mode 100644 index 0000000..ecab3c8 Binary files /dev/null and b/assets/terrain/decoration/flowers01.png differ diff --git a/assets/terrain/decoration/flowers02.png b/assets/terrain/decoration/flowers02.png new file mode 100644 index 0000000..93d200c Binary files /dev/null and b/assets/terrain/decoration/flowers02.png differ diff --git a/assets/terrain/decoration/grass01.png b/assets/terrain/decoration/grass01.png new file mode 100644 index 0000000..00d6e54 Binary files /dev/null and b/assets/terrain/decoration/grass01.png differ diff --git a/assets/terrain/decoration/grass02.png b/assets/terrain/decoration/grass02.png new file mode 100644 index 0000000..f24be50 Binary files /dev/null and b/assets/terrain/decoration/grass02.png differ diff --git a/src/client/game.ts b/src/client/game.ts index 4578841..31fb95f 100644 --- a/src/client/game.ts +++ b/src/client/game.ts @@ -1,5 +1,5 @@ import { Socket } from 'socket.io-client'; -import { Color } from 'three'; +import { Color, DoubleSide, MeshBasicMaterial, Vector3 } from 'three'; import { isMobileOrTablet } from '../common/helper'; import { CharacterPacket, CompositePacket } from '../common/types/packet'; import { IcyNetUser } from '../common/types/user'; @@ -16,6 +16,9 @@ import { ClientWorld } from './object/world/ClientWorld'; import { ClientWorldLoader } from './object/world/ClientWorldLoader'; import { ClientWorldManifest } from './object/world/ClientWorldManifest'; import { Renderer } from './renderer'; +import { to2D } from '../common/convert'; +import { Grass } from './object/model/grass'; +import { BaseTexture } from './object/resource/texture'; export class Game { public players: (Player | PlayerEntity)[] = []; @@ -77,6 +80,71 @@ export class Game { this.renderer.scene.add(this.world.world); this.renderer.scene.background = cube.texture; + + // test + const grasstex = await BaseTexture.load( + '/assets/terrain/decoration/grass01.png', + ); + const flowertex = await BaseTexture.load( + '/assets/terrain/decoration/flowers02.png', + ); + const flowertex2 = await BaseTexture.load( + '/assets/terrain/decoration/flowers01.png', + ); + const grassfield = Grass.getInstance().createGrassPatch( + new Vector3(10, 0, 10), + 8, + 0.5, + 8, + this.world.getInterpolatedHeight.bind(this.world), + ); + + const flowerfield = Grass.getInstance().createGrassPatch( + new Vector3(10, 0, 10), + 8, + 4, + 3, + this.world.getInterpolatedHeight.bind(this.world), + ); + + const flowerfield2 = Grass.getInstance().createGrassPatch( + new Vector3(8, 0, 8), + 4, + 4, + 3, + this.world.getInterpolatedHeight.bind(this.world), + ); + + const grass = Grass.getInstance().createInstance( + grassfield, + new MeshBasicMaterial({ + side: DoubleSide, + map: grasstex.texture, + alphaTest: 0.7, + }), + ); + + const flowers = Grass.getInstance().createInstance( + flowerfield, + new MeshBasicMaterial({ + side: DoubleSide, + map: flowertex.texture, + alphaTest: 0.7, + }), + ); + + const flowers2 = Grass.getInstance().createInstance( + flowerfield2, + new MeshBasicMaterial({ + side: DoubleSide, + map: flowertex2.texture, + alphaTest: 0.7, + }), + ); + + this.renderer.scene.add(grass); + this.renderer.scene.add(flowers); + this.renderer.scene.add(flowers2); } public dispose() { diff --git a/src/client/object/model/grass.ts b/src/client/object/model/grass.ts new file mode 100644 index 0000000..1e796e2 --- /dev/null +++ b/src/client/object/model/grass.ts @@ -0,0 +1,97 @@ +import { + BufferGeometry, + Float32BufferAttribute, + InstancedMesh, + Material, + Matrix4, + Quaternion, + Vector2, + Vector3, +} from 'three'; +import { rand } from '../../../common/helper'; + +let instance: Grass; +export class Grass { + private geom!: BufferGeometry; + initialize() { + const grassindices = [0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7]; + const grassuvs = [0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0]; + const grassverts = [ + -0.25, 0.5, 0, -0.25, 0, 0, 0.25, 0.5, 0, 0.25, 0, 0, 0, 0.5, -0.25, 0, 0, + -0.25, 0, 0.5, 0.25, 0, 0, 0.25, + ]; + + const grassnormals = Array.from({ length: grassverts.length }, (_, k) => { + return k % 2 === 0 ? 1 : 0; + }); + + this.geom = new BufferGeometry(); + this.geom.setIndex(grassindices); + this.geom.setAttribute( + 'position', + new Float32BufferAttribute(grassverts, 3), + ); + this.geom.setAttribute( + 'normal', + new Float32BufferAttribute(grassnormals, 3), + ); + this.geom.setAttribute('uv', new Float32BufferAttribute(grassuvs, 2)); + } + + public static getInstance(): Grass { + if (!instance) { + instance = new Grass(); + instance.initialize(); + } + return instance; + } + + public createInstance( + positions: Vector3[], + material: Material, + ): InstancedMesh { + const mesh = new InstancedMesh(this.geom, material, positions.length); + positions.forEach((pos, index) => { + const mat = new Matrix4(); + const quat = new Quaternion(); + quat.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI * Math.random()); + mat.compose(pos, quat, new Vector3(1, 1, 1)); + mesh.setMatrixAt(index, mat); + }); + return mesh; + } + + public createGrassPatch( + origin: Vector3, + radius: number, + density: number, + pluck: number, + getHeight: (x: number, y: number) => number, + ): Vector3[] { + const positions: Vector3[] = []; + const posc = new Vector2(origin.x, origin.z); + const grassPerUnit = 1 / density; + const actualRadius = radius / density; + // Create patch circle + for (let x = -actualRadius; x < actualRadius; x++) { + for (let y = -actualRadius; y < actualRadius; y++) { + const posi = new Vector2(origin.x + x, origin.z + y); + if (posc.distanceTo(posi) <= actualRadius) { + for (let u = 0; u < grassPerUnit; u++) { + if (pluck > 0 && rand(Math.random, 0, pluck) === 0) continue; + const pos = new Vector3( + origin.x + x * density, + 0, + origin.z + y * density, + ); + pos.x += Math.random() / 2 - 1; + pos.z += Math.random() / 2 - 1; + pos.y = getHeight(pos.x, pos.z); + positions.push(pos); + } + } + } + } + return positions; + } +} diff --git a/src/common/convert.ts b/src/common/convert.ts index 09d6991..a2e8e97 100644 --- a/src/common/convert.ts +++ b/src/common/convert.ts @@ -14,7 +14,7 @@ export function hexToString(hex: number): string { } export function to2D(index: number, size: number): { x: number; y: number } { - const y = index / size; + const y = Math.floor(index / size); const x = index % size; return { x, y }; }