├── .gitignore
├── README.md
├── gfx
├── blocks.png
├── crosshairs.png
└── guy.png
├── gimp
├── blocks.xcf
├── crosshairs.xcf
└── guy.xcf
├── index.html
├── save
└── .gitkeep
├── screenshots
└── 2020-04-07.jpg
├── server.sh
├── server
├── Client.js
└── main.js
├── sfx
└── jump.ogg
└── src
├── blocks.js
├── body.js
├── bone.js
├── buffer.js
├── camera.js
├── chunk.js
├── controller.js
├── crosshairs.js
├── debugger.js
├── display.js
├── generator.js
├── main.js
├── map.js
├── math.js
├── matrix.js
├── mesher.js
├── mob.js
├── model.js
├── picker.js
├── server.js
├── shader.js
├── sky.js
├── speaker.js
├── texture.js
└── vector.js
/.gitignore:
--------------------------------------------------------------------------------
1 | save/chunk_*
2 | deno
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # voxel-game
2 |
3 | This ist just another Minecraft clone for the browser. It ist made with JavaScript and WebGL.
4 | No third party libraries are used. Everything ist written entirely from scratch.
5 |
6 | The server ist build on [Deno](https://deno.land/).
7 |
8 | 
9 |
--------------------------------------------------------------------------------
/gfx/blocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gfx/blocks.png
--------------------------------------------------------------------------------
/gfx/crosshairs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gfx/crosshairs.png
--------------------------------------------------------------------------------
/gfx/guy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gfx/guy.png
--------------------------------------------------------------------------------
/gimp/blocks.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gimp/blocks.xcf
--------------------------------------------------------------------------------
/gimp/crosshairs.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gimp/crosshairs.xcf
--------------------------------------------------------------------------------
/gimp/guy.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/gimp/guy.xcf
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Voxel Game
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/save/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/save/.gitkeep
--------------------------------------------------------------------------------
/screenshots/2020-04-07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/screenshots/2020-04-07.jpg
--------------------------------------------------------------------------------
/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./deno run --allow-net --allow-read=. --allow-write=. server/main.js
--------------------------------------------------------------------------------
/server/Client.js:
--------------------------------------------------------------------------------
1 | let next_id = 0;
2 |
3 | export class Client extends EventTarget
4 | {
5 | constructor(socket, map)
6 | {
7 | super();
8 |
9 | this.socket = socket;
10 | this.map = map;
11 | this.id = create_id();
12 |
13 | socket.addEventListener("message", e => {
14 | let message = JSON.parse(e.data);
15 | this.handle_message(message);
16 | });
17 |
18 | socket.addEventListener("close", e => {
19 | this.dispatchEvent(custom_event("leave"));
20 | });
21 | }
22 |
23 | async handle_message(message)
24 | {
25 | let msg = message.msg;
26 |
27 | if(msg === 0) {
28 | let chunk = await this.get_chunk(message.x, message.y);
29 | let buf = new Uint8Array(3 * 8 + chunk.data.byteLength);
30 | let f64 = new Float64Array(buf.buffer);
31 |
32 | f64[0] = 1;
33 | f64[1] = message.x;
34 | f64[2] = message.y;
35 | buf.set(chunk.data, 3 * 8);
36 | this.socket.send(buf);
37 | }
38 | else if(msg === 2) {
39 | let x = message.x;
40 | let y = message.y;
41 | let z = message.z;
42 | let block = message.block;
43 | let chunk = this.map.setBlock(x, y, z, block);
44 | this.dispatchEvent(custom_event("setblock", {x,y,z,block}));
45 |
46 | Deno.writeFile(
47 | "./save/chunk_" + chunk.cx + "_" + chunk.cy,
48 | chunk.data
49 | );
50 | }
51 | else if(msg === 5) {
52 | let x = message.x;
53 | let y = message.y;
54 | let z = message.z;
55 | let rx = message.rx;
56 | let rz = message.rz;
57 | this.dispatchEvent(custom_event("move", {x,y,z,rx,rz}));
58 | }
59 | }
60 |
61 | async get_chunk(cx, cy)
62 | {
63 | let chunk = this.map.getChunk(cx, cy);
64 |
65 | if(chunk) {
66 | return chunk;
67 | }
68 |
69 | try {
70 | let data = await Deno.readFile("./save/chunk_" + cx + "_" + cy);
71 |
72 | return this.map.loadChunk(cx, cy, data);
73 | }
74 | catch(e) {
75 | return this.map.loadChunk(cx, cy);
76 | }
77 | }
78 |
79 | send_json(msg)
80 | {
81 | this.socket.send(JSON.stringify(msg));
82 | }
83 | }
84 |
85 | function create_id()
86 | {
87 | return next_id ++;
88 | }
89 |
90 | function custom_event(name, data)
91 | {
92 | let e = new CustomEvent(name);
93 | Object.assign(e, data);
94 | return e;
95 | }
--------------------------------------------------------------------------------
/server/main.js:
--------------------------------------------------------------------------------
1 | import Map from "../src/map.js";
2 | import {contentType} from "https://deno.land/std@0.177.0/media_types/mod.ts";
3 | import {extname} from "https://deno.land/std@0.177.0/path/mod.ts";
4 | import {Client} from "./Client.js";
5 |
6 | let map = new Map();
7 | let clients = new Set();
8 | let http_listener = Deno.listen({port: 12345});
9 | let ws_listener = Deno.listen({port: 54321});
10 |
11 | handle_http();
12 | handle_ws();
13 |
14 | async function handle_http()
15 | {
16 | console.log("Game Link http://localhost:12345/");
17 |
18 | for await(let conn of http_listener) {
19 | handle_http_conn(conn);
20 | }
21 | }
22 |
23 | async function handle_http_conn(conn)
24 | {
25 | let http_conn = Deno.serveHttp(conn);
26 |
27 | for await(let event of http_conn) {
28 | let request = event.request;
29 | let url = new URL(request.url);
30 | let path = "." + decodeURIComponent(url.pathname);
31 |
32 | if(path === "./") {
33 | path = "./index.html";
34 | }
35 |
36 | try {
37 | let file = await Deno.open(path);
38 | let stream = file.readable;
39 | let extension = extname(path);
40 | let content_type = contentType(extension);
41 | let headers = new Headers({"Content-Type": content_type});
42 | let response = new Response(stream, {headers});
43 | event.respondWith(response);
44 | }
45 | catch(error) {
46 | if(error instanceof Deno.errors.NotFound) {
47 | let response = new Response("Not Found", {status: 404});
48 | event.respondWith(response);
49 | }
50 | }
51 | }
52 | }
53 |
54 | async function handle_ws()
55 | {
56 | for await(let conn of ws_listener) {
57 | handle_ws_conn(conn);
58 | }
59 | }
60 |
61 | async function handle_ws_conn(conn)
62 | {
63 | let http_conn = Deno.serveHttp(conn);
64 |
65 | for await(let event of http_conn) {
66 | let request = event.request;
67 | try {
68 | let upgrade = Deno.upgradeWebSocket(request);
69 | let socket = upgrade.socket;
70 | let response = upgrade.response;
71 | await event.respondWith(response);
72 | return await handle_socket(socket);
73 | }
74 | catch(e) {}
75 | }
76 | }
77 |
78 | async function handle_socket(socket)
79 | {
80 | let client = new Client(socket, map);
81 |
82 | clients.add(client);
83 |
84 | clients.forEach(other => {
85 | if(other !== client) {
86 | client.send_json({msg: 3, id: other.id});
87 | other.send_json({msg: 3, id: client.id});
88 | }
89 | });
90 |
91 | client.addEventListener("leave", e => {
92 | clients.delete(client);
93 |
94 | clients.forEach(other => {
95 | other.send_json({msg: 4, id: client.id});
96 | });
97 | });
98 |
99 | client.addEventListener("setblock", e => {
100 | clients.forEach(other => {
101 | if(other !== client) {
102 | other.send_json({msg: 2, ...e});
103 | }
104 | });
105 | });
106 |
107 | client.addEventListener("move", e => {
108 | clients.forEach(other => {
109 | if(other !== client) {
110 | other.send_json({msg: 6, id: client.id, ...e});
111 | }
112 | });
113 | });
114 |
115 | return await new Promise(res => {
116 | socket.addEventListener("close", res, true);
117 | });
118 | }
--------------------------------------------------------------------------------
/sfx/jump.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/guckstift/voxel-game-js/6d53fe50dee893140b5f529320418db4ce535dd1/sfx/jump.ogg
--------------------------------------------------------------------------------
/src/blocks.js:
--------------------------------------------------------------------------------
1 | /*
2 | {
3 | name: "blockname",
4 | faces: [left, front, bottom, right, back, top]
5 | },
6 | */
7 |
8 | export default [
9 | {
10 | name: "air",
11 | transparent: true,
12 | },
13 | {
14 | name: "grass",
15 | faces: [2, 2, 1, 2, 2, 0],
16 | },
17 | {
18 | name: "dirt",
19 | faces: [1, 1, 1, 1, 1, 1],
20 | },
21 | {
22 | name: "stone",
23 | faces: [3, 3, 3, 3, 3, 3],
24 | },
25 | {
26 | name: "water",
27 | faces: [16,16,16,16,16,16],
28 | transparent: true,
29 | },
30 | {
31 | name: "sand",
32 | faces: [4, 4, 4, 4, 4, 4],
33 | },
34 | ];
35 |
--------------------------------------------------------------------------------
/src/body.js:
--------------------------------------------------------------------------------
1 | import Vector from "./vector.js";
2 | import Matrix from "./matrix.js";
3 | import {radians} from "./math.js";
4 |
5 | export default class Body
6 | {
7 | constructor(map, x, y, z, rx, rz, boxmin, boxmax)
8 | {
9 | this.map = map;
10 | this.pos = new Vector(x, y, z);
11 | this.vel = new Vector();
12 | this.acc = new Vector();
13 | this.rest = new Vector();
14 | this.rx = rx;
15 | this.rz = rz;
16 | this.boxmin = new Vector(...boxmin);
17 | this.boxmax = new Vector(...boxmax);
18 | this.mat = new Matrix();
19 | }
20 |
21 | move(vec, delta)
22 | {
23 | let deltavec = vec.clone();
24 |
25 | deltavec.scale(delta);
26 |
27 | let boxmin = this.pos.clone();
28 |
29 | boxmin.add(this.boxmin);
30 |
31 | let boxmax = this.pos.clone();
32 |
33 | boxmax.add(this.boxmax);
34 |
35 | for(let i=0; i<3; i++) {
36 | let hit = this.map.boxmarch(boxmin.data, boxmax.data, deltavec.data);
37 |
38 | if(!hit) {
39 | break;
40 | }
41 |
42 | deltavec.data[hit.axis] = hit.offs;
43 | this.rest.data[hit.axis] = hit.step;
44 | }
45 |
46 | this.pos.add(deltavec);
47 |
48 | if(deltavec.x !== 0) {
49 | this.rest.data[0] = 0;
50 | }
51 |
52 | if(deltavec.y !== 0) {
53 | this.rest.data[1] = 0;
54 | }
55 |
56 | if(deltavec.z !== 0) {
57 | this.rest.data[2] = 0;
58 | }
59 | }
60 |
61 | accel(acc, delta)
62 | {
63 | this.vel.addScaled(acc, delta);
64 | }
65 |
66 | update(delta)
67 | {
68 | this.accel(this.acc, delta);
69 | this.move(this.vel, delta);
70 |
71 | if(this.rest.x !== 0) {
72 | this.vel.data[0] = 0;
73 | }
74 |
75 | if(this.rest.y !== 0) {
76 | this.vel.data[1] = 0;
77 | }
78 |
79 | if(this.rest.z !== 0) {
80 | this.vel.data[2] = 0;
81 | }
82 |
83 | this.mat.set();
84 | this.mat.translate(this.pos.x, this.pos.y, this.pos.z);
85 | this.mat.rotateZ(radians(this.rz));
86 | this.mat.rotateX(radians(this.rx));
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/bone.js:
--------------------------------------------------------------------------------
1 | import Vector from "./vector.js";
2 | import Matrix from "./matrix.js";
3 | import {radians} from "./math.js";
4 |
5 | export default class Bone
6 | {
7 | constructor(rootX, rootY, rootZ)
8 | {
9 | this.root = new Vector(rootX, rootY, rootZ);
10 | this.rx = 0;
11 | this.ry = 0;
12 | this.rz = 0;
13 | this.mat = new Matrix();
14 | }
15 |
16 | update()
17 | {
18 | this.mat.set();
19 | this.mat.translate(this.root.x, this.root.y, this.root.z);
20 | this.mat.rotateX(radians(this.rx));
21 | this.mat.rotateX(radians(this.ry));
22 | this.mat.rotateX(radians(this.rz));
23 | this.mat.translate(-this.root.x, -this.root.y, -this.root.z);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/buffer.js:
--------------------------------------------------------------------------------
1 | export default class Buffer
2 | {
3 | constructor(display, data = null)
4 | {
5 | let gl = display.gl;
6 | let buf = gl.createBuffer();
7 |
8 | if(data) {
9 | gl.bindBuffer(gl.ARRAY_BUFFER, buf);
10 | gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
11 | }
12 |
13 | this.buf = buf;
14 | this.gl = gl;
15 | }
16 |
17 | update(data)
18 | {
19 | let gl = this.gl;
20 |
21 | gl.bindBuffer(gl.ARRAY_BUFFER, this.buf);
22 | gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/camera.js:
--------------------------------------------------------------------------------
1 | import Vector from "./vector.js";
2 | import Matrix from "./matrix.js";
3 | import {radians} from "./math.js";
4 | import Body from "./body.js";
5 |
6 | export default class Camera extends Body
7 | {
8 | constructor(map, fovy, aspect, near, far, x, y, z, rx, rz)
9 | {
10 | super(map, x, y, z, rx, rz, [-0.25, -0.25, -1.75], [+0.25, +0.25, +0.25]);
11 |
12 | this.fovy = fovy;
13 | this.aspect = aspect;
14 | this.near = near;
15 | this.far = far;
16 | this.proj = new Matrix();
17 | this.view = new Matrix();
18 | this.model = new Matrix();
19 | this.rightward = new Vector(1,0,0);
20 | this.forward = new Vector(0,1,0);
21 | this.upward = new Vector(0,0,1);
22 | this.lookat = new Vector(0,1,0);
23 | }
24 |
25 | update(delta)
26 | {
27 | super.update(delta);
28 |
29 | this.proj.perspective(radians(this.fovy), this.aspect, this.near, this.far)
30 | this.view.set();
31 | this.view.rotateX(-radians(this.rx + 90));
32 | this.view.rotateZ(-radians(this.rz));
33 | this.view.translate(-this.pos.x, -this.pos.y, -this.pos.z);
34 | this.model.set();
35 | this.model.translate(this.pos.x, this.pos.y, this.pos.z);
36 | this.rightward.set(1,0,0);
37 | this.rightward.rotateZ(radians(this.rz));
38 | this.forward.set(0,1,0);
39 | this.forward.rotateZ(radians(this.rz));
40 | this.lookat.set(0,1,0);
41 | this.lookat.rotateX(radians(this.rx));
42 | this.lookat.rotateZ(radians(this.rz));
43 | }
44 |
45 | moveForward(delta)
46 | {
47 | this.move(this.forward, +delta);
48 | }
49 |
50 | moveBackward(delta)
51 | {
52 | this.move(this.forward, -delta);
53 | }
54 |
55 | moveRightward(delta)
56 | {
57 | this.move(this.rightward, +delta);
58 | }
59 |
60 | moveLeftward(delta)
61 | {
62 | this.move(this.rightward, -delta);
63 | }
64 |
65 | moveUpward(delta)
66 | {
67 | this.move(this.upward, +delta);
68 | }
69 |
70 | moveDownward(delta)
71 | {
72 | this.move(this.upward, -delta);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/chunk.js:
--------------------------------------------------------------------------------
1 | import Shader from "./shader.js";
2 | import Texture from "./texture.js";
3 | import Buffer from "./buffer.js";
4 | import Matrix from "./matrix.js";
5 | import Generator from "./generator.js";
6 |
7 | let vert = `
8 | uniform mat4 proj;
9 | uniform mat4 view;
10 | uniform mat4 model;
11 | uniform vec3 sun;
12 | attribute vec3 pos;
13 | attribute vec3 norm;
14 | attribute vec2 uv;
15 | attribute float face;
16 | attribute float ao;
17 | varying mediump vec2 vUv;
18 | varying mediump vec2 texOffs;
19 | varying mediump float factor;
20 |
21 | void main()
22 | {
23 | gl_Position = proj * view * model * vec4(pos, 1);
24 | vUv = uv;
25 |
26 | float texX = mod(face, 16.0);
27 | float texY = floor(face / 16.0);
28 |
29 | texOffs = vec2(texX, texY) / 16.0;
30 |
31 | float diffuse = max(0.0, dot(sun, norm));
32 | float ambient = (4.0 - ao) * 0.25;
33 |
34 | factor = mix(diffuse, ambient, 0.5);
35 | }
36 | `;
37 |
38 | let frag = `
39 | uniform sampler2D tex;
40 | varying mediump vec2 vUv;
41 | varying mediump vec2 texOffs;
42 | varying mediump float factor;
43 |
44 | void main()
45 | {
46 | mediump vec2 texCoord = texOffs + fract(vUv) / 16.0;
47 |
48 | gl_FragColor = texture2D(tex, texCoord);
49 | gl_FragColor.rgb *= factor;
50 | }
51 | `;
52 |
53 | let zeroChunk = new Uint8Array(16 * 16 * 256);
54 |
55 | export default class Chunk
56 | {
57 | constructor(display, map, cx, cy)
58 | {
59 | if(display) {
60 | this.shader = display.getCached("Chunk.shader", () => new Shader(display, vert, frag));
61 | this.texture = display.getCached("Chunk.texture", () => new Texture(display, "gfx/blocks.png"));
62 | this.buffer = new Buffer(display);
63 | this.transbuf = new Buffer(display);
64 | this.gl = display.gl;
65 | }
66 |
67 | this.data = new Uint8Array(16 * 16 * 256);
68 | this.generator = new Generator();
69 | this.count = 0;
70 | this.transcount = 0;
71 | this.display = display;
72 | this.map = map;
73 | this.cx = cx;
74 | this.cy = cy;
75 | this.invalid = false;
76 | this.meshingStartTime = 0;
77 | this.model = new Matrix();
78 | this.model.translate(cx * 16, cy * 16, 0);
79 | }
80 |
81 | getBlock(x, y, z)
82 | {
83 | if(x >= 0 && y >= 0 && z >= 0 && x < 16 && y < 16 && z < 256) {
84 | return this.data[x + y * 16 + z * 16 * 16];
85 | }
86 |
87 | if(z >= 0 && z < 256) {
88 | return this.map.getBlock(this.cx * 16 + x, this.cy * 16 + y, z);
89 | }
90 |
91 | return 0;
92 | }
93 |
94 | setBlock(x, y, z, b)
95 | {
96 | if(x >= 0 && y >= 0 && z >= 0 && x < 16 && y < 16 && z < 256) {
97 | this.data[x + y * 16 + z * 16 * 16] = b;
98 | this.invalid = true;
99 |
100 | let adjacentList = [];
101 |
102 | if(x === 0) {
103 | adjacentList.push(this.map.getChunk(this.cx - 1, this.cy));
104 |
105 | if(y === 0) {
106 | adjacentList.push(this.map.getChunk(this.cx - 1, this.cy - 1));
107 | }
108 | else if(y === 15) {
109 | adjacentList.push(this.map.getChunk(this.cx - 1, this.cy + 1));
110 | }
111 | }
112 | else if(x === 15) {
113 | adjacentList.push(this.map.getChunk(this.cx + 1, this.cy));
114 |
115 | if(y === 0) {
116 | adjacentList.push(this.map.getChunk(this.cx + 1, this.cy - 1));
117 | }
118 | else if(y === 15) {
119 | adjacentList.push(this.map.getChunk(this.cx + 1, this.cy + 1));
120 | }
121 | }
122 |
123 | if(y === 0) {
124 | adjacentList.push(this.map.getChunk(this.cx, this.cy - 1));
125 | }
126 | else if(y === 15) {
127 | adjacentList.push(this.map.getChunk(this.cx, this.cy + 1));
128 | }
129 |
130 | adjacentList.forEach(chunk => {
131 | if(chunk) {
132 | chunk.invalid = true;
133 | }
134 | });
135 | }
136 | }
137 |
138 | generate()
139 | {
140 | for(let z=0, i=0; z<256; z++) {
141 | for(let y=0; y<16; y++) {
142 | for(let x=0; x<16; x++, i++) {
143 | this.data[i] = this.generator.getBlock(
144 | x + this.cx * 16,
145 | y + this.cy * 16,
146 | z,
147 | );
148 | }
149 | }
150 | }
151 |
152 | this.invalidateVicinity();
153 | }
154 |
155 | setData(data)
156 | {
157 | this.data = data;
158 | this.invalidateVicinity();
159 | }
160 |
161 | invalidateVicinity()
162 | {
163 | this.invalid = true;
164 |
165 | for(let y = -1; y <= +1; y++) {
166 | for(let x = -1; x <= +1; x++) {
167 | let chunk = this.map.getChunk(this.cx + x, this.cy + y);
168 |
169 | if(chunk) {
170 | chunk.invalid = true;
171 | }
172 | }
173 | }
174 | }
175 |
176 | getVicinity()
177 | {
178 | let chunks = [];
179 |
180 | for(let y = -1; y <= +1; y++) {
181 | for(let x = -1; x <= +1; x++) {
182 | let chunk = this.map.getChunk(this.cx + x, this.cy + y);
183 |
184 | if(chunk) {
185 | chunks.push(chunk.data);
186 | }
187 | else {
188 | chunks.push(zeroChunk);
189 | }
190 | }
191 | }
192 |
193 | return chunks;
194 | }
195 |
196 | update()
197 | {
198 | if(this.invalid) {
199 | this.meshingStartTime = performance.now();
200 | this.map.remeshChunk(this);
201 | this.invalid = false;
202 | }
203 | }
204 |
205 | applyMesh(mesh, transmesh)
206 | {
207 | this.buffer.update(new Float32Array(mesh));
208 | this.count = mesh.length / 10;
209 | this.transbuf.update(new Float32Array(transmesh));
210 | this.transcount = transmesh.length / 10;
211 |
212 | console.log("chunk mesh updated", this.cx, this.cy, "time", performance.now() - this.meshingStartTime);
213 | }
214 |
215 | draw(camera, sun, drawTrans)
216 | {
217 | let shader = this.shader;
218 | let buffer = null;
219 | let count = 0;
220 |
221 | if(drawTrans) {
222 | buffer = this.transbuf;
223 | count = this.transcount;
224 | }
225 | else {
226 | buffer = this.buffer;
227 | count = this.count;
228 | }
229 |
230 | if(count === 0) {
231 | return;
232 | }
233 |
234 | shader.assignFloatAttrib("pos", buffer, 3, 10, 0);
235 | shader.assignFloatAttrib("norm", buffer, 3, 10, 3);
236 | shader.assignFloatAttrib("uv", buffer, 2, 10, 6);
237 | shader.assignFloatAttrib("face", buffer, 1, 10, 8);
238 | shader.assignFloatAttrib("ao", buffer, 1, 10, 9);
239 | shader.use();
240 | shader.assignMatrix("proj", camera.proj);
241 | shader.assignMatrix("view", camera.view);
242 | shader.assignMatrix("model", this.model);
243 | shader.assignVector("sun", sun);
244 | shader.assignTexture("tex", this.texture, 0);
245 |
246 | this.display.drawTriangles(count);
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/controller.js:
--------------------------------------------------------------------------------
1 | import Vector from "./vector.js";
2 |
3 | export default class Controller
4 | {
5 | constructor(camera, display, picker, map, speaker)
6 | {
7 | let canvas = display.canvas;
8 |
9 | this.canvas = canvas;
10 | this.camera = camera;
11 | this.picker = picker;
12 | this.speaker = speaker;
13 | this.map = map;
14 | this.keymap = {};
15 | this.locked = false;
16 | this.movespeed = 4;
17 | this.flyspeed = 8;
18 | this.jumpspeed = 8;
19 | this.flymode = false;
20 | this.jumpsound = null;
21 | this.soundcooldown = true;
22 |
23 | speaker.loadSound("sfx/jump.ogg").then(sound => this.jumpsound = sound);
24 |
25 | window.addEventListener("keydown", e => this.keydown(e));
26 | window.addEventListener("keyup", e => this.keyup(e));
27 | canvas.addEventListener("mousedown", e => this.mousedown(e));
28 | canvas.addEventListener("mousemove", e => this.mousemove(e));
29 | document.addEventListener("pointerlockchange", e => this.lockchange(e));
30 | }
31 |
32 | getKey(e)
33 | {
34 | let key = e.key.toLowerCase();
35 |
36 | if(key === " ") {
37 | key = "space";
38 | }
39 |
40 | return key;
41 | }
42 |
43 | keydown(e)
44 | {
45 | this.speaker.activate();
46 |
47 | let key = this.getKey(e);
48 |
49 | this.keymap[key] = true;
50 | }
51 |
52 | keyup(e)
53 | {
54 | let key = this.getKey(e);
55 |
56 | this.keymap[key] = false;
57 | }
58 |
59 | mousedown(e)
60 | {
61 | this.speaker.activate();
62 |
63 | if(this.locked) {
64 | if(e.button === 0 && this.picker.hasHit) {
65 | this.map.setBlock(...this.picker.hitVox, 0);
66 | }
67 | }
68 | else {
69 | this.canvas.requestPointerLock();
70 | this.locked = true;
71 | }
72 | }
73 |
74 | mousemove(e)
75 | {
76 | if(this.locked) {
77 | this.camera.rx -= e.movementY;
78 | this.camera.rz -= e.movementX;
79 | }
80 | }
81 |
82 | lockchange(e)
83 | {
84 | if(document.pointerLockElement !== this.canvas) {
85 | this.locked = false;
86 | }
87 | }
88 |
89 | enableFly()
90 | {
91 | this.flymode = true;
92 | this.camera.acc.set(0,0,0);
93 | this.camera.vel.set(0,0,0);
94 | }
95 |
96 | disableFly()
97 | {
98 | this.flymode = false;
99 | this.camera.acc.set(0,0,-20);
100 | }
101 |
102 | update(delta)
103 | {
104 | let movespeed = this.flymode ? this.flyspeed : this.movespeed;
105 |
106 | if(this.flymode) {
107 | if(this.keymap.space) {
108 | this.camera.moveUpward(delta * movespeed);
109 | }
110 | if(this.keymap.shift) {
111 | this.camera.moveDownward(delta * movespeed);
112 | }
113 | }
114 | else {
115 | if(this.keymap.space && this.camera.rest.z < 0) {
116 | this.camera.accel(new Vector(0, 0, this.jumpspeed), 1);
117 |
118 | if(this.jumpsound && this.soundcooldown) {
119 | this.speaker.playSound(this.jumpsound);
120 | this.soundcooldown = false;
121 | setTimeout(() => this.soundcooldown = true, 500);
122 | }
123 | }
124 | }
125 |
126 | if(this.keymap.w) {
127 | this.camera.moveForward(delta * movespeed);
128 | }
129 | if(this.keymap.s) {
130 | this.camera.moveBackward(delta * movespeed);
131 | }
132 | if(this.keymap.d) {
133 | this.camera.moveRightward(delta * movespeed);
134 | }
135 | if(this.keymap.a) {
136 | this.camera.moveLeftward(delta * movespeed);
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/crosshairs.js:
--------------------------------------------------------------------------------
1 | export default class Crosshairs
2 | {
3 | constructor()
4 | {
5 | let dom = document.createElement("img");
6 |
7 | dom.src = "gfx/crosshairs.png";
8 | dom.style.display = "block";
9 | dom.style.position = "absolute";
10 | dom.style.left = "50%";
11 | dom.style.top = "50%";
12 | dom.style.transform = "translate(-50%,-50%)";
13 |
14 | this.dom = dom;
15 | }
16 |
17 | appendToBody()
18 | {
19 | document.body.appendChild(this.dom);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/debugger.js:
--------------------------------------------------------------------------------
1 | export default class Debugger
2 | {
3 | constructor(camera, map, controller, server)
4 | {
5 | let dom = document.createElement("div");
6 |
7 | dom.style.display = "none";
8 | dom.style.position = "absolute";
9 | dom.style.left = "16px";
10 | dom.style.top = "16px";
11 | dom.style.padding = "16px";
12 | dom.style.boxSizing = "border-box";
13 | dom.style.backgroundColor = "#0000ff80";
14 | dom.style.borderRadius = "8px";
15 | dom.style.color = "#fff";
16 | dom.style.fontFamily = "monospace";
17 |
18 | this.dom = dom;
19 | this.frames = 0;
20 | this.timeAccu = 0;
21 | this.last = 0;
22 | this.camera = camera;
23 | this.map = map;
24 | this.controller = controller;
25 | this.server = server;
26 | this.enabled = false;
27 |
28 | this.fpsMonitor = this.addMonitor("FPS");
29 | this.posMonitor = this.addMonitor("Position");
30 | this.chunksMonitor = this.addMonitor("Loaded chunks");
31 | this.chunkPosMonitor = this.addMonitor("Chunk position");
32 | this.chunkQuadsMonitor = this.addMonitor("Chunk quad count");
33 | this.othersMonitor = this.addMonitor("Other player IDs");
34 |
35 | window.addEventListener("keydown", e => {
36 | if(e.key === "F3") {
37 | e.preventDefault();
38 | this.toggle();
39 | }
40 | });
41 | }
42 |
43 | appendToBody()
44 | {
45 | document.body.appendChild(this.dom);
46 | }
47 |
48 | addMonitor(labelText)
49 | {
50 | let monitor = document.createElement("div");
51 | let label = document.createElement("span");
52 | let value = document.createElement("span");
53 |
54 | label.textContent = labelText + ": ";
55 | monitor.appendChild(label);
56 | monitor.appendChild(value);
57 | this.dom.appendChild(monitor);
58 |
59 | return value;
60 | }
61 |
62 | frame()
63 | {
64 | if(!this.enabled) {
65 | return;
66 | }
67 |
68 | let now = performance.now();
69 |
70 | this.last = this.last || now;
71 | this.frames ++;
72 |
73 | let delta = now - this.last;
74 |
75 | if(delta >= 1000) {
76 | let fps = this.frames * 1000 / delta;
77 |
78 | this.fpsMonitor.textContent = fps.toFixed(0);
79 | this.last = now;
80 | this.frames = 0;
81 | }
82 |
83 | this.posMonitor.textContent = `
84 | x = ${this.camera.pos.x.toFixed(2)}
85 | y = ${this.camera.pos.y.toFixed(2)}
86 | z = ${this.camera.pos.z.toFixed(2)}
87 | `;
88 |
89 | this.chunksMonitor.textContent = this.map.loadedChunks;
90 |
91 | let cx = Math.floor(this.camera.pos.x / 16);
92 | let cy = Math.floor(this.camera.pos.y / 16);
93 |
94 | this.chunkPosMonitor.textContent = `
95 | x = ${cx.toFixed(0)}
96 | y = ${cy.toFixed(0)}
97 | `;
98 |
99 | let chunk = this.map.getChunk(cx, cy);
100 |
101 | this.chunkQuadsMonitor.textContent = chunk ? chunk.count / 6 : "no chunk loaded";
102 |
103 | this.othersMonitor.textContent = Array.from(this.server.others).join(",");
104 | }
105 |
106 | enable()
107 | {
108 | this.enabled = true;
109 | this.dom.style.display = "block";
110 | this.controller.enableFly();
111 | }
112 |
113 | disable()
114 | {
115 | this.enabled = false;
116 | this.dom.style.display = "none";
117 | this.controller.disableFly();
118 | }
119 |
120 | toggle()
121 | {
122 | if(this.enabled) {
123 | this.disable();
124 | }
125 | else {
126 | this.enable();
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/display.js:
--------------------------------------------------------------------------------
1 | export default class Display
2 | {
3 | constructor()
4 | {
5 | let canvas = document.createElement("canvas");
6 |
7 | canvas.style.position = "absolute";
8 | canvas.style.left = "0";
9 | canvas.style.top = "0";
10 | canvas.style.width = "100%";
11 | canvas.style.height = "100%";
12 |
13 | let gl = canvas.getContext("webgl", {alpha: false, antialias: false});
14 |
15 | gl.enable(gl.DEPTH_TEST);
16 | gl.enable(gl.CULL_FACE);
17 | gl.enable(gl.BLEND);
18 |
19 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
20 |
21 | let self = this;
22 |
23 | requestAnimationFrame(function frame() {
24 | requestAnimationFrame(frame);
25 | self.adjustCanvasSize();
26 | self.onframe();
27 | });
28 |
29 | this.canvas = canvas;
30 | this.gl = gl;
31 | this.onframe = () => {};
32 | this.cache = {};
33 | }
34 |
35 | appendToBody()
36 | {
37 | document.body.appendChild(this.canvas);
38 | }
39 |
40 | drawTriangles(count)
41 | {
42 | let gl = this.gl;
43 |
44 | gl.drawArrays(gl.TRIANGLES, 0, count);
45 | }
46 |
47 | adjustCanvasSize()
48 | {
49 | let gl = this.gl;
50 | let canvas = this.canvas;
51 | let w = canvas.clientWidth;
52 | let h = canvas.clientHeight;
53 |
54 | if(w !== canvas.width || h !== canvas.height) {
55 | canvas.width = w;
56 | canvas.height = h;
57 | gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
58 | }
59 | }
60 |
61 | getAspect()
62 | {
63 | let canvas = this.canvas;
64 |
65 | return canvas.clientWidth / canvas.clientHeight;
66 | }
67 |
68 | getCached(id, factory)
69 | {
70 | if(!this.cache[id]) {
71 | this.cache[id] = factory();
72 | }
73 |
74 | return this.cache[id];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/generator.js:
--------------------------------------------------------------------------------
1 | export default class Generator
2 | {
3 | rand2d(x, y)
4 | {
5 | x *= 15485863; // mult with 1000000. prime
6 | y *= 285058399; // mult with 15485863. prime
7 | x += y; // add x and y together
8 | x *= 3141592653; // scramble with 10 first digits of PI
9 | x ^= x >>> 2; // xor with r-shift with 1. prime
10 | x ^= x << 5; // xor with l-shift with 3. prime
11 | x ^= x >>> 11; // xor with r-shift with 5. prime
12 | x ^= x << 17; // xor with l-shift with 7. prime
13 | x ^= x >>> 23; // xor with r-shift with 9. prime
14 | x ^= x << 31; // xor with l-shift with 11. prime
15 |
16 | return (x >>> 0) / 0xFFffFFff;
17 | }
18 |
19 | smooth(x, a, b)
20 | {
21 | return a + (3 * x ** 2 - 2 * x ** 3) * (b - a);
22 | }
23 |
24 | sample2d(x, y)
25 | {
26 | let ax = Math.floor(x);
27 | let ay = Math.floor(y);
28 | let bx = Math.ceil(x);
29 | let by = Math.ceil(y);
30 | let aa = this.rand2d(ax, ay);
31 | let ab = this.rand2d(ax, by);
32 | let ba = this.rand2d(bx, ay);
33 | let bb = this.rand2d(bx, by);
34 | let fx = x - ax;
35 | let fy = y - ay;
36 |
37 | return this.smooth(fy,
38 | this.smooth(fx, aa, ba),
39 | this.smooth(fx, ab, bb),
40 | );
41 | }
42 |
43 | getBlock(x, y, z)
44 | {
45 | let height = Math.floor(16 * this.sample2d(x / 16, y / 16));
46 |
47 | if(z < height / 3 * 2) {
48 | return 3;
49 | }
50 | else if(height <= 8 && z <= height) {
51 | return 5;
52 | }
53 | else if(z < height) {
54 | return 2;
55 | }
56 | else if(z === height) {
57 | return 1;
58 | }
59 | else if(z < 8) {
60 | return 4;
61 | }
62 | else {
63 | return 0;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Display from "./display.js";
2 | import Camera from "./camera.js";
3 | import Controller from "./controller.js";
4 | import Map from "./map.js";
5 | import Vector from "./vector.js";
6 | import {radians} from "./math.js";
7 | import Picker from "./picker.js";
8 | import Crosshairs from "./crosshairs.js";
9 | import Debugger from "./debugger.js";
10 | import Model from "./model.js";
11 | import Texture from "./texture.js";
12 | import Server from "./server.js";
13 | import Sky from "./sky.js";
14 | import Speaker from "./speaker.js";
15 | import Mob from "./mob.js";
16 |
17 | let display = new Display();
18 |
19 | display.appendToBody();
20 |
21 | let crosshairs = new Crosshairs();
22 |
23 | crosshairs.appendToBody();
24 |
25 | let server = new Server();
26 | let map = new Map(display, server);
27 |
28 | let camera = new Camera(map, 90, 800/600, 0.1, 1000, 8,8,16, -30,0);
29 |
30 | let picker = new Picker(display, map);
31 | let speaker = new Speaker();
32 | let controller = new Controller(camera, display, picker, map, speaker);
33 |
34 | let dbg = new Debugger(camera, map, controller, server);
35 |
36 | dbg.enable();
37 | dbg.appendToBody();
38 |
39 | let sun = new Vector(0,0,1);
40 |
41 | sun.rotateX(radians(30));
42 |
43 | let model = new Model(
44 | display,
45 | new Texture(display, "gfx/guy.png"),
46 | [[-0.375, 0, -0.375], [+0.375, 0, -0.375], [0, 0, -0.25]]
47 | );
48 |
49 | model.addCube([-0.25, -0.25,-0.25], [ 0.5, 0.5, 0.5], [ 0, 0], [8,8, 8], 64, 3); // head
50 | model.addCube([-0.25,-0.125, -1.0], [ 0.5,0.25,0.75], [ 0, 8], [8,4,12], 64, 0); // upper body
51 | model.addCube([ -0.5,-0.125, -1.0], [0.25,0.25,0.75], [40, 0], [4,4,12], 64, 1); // left arm
52 | model.addCube([ 0.25,-0.125, -1.0], [0.25,0.25,0.75], [40,12], [4,4,12], 64, 2); // right arm
53 | model.addCube([-0.25,-0.125,-1.75], [0.25,0.25,0.75], [ 0,20], [4,4,12], 64, 0); // left leg
54 | model.addCube([ 0,-0.125,-1.75], [0.25,0.25,0.75], [20,20], [4,4,12], 64, 0); // right leg
55 |
56 | let players = {};
57 |
58 | server.onAddPlayer = id => {
59 | players[id] = new Mob(map, 6,15,16, 0,0, [-0.25, -0.25, -1.75], [+0.25, +0.25, +0.25], model);
60 | };
61 |
62 | server.onRemovePlayer = id => {
63 | delete players[id];
64 | };
65 |
66 | server.onSetPlayerPos = (id, x, y, z, rx, rz) => {
67 | let player = players[id];
68 |
69 | if(player) {
70 | player.pos.data[0] = x;
71 | player.pos.data[1] = y;
72 | player.pos.data[2] = z;
73 | player.bones[2].rx = rx;
74 | player.rz = rz;
75 | }
76 | };
77 |
78 | let sky = new Sky(display);
79 |
80 | display.onframe = () =>
81 | {
82 | dbg.frame();
83 |
84 | let cx = Math.floor(camera.pos.x / 16);
85 | let cy = Math.floor(camera.pos.y / 16);
86 |
87 | for(let y = cy - 1; y <= cy + 1; y++) {
88 | for(let x = cx - 1; x <= cx + 1; x++) {
89 | map.loadChunk(x, y);
90 | }
91 | }
92 |
93 | controller.update(1/60);
94 |
95 | server.setMyPos(camera.pos.x, camera.pos.y, camera.pos.z, camera.rx, camera.rz);
96 |
97 | camera.aspect = display.getAspect();
98 | camera.update(1/60);
99 |
100 | picker.pick(camera.pos, camera.lookat, 16);
101 |
102 | map.update();
103 |
104 | for(let id in players) {
105 | players[id].update(1/60);
106 | }
107 |
108 | sky.draw(camera);
109 | map.draw(camera, sun);
110 |
111 | for(let id in players) {
112 | players[id].draw(camera, sun);
113 | }
114 |
115 | picker.draw(camera);
116 | };
117 |
--------------------------------------------------------------------------------
/src/map.js:
--------------------------------------------------------------------------------
1 | import Chunk from "./chunk.js";
2 |
3 | export default class Map
4 | {
5 | constructor(display, server)
6 | {
7 | if(display) {
8 | this.mesher = new Worker("src/mesher.js", {type: "module"});
9 |
10 | this.mesher.onmessage = e => {
11 | let cx = e.data.cx;
12 | let cy = e.data.cy;
13 | let chunk = this.getChunk(cx, cy);
14 |
15 | if(chunk) {
16 | chunk.applyMesh(e.data.mesh, e.data.transmesh);
17 | }
18 | };
19 | }
20 |
21 | if(server) {
22 | server.onSetChunk = (cx, cy, data) => {
23 | let chunk = this.getChunk(cx, cy);
24 | chunk.setData(data);
25 | };
26 |
27 | server.onSetBlock = (x, y, z, block) => {
28 | this.setBlock(x, y, z, block, false)
29 | };
30 | }
31 |
32 | this.chunks = {};
33 | this.display = display;
34 | this.server = server;
35 | this.loadedChunks = 0;
36 | }
37 |
38 | getChunk(cx, cy)
39 | {
40 | if(this.chunks[cy] && this.chunks[cy][cx]) {
41 | return this.chunks[cy][cx];
42 | }
43 | }
44 |
45 | loadChunk(cx, cy, data = null)
46 | {
47 | let chunk = this.getChunk(cx, cy);
48 |
49 | if(!chunk) {
50 | if(!this.chunks[cy]) {
51 | this.chunks[cy] = {};
52 | }
53 |
54 | chunk = new Chunk(this.display, this, cx, cy);
55 |
56 | if(this.server) {
57 | this.server.getChunk(cx, cy);
58 | }
59 | else if(data) {
60 | chunk.setData(data);
61 | }
62 | else {
63 | chunk.generate();
64 | }
65 |
66 | this.chunks[cy][cx] = chunk;
67 | this.loadedChunks ++;
68 | }
69 |
70 | return chunk;
71 | }
72 |
73 | remeshChunk(chunk)
74 | {
75 | let chunks = chunk.getVicinity();
76 | let cx = chunk.cx;
77 | let cy = chunk.cy;
78 |
79 | this.mesher.postMessage({chunks, cx, cy});
80 | }
81 |
82 | getBlock(x, y, z)
83 | {
84 | let cx = Math.floor(x / 16);
85 | let cy = Math.floor(y / 16);
86 | let chunk = this.getChunk(cx, cy);
87 |
88 | return chunk ? chunk.getBlock(x - cx * 16, y - cy * 16, z) : 0;
89 | }
90 |
91 | setBlock(x, y, z, b, pushToServer = true)
92 | {
93 | let cx = Math.floor(x / 16);
94 | let cy = Math.floor(y / 16);
95 | let chunk = this.getChunk(cx, cy);
96 |
97 | if(chunk) {
98 | chunk.setBlock(x - cx * 16, y - cy * 16, z, b);
99 | }
100 |
101 | if(this.server && pushToServer) {
102 | this.server.setBlock(x, y, z, b);
103 | }
104 |
105 | return chunk;
106 | }
107 |
108 | forEachChunk(fn)
109 | {
110 | for(let y in this.chunks) {
111 | if(this.chunks[y]) {
112 | for(let x in this.chunks[y]) {
113 | let chunk = this.chunks[y][x];
114 |
115 | fn(chunk, x, y);
116 | }
117 | }
118 | }
119 | }
120 |
121 | update()
122 | {
123 | this.forEachChunk(chunk => chunk.update());
124 | }
125 |
126 | draw(camera, sun)
127 | {
128 | this.forEachChunk(chunk => chunk.draw(camera, sun, false));
129 | this.forEachChunk(chunk => chunk.draw(camera, sun, true));
130 | }
131 |
132 | raymarch(start, vec)
133 | {
134 | let len = Math.sqrt(vec[0] ** 2 + vec[1] ** 2 + vec[2] ** 2);
135 | let way = 0;
136 | let axis = 0;
137 | let voxpos = [0,0,0];
138 | let step = [0,0,0];
139 | let waydelta = [0,0,0];
140 | let waynext = [0,0,0];
141 |
142 | for(let i=0; i<3; i++) {
143 | voxpos[i] = Math.floor(start[i]);
144 |
145 | if(vec[i] > 0) {
146 | waydelta[i] = +len / vec[i];
147 | waynext[i] = waydelta[i] * (voxpos[i] + 1 - start[i]);
148 | step[i] = +1;
149 | }
150 | else if(vec[i] < 0) {
151 | waydelta[i] = -len / vec[i];
152 | waynext[i] = waydelta[i] * (start[i] - voxpos[i]);
153 | step[i] = -1;
154 | }
155 | else {
156 | waynext[i] = Infinity;
157 | }
158 | }
159 |
160 | while(true) {
161 | if(waynext[0] < waynext[1] && waynext[0] < waynext[2]) {
162 | axis = 0;
163 | }
164 | else if(waynext[1] < waynext[2]) {
165 | axis = 1;
166 | }
167 | else {
168 | axis = 2;
169 | }
170 |
171 | way = waynext[axis];
172 | waynext[axis] += waydelta[axis];
173 | voxpos[axis] += step[axis];
174 |
175 | if(way >= len) {
176 | break;
177 | }
178 |
179 | if(this.getBlock(...voxpos) > 0) {
180 | return {axis, voxpos, step};
181 | }
182 | }
183 | }
184 |
185 | boxmarch(boxmin, boxmax, vec)
186 | {
187 | let len = Math.sqrt(vec[0] ** 2 + vec[1] ** 2 + vec[2] ** 2);
188 | let way = 0;
189 | let axis = 0;
190 | let slope = 0;
191 | let voxmin = [0,0,0];
192 | let voxmax = [0,0,0];
193 | let leadvox = [0,0,0];
194 | let trailvox = [0,0,0];
195 | let step = [0,0,0];
196 | let waydelta = [0,0,0];
197 | let waynext = [0,0,0];
198 |
199 | for(let i=0; i<3; i++) {
200 | voxmin[i] = Math.floor(boxmin[i]);
201 | voxmax[i] = Math.ceil(boxmax[i]) - 1;
202 |
203 | if(vec[i] > 0) {
204 | leadvox[i] = voxmax[i];
205 | trailvox[i] = voxmin[i];
206 | waydelta[i] = +len / vec[i];
207 | waynext[i] = waydelta[i] * (voxmax[i] + 1 - boxmax[i]);
208 | step[i] = +1;
209 | }
210 | else if(vec[i] < 0) {
211 | leadvox[i] = voxmin[i];
212 | trailvox[i] = voxmax[i];
213 | waydelta[i] = -len / vec[i];
214 | waynext[i] = waydelta[i] * (boxmin[i] - voxmin[i]);
215 | step[i] = -1;
216 | }
217 | else {
218 | leadvox[i] = voxmax[i];
219 | trailvox[i] = voxmin[i];
220 | waynext[i] = Infinity;
221 | step[i] = +1;
222 | }
223 | }
224 |
225 | while(true) {
226 | if(waynext[0] < waynext[1] && waynext[0] < waynext[2]) {
227 | axis = 0;
228 | }
229 | else if(waynext[1] < waynext[2]) {
230 | axis = 1;
231 | }
232 | else {
233 | axis = 2;
234 | }
235 |
236 | way = waynext[axis];
237 | waynext[axis] += waydelta[axis];
238 | leadvox[axis] += step[axis];
239 | trailvox[axis] += step[axis];
240 |
241 | if(way >= len) {
242 | break;
243 | }
244 |
245 | let xs = axis === 0 ? leadvox[0] : trailvox[0];
246 | let ys = axis === 1 ? leadvox[1] : trailvox[1];
247 | let zs = axis === 2 ? leadvox[2] : trailvox[2];
248 | let xe = leadvox[0] + step[0];
249 | let ye = leadvox[1] + step[1];
250 | let ze = leadvox[2] + step[2];
251 |
252 | for(let x = xs; x !== xe; x += step[0]) {
253 | for(let y = ys; y !== ye; y += step[1]) {
254 | for(let z = zs; z !== ze; z += step[2]) {
255 | if(this.getBlock(x, y, z) > 0) {
256 | let offs = way / len * vec[axis];
257 |
258 | return {axis, offs, step: step[axis]};
259 | }
260 | }
261 | }
262 | }
263 | }
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/math.js:
--------------------------------------------------------------------------------
1 | export function radians(d)
2 | {
3 | return d * Math.PI / 180;
4 | }
5 |
--------------------------------------------------------------------------------
/src/matrix.js:
--------------------------------------------------------------------------------
1 | export default class Matrix
2 | {
3 | constructor()
4 | {
5 | this.data = [
6 | 1,0,0,0,
7 | 0,1,0,0,
8 | 0,0,1,0,
9 | 0,0,0,1,
10 | ];
11 | }
12 |
13 | set(
14 | m00 = 1, m01 = 0, m02 = 0, m03 = 0,
15 | m10 = 0, m11 = 1, m12 = 0, m13 = 0,
16 | m20 = 0, m21 = 0, m22 = 1, m23 = 0,
17 | m30 = 0, m31 = 0, m32 = 0, m33 = 1,
18 | ) {
19 | let m = this.data;
20 |
21 | m[0] = m00;
22 | m[1] = m01;
23 | m[2] = m02;
24 | m[3] = m03;
25 | m[4] = m10;
26 | m[5] = m11;
27 | m[6] = m12;
28 | m[7] = m13;
29 | m[8] = m20;
30 | m[9] = m21;
31 | m[10] = m22;
32 | m[11] = m23;
33 | m[12] = m30;
34 | m[13] = m31;
35 | m[14] = m32;
36 | m[15] = m33;
37 | }
38 |
39 | perspective(fovy, aspect, near, far)
40 | {
41 | let fy = 1 / Math.tan(fovy / 2);
42 | let fx = fy / aspect;
43 | let nf = 1 / (near - far);
44 | let a = (near + far) * nf;
45 | let b = 2 * near * far * nf;
46 |
47 | this.set(
48 | fx, 0, 0, 0,
49 | 0,fy, 0, 0,
50 | 0, 0, a,-1,
51 | 0, 0, b, 0,
52 | );
53 | }
54 |
55 | translate(x, y, z)
56 | {
57 | let m = this.data;
58 |
59 | m[12] += x * m[0] + y * m[4] + z * m[8];
60 | m[13] += x * m[1] + y * m[5] + z * m[9];
61 | m[14] += x * m[2] + y * m[6] + z * m[10];
62 | m[15] += x * m[3] + y * m[7] + z * m[11];
63 | }
64 |
65 | rotateX(a)
66 | {
67 | let m = this.data;
68 | let s = Math.sin(a);
69 | let c = Math.cos(a);
70 | let a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
71 | let a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
72 |
73 | m[4] = c * a10 + s * a20;
74 | m[5] = c * a11 + s * a21;
75 | m[6] = c * a12 + s * a22;
76 | m[7] = c * a13 + s * a23;
77 | m[8] = -s * a10 + c * a20;
78 | m[9] = -s * a11 + c * a21;
79 | m[10] = -s * a12 + c * a22;
80 | m[11] = -s * a13 + c * a23;
81 | }
82 |
83 | rotateY(a)
84 | {
85 | let m = this.data;
86 | let s = Math.sin(a);
87 | let c = Math.cos(a);
88 | let a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
89 | let a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
90 |
91 | m[0] = c * a00 + s * a20;
92 | m[1] = c * a01 + s * a21;
93 | m[2] = c * a02 + s * a22;
94 | m[3] = c * a03 + s * a23;
95 | m[8] = -s * a00 + c * a20;
96 | m[9] = -s * a01 + c * a21;
97 | m[10] = -s * a02 + c * a22;
98 | m[11] = -s * a03 + c * a23;
99 | }
100 |
101 | rotateZ(a)
102 | {
103 | let m = this.data;
104 | let s = Math.sin(a);
105 | let c = Math.cos(a);
106 | let a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
107 | let a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
108 |
109 | m[0] = c * a00 + s * a10;
110 | m[1] = c * a01 + s * a11;
111 | m[2] = c * a02 + s * a12;
112 | m[3] = c * a03 + s * a13;
113 | m[4] = -s * a00 + c * a10;
114 | m[5] = -s * a01 + c * a11;
115 | m[6] = -s * a02 + c * a12;
116 | m[7] = -s * a03 + c * a13;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/mesher.js:
--------------------------------------------------------------------------------
1 | import blocks from "./blocks.js";
2 |
3 | let chunks = null;
4 |
5 | onmessage = e => {
6 | chunks = e.data.chunks;
7 |
8 | let cx = e.data.cx;
9 | let cy = e.data.cy;
10 | let mesh = remesh(false);
11 | let transmesh = remesh(true);
12 |
13 | postMessage({mesh, transmesh, cx, cy});
14 | };
15 |
16 | function getBlock(x, y, z)
17 | {
18 | if(z < 0 || z > 255) {
19 | return 0;
20 | }
21 |
22 | let cx = Math.floor(x / 16);
23 | let cy = Math.floor(y / 16);
24 | let ci = (cx + 1) + (cy + 1) * 3;
25 | let ch = chunks[ci];
26 |
27 | x -= cx * 16;
28 | y -= cy * 16;
29 |
30 | return ch[x + y * 16 + z * 16 * 16];
31 | }
32 |
33 | function remesh(meshTrans)
34 | {
35 | let mesh = [];
36 |
37 | remeshSide(mesh, -1, 0, 0, 0, meshTrans, // left
38 | 1, 2, 0, false, true, false,
39 | [-1,+1,+1], [-1, 0,+1], [-1,-1,+1],
40 | [-1,+1, 0], [-1,-1, 0],
41 | [-1,+1,-1], [-1, 0,-1], [-1,-1,-1]);
42 | remeshSide(mesh, 0,-1, 0, 1, meshTrans, // front
43 | 0, 2, 1, false, false, false,
44 | [-1,-1,+1], [ 0,-1,+1], [+1,-1,+1],
45 | [-1,-1, 0], [+1,-1, 0],
46 | [-1,-1,-1], [ 0,-1,-1], [+1,-1,-1]);
47 | remeshSide(mesh, 0, 0,-1, 2, meshTrans, // bottom
48 | 0, 1, 2, false, true, false,
49 | [-1,-1,-1], [ 0,-1,-1], [+1,-1,-1],
50 | [-1, 0,-1], [+1, 0,-1],
51 | [-1,+1,-1], [ 0,+1,-1], [+1,+1,-1]);
52 | remeshSide(mesh, +1, 0, 0, 3, meshTrans, // right
53 | 1, 2, 0, false, false, false,
54 | [+1,-1,+1], [+1, 0,+1], [+1,+1,+1],
55 | [+1,-1, 0], [+1,+1, 0],
56 | [+1,-1,-1], [+1, 0,-1], [+1,+1,-1]);
57 | remeshSide(mesh, 0,+1, 0, 4, meshTrans, // back
58 | 0, 2, 1, true, false, false,
59 | [+1,+1,+1], [ 0,+1,+1], [-1,+1,+1],
60 | [+1,+1, 0], [-1,+1, 0],
61 | [+1,+1,-1], [ 0,+1,-1], [-1,+1,-1]);
62 | remeshSide(mesh, 0, 0,+1, 5, meshTrans, // top
63 | 0, 1, 2, false, false, false,
64 | [-1,+1,+1], [ 0,+1,+1], [+1,+1,+1],
65 | [-1, 0,+1], [+1, 0,+1],
66 | [-1,-1,+1], [ 0,-1,+1], [+1,-1,+1]);
67 |
68 | return mesh;
69 | }
70 |
71 | function remeshSide(
72 | mesh, nx, ny, nz, fid, meshTrans,
73 | ax0, ax1, ax2, fx, fy, fz, aov0, aov1, aov2, aov3, aov4, aov5, aov6, aov7
74 | ) {
75 | let map = [];
76 |
77 | for(let z=0, i=0; z<256; z++) {
78 | for(let y=0; y<16; y++) {
79 | for(let x=0; x<16; x++, i++) {
80 | let block = getBlock(x, y, z);
81 | let adjacent = getBlock(x + nx, y + ny, z + nz);
82 | let transparent = blocks[block].transparent || false;
83 | let adjTrans = blocks[adjacent].transparent || false;
84 |
85 | if(meshTrans) {
86 | map[i] = (
87 | block > 0 && transparent === true && adjacent !== block && adjTrans === true
88 | ? block
89 | : 0
90 | );
91 | }
92 | else {
93 | map[i] = (
94 | block > 0 && transparent === false && adjTrans === true
95 | ? block
96 | : 0
97 | );
98 | }
99 |
100 | if(map[i] > 0) {
101 | let ao0 = getOcclusion(
102 | [x + aov3[0], y + aov3[1], z + aov3[2]],
103 | [x + aov5[0], y + aov5[1], z + aov5[2]],
104 | [x + aov6[0], y + aov6[1], z + aov6[2]],
105 | );
106 |
107 | let ao1 = getOcclusion(
108 | [x + aov4[0], y + aov4[1], z + aov4[2]],
109 | [x + aov7[0], y + aov7[1], z + aov7[2]],
110 | [x + aov6[0], y + aov6[1], z + aov6[2]],
111 | );
112 |
113 | let ao2 = getOcclusion(
114 | [x + aov3[0], y + aov3[1], z + aov3[2]],
115 | [x + aov0[0], y + aov0[1], z + aov0[2]],
116 | [x + aov1[0], y + aov1[1], z + aov1[2]],
117 | );
118 |
119 | let ao3 = getOcclusion(
120 | [x + aov4[0], y + aov4[1], z + aov4[2]],
121 | [x + aov2[0], y + aov2[1], z + aov2[2]],
122 | [x + aov1[0], y + aov1[1], z + aov1[2]],
123 | );
124 |
125 | map[i] |= ao0 << 8 | ao1 << 10 | ao2 << 12 | ao3 << 14;
126 | }
127 | }
128 | }
129 | }
130 |
131 | let a = [fx ? 15 : 0, fy ? 15 : 0, fz ? 255 : 0];
132 | let b = [fx ? -1 : 16, fy ? -1 : 16, fz ? -1 : 256];
133 | let s = [fx ? -1 : +1, fy ? -1 : +1, fz ? -1 : +1];
134 | let i = [0,0,0];
135 | let j = [0,0,0];
136 | let k = [0,0,0];
137 |
138 | for(i[ax2] = a[ax2]; i[ax2] !== b[ax2]; i[ax2] += s[ax2]) {
139 | for(i[ax1] = a[ax1]; i[ax1] !== b[ax1]; i[ax1] += s[ax1]) {
140 | for(i[ax0] = a[ax0]; i[ax0] !== b[ax0]; i[ax0] += s[ax0]) {
141 | let I = i[0] + i[1] * 16 + i[2] * 16 * 16;
142 | let val = map[I];
143 |
144 | if(val > 0) {
145 | j[0] = i[0];
146 | j[1] = i[1];
147 | j[2] = i[2];
148 | k[0] = i[0];
149 | k[1] = i[1];
150 | k[2] = i[2];
151 |
152 | for(j[ax0] = i[ax0] + s[ax0]; j[ax0] !== b[ax0]; j[ax0] += s[ax0]) {
153 | let J = j[0] + j[1] * 16 + j[2] * 16 * 16;
154 |
155 | if(map[J] !== val) {
156 | break;
157 | }
158 |
159 | map[J] = 0;
160 | }
161 |
162 | outer:
163 | for(k[ax1] = i[ax1] + s[ax1]; k[ax1] !== b[ax1]; k[ax1] += s[ax1]) {
164 | for(k[ax0] = i[ax0]; k[ax0] !== j[ax0]; k[ax0] += s[ax0]) {
165 | let K = k[0] + k[1] * 16 + k[2] * 16 * 16;
166 |
167 | if(map[K] !== val) {
168 | break outer;
169 | }
170 | }
171 |
172 | for(k[ax0] = i[ax0]; k[ax0] !== j[ax0]; k[ax0] += s[ax0]) {
173 | let K = k[0] + k[1] * 16 + k[2] * 16 * 16;
174 |
175 | map[K] = 0;
176 | }
177 | }
178 |
179 | let block = val & 0xff;
180 | let face = blocks[block].faces[fid];
181 | let ao0 = val >> 8 & 3;
182 | let ao1 = val >> 10 & 3;
183 | let ao2 = val >> 12 & 3;
184 | let ao3 = val >> 14 & 3;
185 | let quadw = (j[ax0] - i[ax0]) * s[ax0];
186 | let quadh = (k[ax1] - i[ax1]) * s[ax1];
187 | let v0 = [...i, nx, ny, nz, 0, quadh, face, ao0];
188 | let v1 = [...i, nx, ny, nz, quadw, quadh, face, ao1];
189 | let v2 = [...i, nx, ny, nz, 0, 0, face, ao2];
190 | let v3 = [...i, nx, ny, nz, quadw, 0, face, ao3];
191 |
192 | v1[ax0] = j[ax0];
193 | v3[ax0] = j[ax0];
194 | v2[ax1] = k[ax1];
195 | v3[ax1] = k[ax1];
196 |
197 | if(s[ax0] < 0) {
198 | v0[ax0] ++;
199 | v1[ax0] ++;
200 | v2[ax0] ++;
201 | v3[ax0] ++;
202 | }
203 |
204 | if(s[ax1] < 0) {
205 | v0[ax1] ++;
206 | v1[ax1] ++;
207 | v2[ax1] ++;
208 | v3[ax1] ++;
209 | }
210 |
211 | if(nx > 0 || ny > 0 || nz > 0) {
212 | v0[ax2] ++;
213 | v1[ax2] ++;
214 | v2[ax2] ++;
215 | v3[ax2] ++;
216 | }
217 |
218 | if(ao0 + ao3 < ao1 + ao2) {
219 | mesh.push(...v1, ...v3, ...v0, ...v0, ...v3, ...v2);
220 | }
221 | else {
222 | mesh.push(...v0, ...v1, ...v2, ...v2, ...v1, ...v3);
223 | }
224 | }
225 | }
226 | }
227 | }
228 | }
229 |
230 | function getOcclusion(p0, p1, p2)
231 | {
232 | let ao0 = getBlock(...p0) > 0 ? 1 : 0;
233 | let ao1 = getBlock(...p1) > 0 ? 1 : 0;
234 | let ao2 = getBlock(...p2) > 0 ? 1 : 0;
235 |
236 | return ao0 > 0 && ao2 > 0 ? 3 : ao0 + ao1 + ao2;
237 | }
238 |
--------------------------------------------------------------------------------
/src/mob.js:
--------------------------------------------------------------------------------
1 | import Body from "./body.js";
2 | import Bone from "./bone.js";
3 |
4 | export default class Mob extends Body
5 | {
6 | constructor(map, x, y, z, rx, rz, boxmin, boxmax, model)
7 | {
8 | super(map, x, y, z, rx, rz, boxmin, boxmax);
9 |
10 | this.model = model;
11 | this.bones = model.roots.map(root => new Bone(...root));
12 | }
13 |
14 | update(delta)
15 | {
16 | super.update(delta);
17 |
18 | this.model.update();
19 | this.bones.forEach(b => b.update());
20 | }
21 |
22 | draw(camera, sun)
23 | {
24 | this.model.draw(camera, sun, this.mat, this.bones.map(b => b.mat));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/model.js:
--------------------------------------------------------------------------------
1 | import Shader from "./shader.js";
2 | import Buffer from "./buffer.js";
3 |
4 | let vert = `
5 | uniform mat4 proj;
6 | uniform mat4 view;
7 | uniform mat4 model;
8 | uniform vec3 sun;
9 | uniform mat4 bones[16];
10 | attribute vec3 pos;
11 | attribute vec3 norm;
12 | attribute vec2 uv;
13 | attribute float bone;
14 | varying mediump vec2 vUv;
15 | varying mediump float factor;
16 |
17 | void main()
18 | {
19 | gl_Position = vec4(pos, 1);
20 |
21 | vec4 normal = vec4(norm, 0);
22 |
23 | for(int i=0; i<16; i++) {
24 | if(i + 1 == int(bone)) {
25 | gl_Position = bones[i] * gl_Position;
26 | normal = bones[i] * normal;
27 | }
28 | }
29 |
30 | normal = model * normal;
31 |
32 | gl_Position = proj * view * model * gl_Position;
33 | vUv = uv;
34 |
35 | float diffuse = max(0.0, dot(sun, normal.xyz));
36 | float ambient = 1.0;
37 |
38 | factor = mix(diffuse, ambient, 0.5);
39 | }
40 | `;
41 |
42 | let frag = `
43 | uniform sampler2D tex;
44 | varying mediump vec2 vUv;
45 | varying mediump float factor;
46 |
47 | void main()
48 | {
49 | gl_FragColor = texture2D(tex, vUv);
50 | gl_FragColor.rgb *= factor;
51 | }
52 | `;
53 |
54 | export default class Model
55 | {
56 | constructor(display, texture, roots)
57 | {
58 | this.gl = display.gl;
59 | this.display = display;
60 | this.shader = display.getCached("Model.shader", () => new Shader(display, vert, frag));
61 | this.texture = texture;
62 | this.buffer = new Buffer(display);
63 | this.mesh = [];
64 | this.invalid = false;
65 | this.boneCount = 0;
66 | this.roots = roots;
67 | }
68 |
69 | addQuad(v0, v1, v2, v3, n, uvStart, uvSize, bone)
70 | {
71 | let uv00 = [uvStart[0], uvStart[1] + uvSize[1]];
72 | let uv01 = [uvStart[0], uvStart[1]];
73 | let uv10 = [uvStart[0] + uvSize[0], uvStart[1] + uvSize[1]];
74 | let uv11 = [uvStart[0] + uvSize[0], uvStart[1]];
75 |
76 | this.mesh.push(...v0, ...n, ...uv00, bone);
77 | this.mesh.push(...v1, ...n, ...uv10, bone);
78 | this.mesh.push(...v2, ...n, ...uv01, bone);
79 | this.mesh.push(...v2, ...n, ...uv01, bone);
80 | this.mesh.push(...v1, ...n, ...uv10, bone);
81 | this.mesh.push(...v3, ...n, ...uv11, bone);
82 |
83 | this.invalid = true;
84 | this.boneCount = Math.max(this.boneCount, bone);
85 | }
86 |
87 | addCube(start, size, texpos, texbox, div, bone)
88 | {
89 | let end = [start[0] + size[0], start[1] + size[1], start[2] + size[2]];
90 | let v000 = start;
91 | let v001 = [start[0], start[1], end[2]];
92 | let v010 = [start[0], end[1], start[2]];
93 | let v011 = [start[0], end[1], end[2]];
94 | let v100 = [end[0], start[1], start[2]];
95 | let v101 = [end[0], start[1], end[2]];
96 | let v110 = [end[0], end[1], start[2]];
97 | let v111 = end;
98 | let u = texpos[0];
99 | let v = texpos[1];
100 | let sx = texbox[0];
101 | let sy = texbox[1];
102 | let sz = texbox[2];
103 |
104 | // left
105 | this.addQuad(v010, v000, v011, v001, [-1, 0, 0], [ (2*sx+sy+u)/div, v/div], [sy/div, sz/div], bone);
106 |
107 | // front
108 | this.addQuad(v000, v100, v001, v101, [ 0,-1, 0], [ u/div, v/div], [sx/div, sz/div], bone);
109 |
110 | // bottom
111 | this.addQuad(v010, v110, v000, v100, [ 0, 0,-1], [(2*sx+2*sy+u)/div, (sy+v)/div], [sx/div, sy/div], bone);
112 |
113 | // right
114 | this.addQuad(v100, v110, v101, v111, [+1, 0, 0], [ (sx+u)/div, v/div], [sy/div, sz/div], bone);
115 |
116 | // back
117 | this.addQuad(v110, v010, v111, v011, [ 0,+1, 0], [ (sx+sy+u)/div, v/div], [sx/div, sz/div], bone);
118 |
119 | // top
120 | this.addQuad(v001, v101, v011, v111, [ 0, 0,+1], [(2*sx+2*sy+u)/div, v/div], [sx/div, sy/div], bone);
121 | }
122 |
123 | update()
124 | {
125 | if(this.invalid) {
126 | this.buffer.update(new Float32Array(this.mesh));
127 | this.invalid = false;
128 | }
129 | }
130 |
131 | draw(camera, sun, modelMat, bones)
132 | {
133 | let shader = this.shader;
134 | let buffer = this.buffer;
135 |
136 | shader.assignFloatAttrib("pos", buffer, 3, 9, 0);
137 | shader.assignFloatAttrib("norm", buffer, 3, 9, 3);
138 | shader.assignFloatAttrib("uv", buffer, 2, 9, 6);
139 | shader.assignFloatAttrib("bone", buffer, 1, 9, 8);
140 | shader.use();
141 | shader.assignMatrix("proj", camera.proj);
142 | shader.assignMatrix("view", camera.view);
143 | shader.assignMatrix("model", modelMat);
144 | shader.assignMatrices("bones", bones);
145 | shader.assignVector("sun", sun);
146 | shader.assignTexture("tex", this.texture, 0);
147 |
148 | this.display.drawTriangles(this.mesh.length / 9);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/picker.js:
--------------------------------------------------------------------------------
1 | import Shader from "./shader.js";
2 | import Buffer from "./buffer.js";
3 | import Matrix from "./matrix.js";
4 | import {radians} from "./math.js";
5 |
6 | let vert = `
7 | uniform mat4 proj;
8 | uniform mat4 view;
9 | uniform mat4 model;
10 | attribute vec3 pos;
11 | varying mediump vec3 vPos;
12 |
13 | void main()
14 | {
15 | gl_Position = proj * view * model * vec4(pos, 1);
16 | vPos = pos;
17 | }
18 | `;
19 |
20 | let frag = `
21 | varying mediump vec3 vPos;
22 |
23 | void main()
24 | {
25 | if(vPos.x < 0.0625 || vPos.x > 0.9375 || vPos.y < 0.0625 || vPos.y > 0.9375) {
26 | gl_FragColor = vec4(1, 1, 1, 0.5);
27 | }
28 | else {
29 | gl_FragColor = vec4(0);
30 | }
31 | }
32 | `;
33 |
34 | export default class Picker
35 | {
36 | constructor(display, map)
37 | {
38 | this.display = display;
39 | this.map = map;
40 | this.shader = new Shader(display, vert, frag);
41 | this.buffer = new Buffer(display, new Float32Array([0,0,0, 1,0,0, 0,1,0, 0,1,0, 1,0,0, 1,1,0]));
42 | this.model = new Matrix();
43 | this.hasHit = false;
44 | this.hitVox = null;
45 | }
46 |
47 | pick(pos, vec, len)
48 | {
49 | let scaledVec = vec.clone();
50 |
51 | scaledVec.scale(len);
52 |
53 | let hit = this.map.raymarch(pos.data, scaledVec.data);
54 |
55 | if(hit) {
56 | this.hasHit = true;
57 | this.hitVox = hit.voxpos;
58 | this.model.set();
59 | this.model.translate(...hit.voxpos);
60 |
61 | if(hit.axis === 0) {
62 | if(hit.step[0] < 0) {
63 | this.model.translate(1,0,0);
64 | }
65 |
66 | this.model.rotateY(radians(90));
67 | }
68 | else if(hit.axis === 1) {
69 | if(hit.step[1] < 0) {
70 | this.model.translate(0,1,0);
71 | }
72 |
73 | this.model.rotateX(radians(90));
74 | }
75 | else {
76 | if(hit.step[2] < 0) {
77 | this.model.translate(0,0,1);
78 | }
79 | }
80 | }
81 | else {
82 | this.hasHit = false;
83 | }
84 | }
85 |
86 | draw(camera)
87 | {
88 | if(this.hasHit) {
89 | let shader = this.shader;
90 | let gl = this.display.gl;
91 |
92 | shader.assignFloatAttrib("pos", this.buffer, 3, 3, 0);
93 | shader.use();
94 | shader.assignMatrix("proj", camera.proj);
95 | shader.assignMatrix("view", camera.view);
96 | shader.assignMatrix("model", this.model);
97 |
98 | gl.disable(gl.DEPTH_TEST);
99 | gl.disable(gl.CULL_FACE);
100 |
101 | this.display.drawTriangles(6);
102 |
103 | gl.enable(gl.DEPTH_TEST);
104 | gl.enable(gl.CULL_FACE);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | export default class Server
2 | {
3 | constructor()
4 | {
5 | this.others = new Set();
6 |
7 | this.socket = new WebSocket(
8 | "ws://" + window.location.hostname + ":54321"
9 | );
10 |
11 | this.socket.onopen = e => {
12 | this.isopen = true;
13 | this.queue.forEach(fn => fn());
14 | this.queue = [];
15 | };
16 |
17 | this.socket.onmessage = e => {
18 | if(typeof e.data === "string") {
19 | let msg = JSON.parse(e.data);
20 |
21 | if(msg.msg === 2) {
22 | this.onSetBlock(msg.x, msg.y, msg.z, msg.block);
23 | }
24 | else if(msg.msg === 3) {
25 | this.others.add(msg.id);
26 | this.onAddPlayer(msg.id);
27 | }
28 | else if(msg.msg === 4) {
29 | this.others.delete(msg.id);
30 | this.onRemovePlayer(msg.id);
31 | }
32 | else if(msg.msg === 6) {
33 | this.onSetPlayerPos(msg.id, msg.x, msg.y, msg.z, msg.rx, msg.rz);
34 | }
35 | }
36 | else {
37 | e.data.arrayBuffer().then(buf => {
38 | let f64 = new Float64Array(buf);
39 | let msg = f64[0];
40 |
41 | if(msg === 1) {
42 | this.onSetChunk(f64[1], f64[2], new Uint8Array(buf, 3 * 8));
43 | }
44 | });
45 | }
46 | };
47 |
48 | this.isopen = false;
49 | this.queue = [];
50 | this.onSetChunk = () => {};
51 | this.onSetBlock = () => {};
52 | this.onAddPlayer = () => {};
53 | this.onRemovePlayer = () => {};
54 | this.onSetPlayerPos = () => {};
55 | }
56 |
57 | send(msg)
58 | {
59 | if(this.isopen) {
60 | this.socket.send(JSON.stringify(msg));
61 | }
62 | else {
63 | this.queue.push(() => this.send(msg));
64 | }
65 | }
66 |
67 | getChunk(x, y)
68 | {
69 | this.send({msg: 0, x, y});
70 | }
71 |
72 | setBlock(x, y, z, block)
73 | {
74 | this.send({msg: 2, x, y, z, block});
75 | }
76 |
77 | setMyPos(x, y, z, rx, rz)
78 | {
79 | this.send({msg: 5, x, y, z, rx, rz});
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/shader.js:
--------------------------------------------------------------------------------
1 | export default class Shader
2 | {
3 | constructor(display, vertSrc, fragSrc)
4 | {
5 | let gl = display.gl;
6 | let vert = gl.createShader(gl.VERTEX_SHADER);
7 |
8 | gl.shaderSource(vert, vertSrc);
9 | gl.compileShader(vert);
10 |
11 | console.log("compile vertex shader:", gl.getShaderInfoLog(vert));
12 |
13 | let frag = gl.createShader(gl.FRAGMENT_SHADER);
14 |
15 | gl.shaderSource(frag, fragSrc);
16 | gl.compileShader(frag);
17 |
18 | console.log("compile fragment shader:", gl.getShaderInfoLog(frag));
19 |
20 | let prog = gl.createProgram();
21 |
22 | gl.attachShader(prog, vert);
23 | gl.attachShader(prog, frag);
24 | gl.linkProgram(prog);
25 |
26 | console.log("link shader program:", gl.getProgramInfoLog(prog));
27 |
28 | this.gl = gl;
29 | this.prog = prog;
30 | }
31 |
32 | assignFloatAttrib(name, buf, size, stride, offset)
33 | {
34 | let gl = this.gl;
35 | let loca = gl.getAttribLocation(this.prog, name);
36 |
37 | gl.bindBuffer(gl.ARRAY_BUFFER, buf.buf);
38 | gl.enableVertexAttribArray(loca);
39 | gl.vertexAttribPointer(loca, size, gl.FLOAT, false, stride * 4, offset * 4);
40 | }
41 |
42 | assignVector(name, vec)
43 | {
44 | let gl = this.gl;
45 | let loca = gl.getUniformLocation(this.prog, name);
46 |
47 | gl.uniform3fv(loca, vec.data);
48 | }
49 |
50 | assignMatrix(name, mat)
51 | {
52 | let gl = this.gl;
53 | let loca = gl.getUniformLocation(this.prog, name);
54 |
55 | gl.uniformMatrix4fv(loca, false, mat.data);
56 | }
57 |
58 | assignMatrices(name, mats)
59 | {
60 | let gl = this.gl;
61 |
62 | mats.forEach((mat, i) => {
63 | let loca = gl.getUniformLocation(this.prog, name + "[" + i + "]");
64 |
65 | gl.uniformMatrix4fv(loca, false, mat.data);
66 | });
67 | }
68 |
69 | assignTexture(name, tex, unit)
70 | {
71 | let gl = this.gl;
72 | let loca = gl.getUniformLocation(this.prog, name);
73 |
74 | gl.activeTexture(gl.TEXTURE0 + unit);
75 | gl.bindTexture(gl.TEXTURE_2D, tex.tex);
76 | gl.uniform1i(loca, unit);
77 | }
78 |
79 | use()
80 | {
81 | this.gl.useProgram(this.prog);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/sky.js:
--------------------------------------------------------------------------------
1 | import Buffer from "./buffer.js";
2 | import Shader from "./shader.js";
3 |
4 | const verts = new Float32Array([
5 | -1,+1,-1, +1,+1,-1, -1,+1,+1, -1,+1,+1, +1,+1,-1, +1,+1,+1, // back
6 | +1,-1,-1, -1,-1,-1, +1,-1,+1, +1,-1,+1, -1,-1,-1, -1,-1,+1, // front
7 | -1,-1,-1, -1,+1,-1, -1,-1,+1, -1,-1,+1, -1,+1,-1, -1,+1,+1, // left
8 | +1,+1,-1, +1,-1,-1, +1,+1,+1, +1,+1,+1, +1,-1,-1, +1,-1,+1, // right
9 | -1,-1,-1, +1,-1,-1, -1,+1,-1, -1,+1,-1, +1,-1,-1, +1,+1,-1, // bottom
10 | -1,+1,+1, +1,+1,+1, -1,-1,+1, -1,-1,+1, +1,+1,+1, +1,-1,+1, // top
11 | ]);
12 |
13 | const vert = `
14 | uniform mat4 proj;
15 | uniform mat4 view;
16 | uniform mat4 model;
17 | attribute vec3 pos;
18 | varying mediump vec3 vPos;
19 |
20 | void main()
21 | {
22 | gl_Position = proj * view * model * vec4(pos, 1);
23 | vPos = pos;
24 | }
25 | `;
26 |
27 | const frag = `
28 | varying mediump vec3 vPos;
29 |
30 | void main()
31 | {
32 | mediump vec3 norm = normalize(vPos);
33 |
34 | gl_FragColor = mix(
35 | vec4(0.5, 0.75, 1.0, 1),
36 | vec4(0.125, 0.25, 0.5, 1),
37 | clamp(norm.z, 0.0, 1.0)
38 | );
39 | }
40 | `;
41 |
42 | export default class Sky
43 | {
44 | constructor(display)
45 | {
46 | this.buffer = new Buffer(display, verts);
47 | this.shader = new Shader(display, vert, frag);
48 | this.display = display;
49 | }
50 |
51 | draw(camera)
52 | {
53 | let shader = this.shader;
54 | let buffer = this.buffer;
55 | let gl = this.display.gl;
56 |
57 | shader.assignFloatAttrib("pos", buffer, 3, 3, 0);
58 | shader.use();
59 | shader.assignMatrix("proj", camera.proj);
60 | shader.assignMatrix("view", camera.view);
61 | shader.assignMatrix("model", camera.model);
62 |
63 | gl.disable(gl.DEPTH_TEST);
64 |
65 | this.display.drawTriangles(verts.length / 3);
66 |
67 | gl.enable(gl.DEPTH_TEST);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/speaker.js:
--------------------------------------------------------------------------------
1 | export default class Speaker
2 | {
3 | constructor()
4 | {
5 | this.ctx = new AudioContext();
6 | }
7 |
8 | activate()
9 | {
10 | this.ctx.resume();
11 | }
12 |
13 | async loadSound(url)
14 | {
15 | let response = await fetch(url);
16 | let buffer = await response.arrayBuffer();
17 |
18 | return await this.ctx.decodeAudioData(buffer);
19 | }
20 |
21 | playSound(sound)
22 | {
23 | let source = this.ctx.createBufferSource();
24 |
25 | source.buffer = sound;
26 | source.connect(this.ctx.destination);
27 | source.start();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/texture.js:
--------------------------------------------------------------------------------
1 | export default class Texture
2 | {
3 | constructor(display, src)
4 | {
5 | let gl = display.gl;
6 | let tex = gl.createTexture();
7 |
8 | gl.bindTexture(gl.TEXTURE_2D, tex);
9 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
10 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
11 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
12 |
13 | let img = document.createElement("img");
14 |
15 | img.src = src;
16 |
17 | img.onload = () => {
18 | gl.bindTexture(gl.TEXTURE_2D, tex);
19 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
20 | };
21 |
22 | this.tex = tex;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/vector.js:
--------------------------------------------------------------------------------
1 | export default class Vector
2 | {
3 | constructor(x = 0, y = 0, z = 0)
4 | {
5 | this.data = [x, y, z];
6 | }
7 |
8 | get x()
9 | {
10 | return this.data[0];
11 | }
12 |
13 | get y()
14 | {
15 | return this.data[1];
16 | }
17 |
18 | get z()
19 | {
20 | return this.data[2];
21 | }
22 |
23 | set(x = 0, y = 0, z = 0)
24 | {
25 | this.data[0] = x;
26 | this.data[1] = y;
27 | this.data[2] = z;
28 | }
29 |
30 | add(other)
31 | {
32 | this.data[0] += other.data[0];
33 | this.data[1] += other.data[1];
34 | this.data[2] += other.data[2];
35 | }
36 |
37 | addScaled(other, scale)
38 | {
39 | this.data[0] += other.data[0] * scale;
40 | this.data[1] += other.data[1] * scale;
41 | this.data[2] += other.data[2] * scale;
42 | }
43 |
44 | scale(s)
45 | {
46 | this.data[0] *= s;
47 | this.data[1] *= s;
48 | this.data[2] *= s;
49 | }
50 |
51 | rotateX(a)
52 | {
53 | let v = this.data;
54 | let s = Math.sin(a);
55 | let c = Math.cos(a);
56 | let y = v[1];
57 | let z = v[2];
58 | v[1] = y * c - z * s;
59 | v[2] = y * s + z * c;
60 | }
61 |
62 | rotateZ(a)
63 | {
64 | let v = this.data;
65 | let s = Math.sin(a);
66 | let c = Math.cos(a);
67 | let x = v[0];
68 | let y = v[1];
69 | v[0] = x * c - y * s;
70 | v[1] = x * s + y * c;
71 | }
72 |
73 | clone()
74 | {
75 | return new Vector(this.data[0], this.data[1], this.data[2]);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------