393 lines
15 KiB
JavaScript
393 lines
15 KiB
JavaScript
import EnumBlockFace from "../../util/EnumBlockFace.js";
|
|
import BlockRenderType from "../../util/BlockRenderType.js";
|
|
import Tessellator from "./Tessellator.js";
|
|
import MathHelper from "../../util/MathHelper.js";
|
|
import Block from "../world/block/Block.js";
|
|
|
|
export default class BlockRenderer {
|
|
|
|
constructor(worldRenderer) {
|
|
this.worldRenderer = worldRenderer;
|
|
this.tessellator = new Tessellator();
|
|
this.tessellator.bindTexture(worldRenderer.textureTerrain);
|
|
}
|
|
|
|
renderBlock(world, block, ambientOcclusion, x, y, z) {
|
|
switch (block.getRenderType()) {
|
|
case BlockRenderType.BLOCK:
|
|
this.renderSolidBlock(world, block, ambientOcclusion, x, y, z);
|
|
break;
|
|
case BlockRenderType.TORCH:
|
|
this.renderTorch(world, block, x, y, z);
|
|
break;
|
|
}
|
|
}
|
|
|
|
renderSolidBlock(world, block, ambientOcclusion, x, y, z) {
|
|
let boundingBox = block.getBoundingBox(world, x, y, z);
|
|
|
|
// Render all faces
|
|
let values = EnumBlockFace.values();
|
|
for (let i = 0; i < values.length; i++) {
|
|
let face = values[i];
|
|
|
|
// Check if face is hidden by other block
|
|
if (world === null || block.shouldRenderFace(world, x, y, z, face)) {
|
|
|
|
// Render face
|
|
this.renderFace(world, block, boundingBox, face, ambientOcclusion, x, y, z);
|
|
}
|
|
}
|
|
}
|
|
|
|
renderFace(world, block, boundingBox, face, ambientOcclusion, x, y, z) {
|
|
// Vertex mappings
|
|
let minX = x + boundingBox.minX;
|
|
let minY = y + boundingBox.minY;
|
|
let minZ = z + boundingBox.minZ;
|
|
let maxX = x + boundingBox.maxX;
|
|
let maxY = y + boundingBox.maxY;
|
|
let maxZ = z + boundingBox.maxZ;
|
|
|
|
// UV Mapping
|
|
let textureIndex = block.getTextureForFace(face);
|
|
let minU = (textureIndex % 16) / 16.0;
|
|
let maxU = minU + (16 / 256);
|
|
let minV = Math.floor(textureIndex / 16) / 16.0;
|
|
let maxV = minV + (16 / 256);
|
|
|
|
// Flip V
|
|
minV = 1 - minV;
|
|
maxV = 1 - maxV;
|
|
|
|
// Classic lightning
|
|
if (!ambientOcclusion) {
|
|
let level = world === null ? 15 : world.getTotalLightAt(minX + face.x, minY + face.y, minZ + face.z);
|
|
let brightness = 0.9 / 15.0 * level + 0.1;
|
|
let color = brightness * face.getShading();
|
|
this.tessellator.setColor(color, color, color);
|
|
}
|
|
|
|
// Set opacity of block
|
|
this.tessellator.setAlpha(1 - block.getTransparency());
|
|
|
|
// Add face to tessellator
|
|
this.addFace(world, face, ambientOcclusion, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
|
}
|
|
|
|
addFace(world, face, ambientOcclusion, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV) {
|
|
if (face === EnumBlockFace.BOTTOM) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, maxZ, maxU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, minZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, maxZ, minU, maxV);
|
|
}
|
|
if (face === EnumBlockFace.TOP) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, maxZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, minZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, maxZ, maxU, maxV);
|
|
}
|
|
if (face === EnumBlockFace.NORTH) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, minZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, minZ, maxU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, minZ, maxU, minV);
|
|
}
|
|
if (face === EnumBlockFace.SOUTH) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, maxZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, maxZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, maxZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, maxZ, maxU, maxV);
|
|
}
|
|
if (face === EnumBlockFace.WEST) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, maxZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, minY, minZ, maxU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, minZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, maxZ, minU, minV);
|
|
}
|
|
if (face === EnumBlockFace.EAST) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, maxZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, minZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, minY, maxZ, maxU, maxV);
|
|
}
|
|
}
|
|
|
|
addBlockCorner(world, face, ambientOcclusion, x, y, z, u, v) {
|
|
// Smooth lightning
|
|
if (ambientOcclusion) {
|
|
this.setAverageColor(world, face, x, y, z);
|
|
}
|
|
|
|
this.tessellator.addVertexWithUV(x, y, z, u, v);
|
|
}
|
|
|
|
setAverageColor(world, face, x, y, z) {
|
|
// Get the average light level of all 4 blocks at this corner
|
|
let lightLevelAtThisCorner = this.getAverageLightLevelAt(world, x, y, z);
|
|
|
|
// Convert light level from [0 - 15] to [0.1 - 1.0]
|
|
let brightness = 0.9 / 15.0 * lightLevelAtThisCorner + 0.1;
|
|
let color = brightness * face.getShading();
|
|
|
|
// Set color with shading
|
|
this.tessellator.setColorRGB(color, color, color);
|
|
}
|
|
|
|
getAverageLightLevelAt(world, x, y, z) {
|
|
if (world === null) {
|
|
return 15;
|
|
}
|
|
|
|
let totalLightLevel = 0;
|
|
let totalBlocks = 0;
|
|
|
|
// For all blocks around this corner
|
|
for (let offsetX = -1; offsetX <= 0; offsetX++) {
|
|
for (let offsetY = -1; offsetY <= 0; offsetY++) {
|
|
for (let offsetZ = -1; offsetZ <= 0; offsetZ++) {
|
|
let typeId = world.getBlockAt(x + offsetX, y + offsetY, z + offsetZ);
|
|
let block = typeId === 0 ? null : Block.getById(typeId);
|
|
|
|
// Does it contain air?
|
|
if (block === null || !block.isSolid()) {
|
|
// Sum up the light levels
|
|
totalLightLevel += world.getTotalLightAt(x + offsetX, y + offsetY, z + offsetZ);
|
|
totalBlocks++;
|
|
} else {
|
|
// Count the block if it's on the same level
|
|
if (offsetY === 0) {
|
|
totalBlocks++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the average light level of all surrounding blocks
|
|
return totalBlocks === 0 ? 0 : totalLightLevel / totalBlocks;
|
|
}
|
|
|
|
renderTorch(world, block, x, y, z) {
|
|
// Thickness of the torch
|
|
let size = 1 / 16;
|
|
|
|
let distortX = 0;
|
|
let distortZ = 0;
|
|
|
|
// Attach torch at wall
|
|
if (world != null) {
|
|
switch (world.getBlockDataAt(x, y, z)) {
|
|
case 1:
|
|
distortX = -0.2;
|
|
break;
|
|
case 2:
|
|
distortX = 0.2;
|
|
break;
|
|
case 3:
|
|
distortZ = -0.2;
|
|
break;
|
|
case 4:
|
|
distortZ = 0.2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Model type
|
|
let centerX = 0.5 + distortX * 1.5;
|
|
let centerZ = 0.5 + distortZ * 1.5;
|
|
|
|
// Lift the torch up
|
|
if (distortX !== 0 || distortZ !== 0) {
|
|
y += 0.2;
|
|
}
|
|
|
|
// Vertex mappings
|
|
let minX = x + centerX - size;
|
|
let minY = y;
|
|
let minZ = z + centerZ - size;
|
|
let maxX = x + centerX + size;
|
|
let maxY = y + 10 / 16;
|
|
let maxZ = z + centerZ + size;
|
|
|
|
// UV Mapping
|
|
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
|
let minU = (textureIndex % 16) / 16.0;
|
|
let minV = Math.floor(textureIndex / 16) / 16.0;
|
|
|
|
// Cut to torch texture at 7:6
|
|
minU += 7 / 256;
|
|
minV += 6 / 256;
|
|
|
|
// Size of torch texture (2x10)
|
|
let maxU = minU + 2 / 256;
|
|
let maxV = minV + 10 / 256;
|
|
|
|
// Flip V
|
|
minV = 1 - minV;
|
|
maxV = 1 - maxV;
|
|
|
|
// Set color with shading
|
|
this.tessellator.setColor(1, 1, 1);
|
|
|
|
// Add faces to tessellator
|
|
this.addDistortFace(world, EnumBlockFace.NORTH, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
|
this.addDistortFace(world, EnumBlockFace.EAST, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
|
this.addDistortFace(world, EnumBlockFace.SOUTH, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
|
this.addDistortFace(world, EnumBlockFace.WEST, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ);
|
|
this.addFace(world, EnumBlockFace.TOP, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV + 8 / 256);
|
|
}
|
|
|
|
addDistortFace(world, face, ambientOcclusion, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV, distortX, distortZ) {
|
|
if (face === EnumBlockFace.NORTH) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX + distortX, minY, minZ + distortZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX + distortX, minY, minZ + distortZ, maxU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, minZ, maxU, minV);
|
|
}
|
|
if (face === EnumBlockFace.SOUTH) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, maxZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, maxZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX + distortX, minY, maxZ + distortZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX + distortX, minY, maxZ + distortZ, maxU, maxV);
|
|
}
|
|
if (face === EnumBlockFace.WEST) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX + distortX, minY, maxZ + distortZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX + distortX, minY, minZ + distortZ, maxU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, minZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, minX, maxY, maxZ, minU, minV);
|
|
}
|
|
if (face === EnumBlockFace.EAST) {
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, maxZ, maxU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX, maxY, minZ, minU, minV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX + distortX, minY, minZ + distortZ, minU, maxV);
|
|
this.addBlockCorner(world, face, ambientOcclusion, maxX + distortX, minY, maxZ + distortZ, maxU, maxV);
|
|
}
|
|
}
|
|
|
|
renderBlockInHandThirdPerson(group, block, brightness) {
|
|
this.tessellator.startDrawing();
|
|
|
|
// Render block
|
|
this.renderBlock(null, block, false, 0, 0, 0);
|
|
|
|
// Change brightness
|
|
this.tessellator.transformBrightness(brightness);
|
|
|
|
// Create mesh
|
|
let mesh = this.tessellator.draw(group);
|
|
mesh.geometry.center();
|
|
|
|
// Relative position
|
|
mesh.position.x = 0;
|
|
mesh.position.y = 9;
|
|
mesh.position.z = -5;
|
|
|
|
// Rotation
|
|
mesh.rotation.y = Math.PI / 4;
|
|
|
|
// Scale
|
|
mesh.scale.x = 6;
|
|
mesh.scale.y = -6;
|
|
mesh.scale.z = 6;
|
|
}
|
|
|
|
renderBlockInFirstPerson(group, block, brightness) {
|
|
this.tessellator.startDrawing();
|
|
|
|
// Render block
|
|
this.renderBlock(null, block, false, 0, 0, 0);
|
|
|
|
// Change brightness
|
|
this.tessellator.transformBrightness(brightness);
|
|
|
|
// Create mesh
|
|
let mesh = this.tessellator.draw(group);
|
|
mesh.geometry.center();
|
|
|
|
// Scale
|
|
mesh.scale.x = 16;
|
|
mesh.scale.y = 16;
|
|
mesh.scale.z = 16;
|
|
}
|
|
|
|
renderGuiBlock(group, block, x, y, size, brightness) {
|
|
this.tessellator.startDrawing();
|
|
this.tessellator.setColor(1, 1, 1);
|
|
|
|
let boundingBox = block.getBoundingBox(null, 0, 0, 0);
|
|
|
|
// Render block by type
|
|
switch (block.getRenderType()) {
|
|
case BlockRenderType.BLOCK:
|
|
this.renderFace(null, block, boundingBox, EnumBlockFace.TOP, false, 0, 0, 0);
|
|
this.renderFace(null, block, boundingBox, EnumBlockFace.NORTH, false, 0, 0, 0);
|
|
this.renderFace(null, block, boundingBox, EnumBlockFace.EAST, false, 0, 0, 0);
|
|
break;
|
|
default:
|
|
this.renderGuiItem(block);
|
|
break;
|
|
}
|
|
|
|
// Change brightness
|
|
this.tessellator.transformBrightness(brightness);
|
|
|
|
// Create mesh
|
|
let mesh = this.tessellator.draw(group);
|
|
mesh.geometry.center();
|
|
|
|
// Rotate block
|
|
switch (block.getRenderType()) {
|
|
case BlockRenderType.BLOCK:
|
|
mesh.rotation.x = MathHelper.toRadians(45 / 1.5);
|
|
mesh.rotation.y = -MathHelper.toRadians(45 + 90);
|
|
break;
|
|
default:
|
|
mesh.rotation.y = MathHelper.toRadians(180);
|
|
size += 5;
|
|
break;
|
|
}
|
|
|
|
// Relative position
|
|
mesh.position.x = x;
|
|
mesh.position.y = -y;
|
|
mesh.position.z = -10;
|
|
|
|
// Scale
|
|
mesh.scale.x = size;
|
|
mesh.scale.y = size;
|
|
mesh.scale.z = size;
|
|
}
|
|
|
|
renderGuiItem(block) {
|
|
// Vertex mappings
|
|
let minX = 0;
|
|
let minY = 0;
|
|
let minZ = 0;
|
|
let maxX = 1;
|
|
let maxY = 1;
|
|
let maxZ = 1;
|
|
|
|
let offset = (1 / 256);
|
|
|
|
// UV Mapping
|
|
let textureIndex = block.getTextureForFace(EnumBlockFace.NORTH);
|
|
let minU = (textureIndex % 16) / 16.0;
|
|
let maxU = minU + (16 / 256);
|
|
let minV = Math.floor(textureIndex / 16) / 16.0;
|
|
let maxV = minV + (16 / 256);
|
|
|
|
// Flip V
|
|
minV = 1 - minV;
|
|
maxV = 1 - maxV;
|
|
|
|
minU += offset;
|
|
maxU -= offset;
|
|
minV -= offset;
|
|
maxV += offset;
|
|
|
|
// Render item
|
|
this.addFace(null, EnumBlockFace.NORTH, false, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV);
|
|
}
|
|
} |