mirror of
https://github.com/Floriansylvain/ThatsMyWorld.git
synced 2026-08-19 11:43:16 +02:00
feat: new terrain generation approach
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,50 +0,0 @@
|
||||
import { Object3D, Vector3 } from "three"
|
||||
import Face from "./face"
|
||||
import { degToRad } from "three/src/math/MathUtils.js"
|
||||
|
||||
function randInt(min: number, max: number): number {
|
||||
const minCeiled = Math.ceil(min)
|
||||
const maxFloored = Math.floor(max)
|
||||
return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled)
|
||||
}
|
||||
|
||||
export default class Block {
|
||||
faces: Face[]
|
||||
facesIndexes: number[] = []
|
||||
position: Vector3
|
||||
|
||||
constructor(faces: Face[], position: Vector3) {
|
||||
this.faces = faces
|
||||
this.position = position
|
||||
this.initFaces()
|
||||
}
|
||||
|
||||
setPosition(vec3: Vector3) {
|
||||
this.position = vec3
|
||||
this.updateFacesPosition()
|
||||
}
|
||||
|
||||
private updateFacesPosition() {
|
||||
this.faces.forEach((face, i) => {
|
||||
const dummy = new Object3D()
|
||||
dummy.position.add(this.position)
|
||||
dummy.updateMatrix()
|
||||
face.instancedMesh!.setMatrixAt(this.facesIndexes[i], dummy.matrix)
|
||||
face.instancedMesh!.instanceMatrix.needsUpdate = true
|
||||
})
|
||||
}
|
||||
|
||||
private initFaces() {
|
||||
this.faces.forEach((face) => {
|
||||
const dummy = new Object3D()
|
||||
dummy.position.add(this.position)
|
||||
if (face.orientation === "Up" || face.orientation === "Down") {
|
||||
dummy.rotateOnAxis(new Vector3(0, 1, 0), degToRad(90 * randInt(0, 4)))
|
||||
}
|
||||
dummy.updateMatrix()
|
||||
face.instancedMesh?.setMatrixAt(face.instaceIndex, dummy.matrix)
|
||||
this.facesIndexes.push(face.instaceIndex)
|
||||
face.instaceIndex += 1
|
||||
})
|
||||
}
|
||||
}
|
||||
-126
@@ -1,126 +0,0 @@
|
||||
import { InstancedMesh, Texture, Vector3 } from "three"
|
||||
import Face, { Orientation } from "./face"
|
||||
|
||||
const FACES_OFFSETS: { [key: string]: Vector3 } = {
|
||||
Up: new Vector3(0, 1, 0),
|
||||
North: new Vector3(0, 0, 1),
|
||||
South: new Vector3(0, 0, -1),
|
||||
East: new Vector3(-1, 0, 0),
|
||||
West: new Vector3(1, 0, 0),
|
||||
Down: new Vector3(0, -1, 0)
|
||||
}
|
||||
|
||||
interface BlockData {
|
||||
position: Vector3
|
||||
textureName: string
|
||||
}
|
||||
|
||||
export default class Chunk {
|
||||
position: Vector3
|
||||
chunk: BlockData[]
|
||||
chunkSet: Set<string>
|
||||
radius: number
|
||||
textures: { [name: string]: Texture[] }
|
||||
faces: { [key: string]: Face[] }
|
||||
|
||||
constructor(
|
||||
radius: number,
|
||||
textures: { [name: string]: Texture[] },
|
||||
position: Vector3
|
||||
) {
|
||||
this.position = position
|
||||
this.chunk = []
|
||||
this.chunkSet = new Set<string>()
|
||||
this.radius = radius
|
||||
this.textures = textures
|
||||
this.faces = {}
|
||||
}
|
||||
|
||||
async generateChunk() {
|
||||
return new Promise((resolve) => {
|
||||
for (let i = this.position.x; i < this.radius + this.position.x; i++) {
|
||||
for (let j = this.position.z; j < this.radius + this.position.z; j++) {
|
||||
const max = Math.ceil(Math.sin(i / 30) * 10 + Math.cos(j / 30) * 10)
|
||||
for (let k = -16; k <= max; k++) {
|
||||
const position = new Vector3(i, k, j)
|
||||
let textureName = "stone"
|
||||
if (k === max) textureName = "grass"
|
||||
else if (k > max - 6) textureName = "dirt"
|
||||
this.chunk.push({ position, textureName })
|
||||
this.chunkSet.add(position.toArray().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve("")
|
||||
})
|
||||
}
|
||||
|
||||
initializeFaces() {
|
||||
for (const textureName in this.textures) {
|
||||
const textureFaces = this.textures[textureName].map((texture, index) => {
|
||||
const orientation = ["Up", "North", "South", "East", "West", "Down"][
|
||||
index
|
||||
] as Orientation
|
||||
return new Face(orientation, 0, texture)
|
||||
})
|
||||
this.faces[textureName] = textureFaces
|
||||
}
|
||||
}
|
||||
|
||||
filterFaces(block: Vector3, textureName: string): Face[] {
|
||||
return this.faces[textureName].filter((face) => {
|
||||
const offset = FACES_OFFSETS[face.orientation]
|
||||
const adjacentBlock = new Vector3(
|
||||
block.x + offset.x,
|
||||
block.y + offset.y,
|
||||
block.z + offset.z
|
||||
)
|
||||
return !this.chunkSet.has(adjacentBlock.toArray().toString())
|
||||
})
|
||||
}
|
||||
|
||||
countVisibleFaces() {
|
||||
const faceCounts: { [key: string]: { [key: string]: number } } = {}
|
||||
|
||||
this.chunk.forEach(({ position, textureName }) => {
|
||||
if (!faceCounts[textureName]) {
|
||||
faceCounts[textureName] = {
|
||||
Up: 0,
|
||||
North: 0,
|
||||
South: 0,
|
||||
East: 0,
|
||||
West: 0,
|
||||
Down: 0
|
||||
}
|
||||
}
|
||||
Object.keys(FACES_OFFSETS).forEach((orientation) => {
|
||||
const offset = FACES_OFFSETS[orientation]
|
||||
const adjacentBlock = new Vector3(
|
||||
position.x + offset.x,
|
||||
position.y + offset.y,
|
||||
position.z + offset.z
|
||||
)
|
||||
if (!this.chunkSet.has(adjacentBlock.toArray().toString())) {
|
||||
faceCounts[textureName][orientation]++
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
for (const textureName in faceCounts) {
|
||||
for (const orientation in faceCounts[textureName]) {
|
||||
const face = this.faces[textureName].find(
|
||||
(f) => f.orientation === orientation
|
||||
)
|
||||
if (!face?.instancedMesh) return
|
||||
face.instancedMesh.count = faceCounts[textureName][orientation]
|
||||
face.instancedMesh = new InstancedMesh(
|
||||
face?.geometry,
|
||||
face?.material,
|
||||
faceCounts[textureName][orientation]
|
||||
) // TODO dégueulasse
|
||||
}
|
||||
}
|
||||
|
||||
return faceCounts
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
import {
|
||||
InstancedMesh,
|
||||
MeshBasicMaterial,
|
||||
PlaneGeometry,
|
||||
Texture,
|
||||
Vector3
|
||||
} from "three"
|
||||
|
||||
export type Orientation = "North" | "South" | "East" | "West" | "Up" | "Down"
|
||||
|
||||
export const DEG_RAD = Math.PI / 180
|
||||
export const FACE_ORIENTATION = [
|
||||
{
|
||||
orientation: "North",
|
||||
position: new Vector3(0, 0, 0.5),
|
||||
rotation: new Vector3(0, 0, 0)
|
||||
},
|
||||
{
|
||||
orientation: "Down",
|
||||
position: new Vector3(0, -0.5, 0),
|
||||
rotation: new Vector3(90 * DEG_RAD, 0, 0)
|
||||
},
|
||||
{
|
||||
orientation: "Up",
|
||||
position: new Vector3(0, 0.5, 0),
|
||||
rotation: new Vector3(-90 * DEG_RAD, 0, 0)
|
||||
},
|
||||
{
|
||||
orientation: "East",
|
||||
position: new Vector3(-0.5, 0, 0),
|
||||
rotation: new Vector3(0, -90 * DEG_RAD, 0)
|
||||
},
|
||||
{
|
||||
orientation: "West",
|
||||
position: new Vector3(0.5, 0, 0),
|
||||
rotation: new Vector3(0, 90 * DEG_RAD, 0)
|
||||
},
|
||||
{
|
||||
orientation: "South",
|
||||
position: new Vector3(0, 0, -0.5),
|
||||
rotation: new Vector3(0, 180 * DEG_RAD, 0)
|
||||
}
|
||||
]
|
||||
|
||||
export default class Face {
|
||||
material: MeshBasicMaterial
|
||||
orientation: Orientation
|
||||
geometry: PlaneGeometry
|
||||
instancedMesh: InstancedMesh | undefined
|
||||
instaceIndex: number = 0
|
||||
|
||||
constructor(
|
||||
orientation: Orientation,
|
||||
instancesCount: number,
|
||||
texture: Texture
|
||||
) {
|
||||
this.material = new MeshBasicMaterial({
|
||||
map: texture
|
||||
})
|
||||
|
||||
this.orientation = orientation
|
||||
|
||||
const orientationFace = FACE_ORIENTATION.find(
|
||||
(x) => x.orientation === orientation
|
||||
)
|
||||
|
||||
this.geometry = new PlaneGeometry()
|
||||
this.geometry.rotateX(orientationFace!.rotation.x)
|
||||
this.geometry.rotateY(orientationFace!.rotation.y)
|
||||
this.geometry.rotateZ(orientationFace!.rotation.z)
|
||||
this.geometry.translate(
|
||||
orientationFace!.position.x,
|
||||
orientationFace!.position.y,
|
||||
orientationFace!.position.z
|
||||
)
|
||||
|
||||
this.instancedMesh = new InstancedMesh(
|
||||
this.geometry,
|
||||
this.material,
|
||||
instancesCount
|
||||
)
|
||||
}
|
||||
}
|
||||
+76
-34
@@ -1,8 +1,6 @@
|
||||
import "./style.css"
|
||||
import * as THREE from "three"
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js"
|
||||
import Chunk from "./chunk"
|
||||
import Block from "./block"
|
||||
import TextureLoader from "./textureLoader"
|
||||
|
||||
const scene = new THREE.Scene()
|
||||
@@ -20,47 +18,92 @@ const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.update()
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
let textures: { [name: string]: THREE.Texture[] } = {}
|
||||
const blocks: {
|
||||
[name: string]: { mesh: THREE.InstancedMesh; index: number; count: number }
|
||||
} = {}
|
||||
|
||||
async function loadTextures() {
|
||||
const textureLoader = new TextureLoader(16, 16)
|
||||
textures["grass"] = await textureLoader.load("/grass.png")
|
||||
textures["dirt"] = await textureLoader.load("/dirt.png")
|
||||
textures["stone"] = await textureLoader.load("/stone.png")
|
||||
onTexturesLoaded()
|
||||
const blocksToLoad = ["grass", "dirt", "stone"]
|
||||
|
||||
async function initBlocks() {
|
||||
const textureLoader = new TextureLoader()
|
||||
for (let i = 0; i < blocksToLoad.length; i++) {
|
||||
const blockName = blocksToLoad[i]
|
||||
const textures = await textureLoader.load(`/${blockName}.png`, 16, 16)
|
||||
const geometry = new THREE.BoxGeometry(1, 1)
|
||||
const materials = textures.map((texture) => {
|
||||
return new THREE.MeshBasicMaterial({ map: texture })
|
||||
})
|
||||
const mesh = new THREE.InstancedMesh(geometry, materials, 0)
|
||||
blocks[blockName] = { mesh, index: 0, count: 0 }
|
||||
scene.add(mesh)
|
||||
}
|
||||
onBlocksInitiated()
|
||||
}
|
||||
|
||||
function onTexturesLoaded() {
|
||||
const radius = 16
|
||||
const terrain = {} as {
|
||||
[blockName: string]: { x: number; y: number; z: number }[]
|
||||
}
|
||||
|
||||
const positions = [] as THREE.Vector3[]
|
||||
for (let i = -0.5; i < 0.5; i++) {
|
||||
for (let j = -0.5; j < 0.5; j++) {
|
||||
positions.push(new THREE.Vector3(i * radius, 0, j * radius))
|
||||
function initTerrain() {
|
||||
for (let x = -256; x < 256; x++) {
|
||||
for (let z = -256; z < 256; z++) {
|
||||
for (let y = 0; y < 16; y++) {
|
||||
let blockName = "stone"
|
||||
if (y >= 11 && y < 15) {
|
||||
blockName = "dirt"
|
||||
} else if (y >= 15) {
|
||||
blockName = "grass"
|
||||
}
|
||||
if (!terrain[blockName]) terrain[blockName] = []
|
||||
terrain[blockName].push({ x, y, z })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chunks = positions.map(
|
||||
(position) => new Chunk(radius, textures, position)
|
||||
function onBlocksInitiated() {
|
||||
initTerrain()
|
||||
|
||||
for (const blockName in terrain) {
|
||||
const positions = terrain[blockName]
|
||||
|
||||
const block = blocks[blockName]
|
||||
block.count = positions.length
|
||||
// block.mesh.geometry.applyMatrix4(block.mesh.matrix)
|
||||
const newMesh = new THREE.InstancedMesh(
|
||||
block.mesh.geometry,
|
||||
block.mesh.material,
|
||||
block.count
|
||||
)
|
||||
chunks.map((chunk) =>
|
||||
chunk.generateChunk().then(() => {
|
||||
chunk.initializeFaces()
|
||||
chunk.countVisibleFaces()
|
||||
block.mesh = newMesh
|
||||
scene.add(block.mesh)
|
||||
|
||||
const cubes = [] as Block[]
|
||||
chunk.chunk.forEach(({ position, textureName }) => {
|
||||
const faces = chunk.filterFaces(position, textureName)
|
||||
cubes.push(new Block(faces, position))
|
||||
positions.forEach((position) => {
|
||||
const dummy = new THREE.Object3D()
|
||||
dummy.position.set(position.x, position.y, position.z)
|
||||
dummy.updateMatrix()
|
||||
block.mesh.setMatrixAt(block.index, dummy.matrix)
|
||||
block.index += 1
|
||||
})
|
||||
}
|
||||
|
||||
Object.values(chunk.faces).forEach((faceArray) => {
|
||||
faceArray.forEach((face) => {
|
||||
if (face.instancedMesh) scene.add(face.instancedMesh)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
// const dummy = new THREE.Object3D()
|
||||
// dummy.position.set(x, y, z)
|
||||
// dummy.updateMatrix()
|
||||
|
||||
// const block = blocks[blockName]
|
||||
// block.mesh.setMatrixAt(blocks[blockName].index, dummy.matrix)
|
||||
// block.index += 1
|
||||
// block.count += 1
|
||||
|
||||
// if (block.count > block.mesh.count - 1) {
|
||||
// const newMesh = new THREE.InstancedMesh(
|
||||
// block.mesh.geometry,
|
||||
// block.mesh.material,
|
||||
// block.mesh.count + 64
|
||||
// )
|
||||
// block.mesh = newMesh
|
||||
// }}
|
||||
}
|
||||
|
||||
camera.position.x = 15
|
||||
@@ -69,8 +112,7 @@ camera.position.z = 15
|
||||
|
||||
function animate(elapsedTimeMs: number) {
|
||||
renderer.render(scene, camera)
|
||||
console.log(renderer.info.render.calls)
|
||||
controls.update()
|
||||
}
|
||||
|
||||
loadTextures()
|
||||
initBlocks()
|
||||
|
||||
+26
-28
@@ -1,40 +1,37 @@
|
||||
import { NearestFilter, Texture } from "three";
|
||||
import { NearestFilter, Texture } from "three"
|
||||
|
||||
export default class TextureLoader {
|
||||
private image: HTMLImageElement;
|
||||
private width: number;
|
||||
private height: number;
|
||||
private image: HTMLImageElement = document.createElement("img")
|
||||
private width: number = 16
|
||||
private height: number = 16
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
this.image = document.createElement("img");
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
load(src: string): Promise<Texture[]> {
|
||||
load(src: string, width: number, heigth: number): Promise<Texture[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.image.src = src;
|
||||
this.image = document.createElement("img")
|
||||
this.image.src = src
|
||||
this.width = width
|
||||
this.height = heigth
|
||||
this.image.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
const textures = this.onImageLoaded();
|
||||
resolve(textures);
|
||||
const textures = this.onImageLoaded()
|
||||
resolve(textures)
|
||||
},
|
||||
false
|
||||
);
|
||||
this.image.addEventListener("error", (err) => reject(err), false);
|
||||
});
|
||||
)
|
||||
this.image.addEventListener("error", (err) => reject(err), false)
|
||||
})
|
||||
}
|
||||
|
||||
private onImageLoaded(): Texture[] {
|
||||
const textures = [] as Texture[];
|
||||
const textures = [] as Texture[]
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = this.width
|
||||
canvas.height = this.height
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
const context = canvas.getContext("2d")
|
||||
context?.drawImage(
|
||||
this.image,
|
||||
0,
|
||||
@@ -45,14 +42,15 @@ export default class TextureLoader {
|
||||
0,
|
||||
this.width,
|
||||
this.height
|
||||
);
|
||||
)
|
||||
|
||||
const texture = new Texture(canvas);
|
||||
texture.needsUpdate = true;
|
||||
texture.magFilter = NearestFilter;
|
||||
textures.push(texture);
|
||||
const texture = new Texture(canvas)
|
||||
texture.needsUpdate = true
|
||||
texture.magFilter = NearestFilter
|
||||
textures.push(texture)
|
||||
}
|
||||
|
||||
return textures;
|
||||
console.log(this.image.src + " loaded.")
|
||||
return textures
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user