diff --git a/index.html b/index.html
index 44a9335..e71d871 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Vite + TS
+ ThatsMyWorld
diff --git a/package.json b/package.json
index af79b8d..a62b751 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,17 @@
"preview": "vite preview"
},
"devDependencies": {
+ "@types/three": "^0.165.0",
+ "prettier": "^3.3.1",
"typescript": "^5.2.2",
"vite": "^5.2.0"
+ },
+ "dependencies": {
+ "three": "^0.165.0"
+ },
+ "prettier": {
+ "useTabs": true,
+ "semi": false,
+ "trailingComma": "none"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 463b23d..89eb546 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,7 +7,17 @@ settings:
importers:
.:
+ dependencies:
+ '@types/three':
+ specifier: ^0.165.0
+ version: 0.165.0
+ three:
+ specifier: ^0.165.0
+ version: 0.165.0
devDependencies:
+ prettier:
+ specifier: ^3.3.1
+ version: 3.3.1
typescript:
specifier: ^5.2.2
version: 5.4.5
@@ -235,19 +245,37 @@ packages:
cpu: [x64]
os: [win32]
+ '@tweenjs/tween.js@23.1.2':
+ resolution: {integrity: sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==}
+
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+ '@types/stats.js@0.17.3':
+ resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
+
+ '@types/three@0.165.0':
+ resolution: {integrity: sha512-AJK8JZAFNBF0kBXiAIl5pggYlzAGGA8geVYQXAcPCEDRbyA+oEjkpUBcJJrtNz6IiALwzGexFJGZG2yV3WsYBw==}
+
+ '@types/webxr@0.5.16':
+ resolution: {integrity: sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA==}
+
esbuild@0.20.2:
resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
engines: {node: '>=12'}
hasBin: true
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
+ meshoptimizer@0.18.1:
+ resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
+
nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -260,6 +288,11 @@ packages:
resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
engines: {node: ^10 || ^12 || >=14}
+ prettier@3.3.1:
+ resolution: {integrity: sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
rollup@4.18.0:
resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -269,6 +302,9 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
+ three@0.165.0:
+ resolution: {integrity: sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==}
+
typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'}
@@ -421,8 +457,22 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.18.0':
optional: true
+ '@tweenjs/tween.js@23.1.2': {}
+
'@types/estree@1.0.5': {}
+ '@types/stats.js@0.17.3': {}
+
+ '@types/three@0.165.0':
+ dependencies:
+ '@tweenjs/tween.js': 23.1.2
+ '@types/stats.js': 0.17.3
+ '@types/webxr': 0.5.16
+ fflate: 0.8.2
+ meshoptimizer: 0.18.1
+
+ '@types/webxr@0.5.16': {}
+
esbuild@0.20.2:
optionalDependencies:
'@esbuild/aix-ppc64': 0.20.2
@@ -449,9 +499,13 @@ snapshots:
'@esbuild/win32-ia32': 0.20.2
'@esbuild/win32-x64': 0.20.2
+ fflate@0.8.2: {}
+
fsevents@2.3.3:
optional: true
+ meshoptimizer@0.18.1: {}
+
nanoid@3.3.7: {}
picocolors@1.0.1: {}
@@ -462,6 +516,8 @@ snapshots:
picocolors: 1.0.1
source-map-js: 1.2.0
+ prettier@3.3.1: {}
+
rollup@4.18.0:
dependencies:
'@types/estree': 1.0.5
@@ -486,6 +542,8 @@ snapshots:
source-map-js@1.2.0: {}
+ three@0.165.0: {}
+
typescript@5.4.5: {}
vite@5.2.13:
diff --git a/src/block.ts b/src/block.ts
new file mode 100644
index 0000000..7ff66d8
--- /dev/null
+++ b/src/block.ts
@@ -0,0 +1,42 @@
+import { Object3D, Vector3 } from "three"
+import Face from "./face"
+
+export default class Block {
+ faces: Face[]
+ facesIndexes: number[] = []
+ position: Vector3
+
+ constructor(faces: Face[], coordinates: Vector3) {
+ this.faces = faces
+ this.position = coordinates
+
+ this.faces.forEach((face) => {
+ const dummy = new Object3D()
+ dummy.position.add(coordinates)
+ dummy.updateMatrix()
+ face.instancedMesh?.setMatrixAt(face.instaceIndex, dummy.matrix)
+ this.facesIndexes.push(face.instaceIndex)
+ face.instaceIndex += 1
+ })
+ }
+
+ translate(vec3: Vector3) {
+ this.position.add(vec3)
+ this.updateFacesPosition()
+ }
+
+ 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
+ })
+ }
+}
diff --git a/src/counter.ts b/src/counter.ts
deleted file mode 100644
index 09e5afd..0000000
--- a/src/counter.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export function setupCounter(element: HTMLButtonElement) {
- let counter = 0
- const setCounter = (count: number) => {
- counter = count
- element.innerHTML = `count is ${counter}`
- }
- element.addEventListener('click', () => setCounter(counter + 1))
- setCounter(0)
-}
diff --git a/src/face.ts b/src/face.ts
new file mode 100644
index 0000000..7591af5
--- /dev/null
+++ b/src/face.ts
@@ -0,0 +1,77 @@
+import {
+ Color,
+ InstancedMesh,
+ MeshLambertMaterial,
+ PlaneGeometry,
+ 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: MeshLambertMaterial
+ orientation: Orientation
+ geometry: PlaneGeometry
+ instancedMesh: InstancedMesh | undefined
+ instaceIndex: number = 0
+
+ constructor(color: Color, orientation: Orientation, instancesCount: number) {
+ this.material = new MeshLambertMaterial({ color })
+
+ 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
+ )
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 791547b..5f14aaf 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,24 +1,63 @@
-import './style.css'
-import typescriptLogo from './typescript.svg'
-import viteLogo from '/vite.svg'
-import { setupCounter } from './counter.ts'
+import "./style.css"
+import * as THREE from "three"
+import { OrbitControls } from "three/addons/controls/OrbitControls.js"
+import Face from "./face"
+import Block from "./block"
-document.querySelector('#app')!.innerHTML = `
-
-
-
-
-
-
-
-
Vite + TypeScript
-
-
-
-
- Click on the Vite and TypeScript logos to learn more
-
-
-`
+const scene = new THREE.Scene()
+const camera = new THREE.PerspectiveCamera(
+ 75,
+ window.innerWidth / window.innerHeight,
+ 0.1,
+ 1000
+)
-setupCounter(document.querySelector('#counter')!)
+const renderer = new THREE.WebGLRenderer()
+renderer.setSize(window.innerWidth, window.innerHeight)
+renderer.setAnimationLoop(animate)
+const controls = new OrbitControls(camera, renderer.domElement)
+controls.update()
+document.body.appendChild(renderer.domElement)
+
+const ambientLight = new THREE.AmbientLight(new THREE.Color("white"), 5)
+scene.add(ambientLight)
+
+const chunk = [] as THREE.Vector3[]
+for (let i = -16; i < 16; i++) {
+ for (let j = -16; j < 16; j++) {
+ chunk.push(
+ new THREE.Vector3(
+ i,
+ Math.floor(Math.sin(i / 12) * 4 + Math.cos(j / 12) * 4),
+ j
+ )
+ )
+ }
+}
+
+const faces = [
+ new Face(new THREE.Color("white"), "Up", chunk.length),
+ new Face(new THREE.Color("red"), "Down", chunk.length),
+ new Face(new THREE.Color("blue"), "North", chunk.length),
+ new Face(new THREE.Color("green"), "East", chunk.length),
+ new Face(new THREE.Color("yellow"), "West", chunk.length),
+ new Face(new THREE.Color("purple"), "South", chunk.length)
+]
+
+const cubes = [] as Block[]
+chunk.forEach((block) => {
+ cubes.push(new Block(faces, block))
+})
+
+faces.forEach((face) => {
+ if (face.instancedMesh) scene.add(face.instancedMesh)
+})
+
+camera.position.x = 20
+camera.position.y = 20
+camera.position.z = 20
+
+function animate(elapsedTimeMs: number) {
+ renderer.render(scene, camera)
+ controls.update()
+}
diff --git a/src/style.css b/src/style.css
index f9c7350..34b8e5e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,96 +1,4 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
body {
+ box-sizing: border-box;
margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-#app {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.vanilla:hover {
- filter: drop-shadow(0 0 2em #3178c6aa);
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+}
\ No newline at end of file
diff --git a/src/typescript.svg b/src/typescript.svg
deleted file mode 100644
index d91c910..0000000
--- a/src/typescript.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file