798 lines
30 KiB
JavaScript
798 lines
30 KiB
JavaScript
import BlockRenderer from "./BlockRenderer.js";
|
|
import EntityRenderManager from "./entity/EntityRenderManager.js";
|
|
import MathHelper from "../../util/MathHelper.js";
|
|
import Block from "../world/block/Block.js";
|
|
import Tessellator from "./Tessellator.js";
|
|
import ChunkSection from "../world/ChunkSection.js";
|
|
import Random from "../../util/Random.js";
|
|
import Vector3 from "../../util/Vector3.js";
|
|
import * as THREE from "../../../../../../libraries/three.module.js";
|
|
|
|
export default class WorldRenderer {
|
|
|
|
static THIRD_PERSON_DISTANCE = 4;
|
|
|
|
constructor(minecraft, window) {
|
|
this.minecraft = minecraft;
|
|
this.window = window;
|
|
this.chunkSectionUpdateQueue = [];
|
|
|
|
this.tessellator = new Tessellator();
|
|
|
|
// Load terrain texture
|
|
this.textureTerrain = minecraft.getThreeTexture('terrain/terrain.png');
|
|
this.textureTerrain.magFilter = THREE.NearestFilter;
|
|
this.textureTerrain.minFilter = THREE.NearestFilter;
|
|
|
|
// Load sun texture
|
|
this.textureSun = minecraft.getThreeTexture('terrain/sun.png');
|
|
this.textureSun.magFilter = THREE.NearestFilter;
|
|
this.textureSun.minFilter = THREE.NearestFilter;
|
|
|
|
// Load moon texture
|
|
this.textureMoon = minecraft.getThreeTexture('terrain/moon.png');
|
|
this.textureMoon.magFilter = THREE.NearestFilter;
|
|
this.textureMoon.minFilter = THREE.NearestFilter;
|
|
|
|
// Block Renderer
|
|
this.blockRenderer = new BlockRenderer(this);
|
|
|
|
// Entity render manager
|
|
this.entityRenderManager = new EntityRenderManager(this);
|
|
|
|
this.equippedProgress = 0;
|
|
this.prevEquippedProgress = 0;
|
|
this.itemToRender = 0;
|
|
|
|
this.prevFogBrightness = 0;
|
|
this.fogBrightness = 0;
|
|
|
|
this.flushRebuild = false;
|
|
|
|
this.initialize();
|
|
}
|
|
|
|
initialize() {
|
|
// Create world camera
|
|
this.camera = new THREE.PerspectiveCamera(0, 1, 0.001, 1000);
|
|
this.camera.rotation.order = 'ZYX';
|
|
this.camera.up = new THREE.Vector3(0, 0, 1);
|
|
|
|
// Frustum
|
|
this.frustum = new THREE.Frustum();
|
|
|
|
// Create background scene
|
|
this.background = new THREE.Scene();
|
|
this.background.matrixAutoUpdate = false;
|
|
|
|
// Create world scene
|
|
this.scene = new THREE.Scene();
|
|
this.scene.matrixAutoUpdate = false;
|
|
|
|
// Create overlay for first person model rendering
|
|
this.overlay = new THREE.Scene();
|
|
this.overlay.matrixAutoUpdate = false;
|
|
|
|
// Create web renderer
|
|
this.webRenderer = new THREE.WebGLRenderer({
|
|
canvas: this.window.canvas,
|
|
antialias: false,
|
|
alpha: true
|
|
});
|
|
|
|
// Settings
|
|
this.webRenderer.setSize(this.window.width, this.window.height);
|
|
this.webRenderer.shadowMap.enabled = true;
|
|
this.webRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
|
|
this.webRenderer.autoClear = false;
|
|
this.webRenderer.sortObjects = false;
|
|
this.webRenderer.setClearColor(0x000000, 0);
|
|
this.webRenderer.clear();
|
|
|
|
// Create sky
|
|
this.generateSky();
|
|
|
|
// Create block hit box
|
|
let geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
let edges = new THREE.EdgesGeometry(geometry);
|
|
this.blockHitBox = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({
|
|
color: 0x000000
|
|
}));
|
|
this.scene.add(this.blockHitBox);
|
|
}
|
|
|
|
render(partialTicks) {
|
|
// Setup camera
|
|
this.orientCamera(partialTicks);
|
|
|
|
// Render chunks
|
|
let player = this.minecraft.player;
|
|
let cameraChunkX = Math.floor(player.x) >> 4;
|
|
let cameraChunkZ = Math.floor(player.z) >> 4;
|
|
this.renderChunks(cameraChunkX, cameraChunkZ);
|
|
|
|
// Render sky
|
|
this.renderSky(partialTicks);
|
|
|
|
// Render target block
|
|
this.renderBlockHitBox(player, partialTicks);
|
|
|
|
// Render particles
|
|
this.minecraft.particleRenderer.renderParticles(player, partialTicks);
|
|
|
|
// Hide all entities and make them visible during rendering
|
|
for (let entity of this.minecraft.world.entities) {
|
|
entity.renderer.group.visible = false;
|
|
}
|
|
|
|
// Render entities
|
|
for (let entity of this.minecraft.world.entities) {
|
|
if (entity === player && this.minecraft.settings.thirdPersonView === 0) {
|
|
continue;
|
|
}
|
|
|
|
// Render entity
|
|
entity.renderer.render(entity, partialTicks);
|
|
entity.renderer.group.visible = true;
|
|
}
|
|
|
|
// Render hand
|
|
this.renderHand(partialTicks);
|
|
|
|
// Render background scene
|
|
this.webRenderer.render(this.background, this.camera);
|
|
|
|
// Render actual scene
|
|
this.webRenderer.render(this.scene, this.camera);
|
|
|
|
// Render overlay with a static FOV
|
|
this.camera.fov = 70;
|
|
this.camera.updateProjectionMatrix();
|
|
this.webRenderer.render(this.overlay, this.camera);
|
|
}
|
|
|
|
onTick() {
|
|
// Rebuild 2 chunk sections each tick
|
|
for (let i = 0; i < 2; i++) {
|
|
if (this.chunkSectionUpdateQueue.length !== 0) {
|
|
let chunkSection = this.chunkSectionUpdateQueue.shift();
|
|
if (chunkSection != null) {
|
|
// Rebuild chunk
|
|
chunkSection.rebuild(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.prevFogBrightness = this.fogBrightness;
|
|
this.prevEquippedProgress = this.equippedProgress;
|
|
|
|
let player = this.minecraft.player;
|
|
let itemStack = player.inventory.getItemInSelectedSlot();
|
|
|
|
let showHand = false;
|
|
if (this.itemToRender != null && itemStack != null) {
|
|
if (this.itemToRender !== itemStack) {
|
|
showHand = true;
|
|
}
|
|
} else if (this.itemToRender == null && itemStack == null) {
|
|
showHand = false;
|
|
} else {
|
|
showHand = true;
|
|
}
|
|
|
|
// Update equip progress
|
|
this.equippedProgress += MathHelper.clamp((showHand ? 0.0 : 1.0) - this.equippedProgress, -0.4, 0.4);
|
|
|
|
if (this.equippedProgress < 0.1) {
|
|
this.itemToRender = itemStack;
|
|
}
|
|
|
|
// Update fog brightness
|
|
let brightnessAtPosition = this.minecraft.world.getLightBrightnessForEntity(player);
|
|
let renderDistance = this.minecraft.settings.viewDistance / 32.0;
|
|
let fogBrightness = brightnessAtPosition * (1.0 - renderDistance) + renderDistance;
|
|
this.fogBrightness += (fogBrightness - this.fogBrightness) * 0.1;
|
|
}
|
|
|
|
orientCamera(partialTicks) {
|
|
let player = this.minecraft.player;
|
|
|
|
// Reset rotation stack
|
|
let stack = this.camera;
|
|
|
|
// Position
|
|
let x = player.prevX + (player.x - player.prevX) * partialTicks;
|
|
let y = player.prevY + (player.y - player.prevY) * partialTicks + player.getEyeHeight();
|
|
let z = player.prevZ + (player.z - player.prevZ) * partialTicks;
|
|
|
|
// Rotation
|
|
let yaw = player.prevRotationYaw + (player.rotationYaw - player.prevRotationYaw) * partialTicks;
|
|
let pitch = player.prevRotationPitch + (player.rotationPitch - player.prevRotationPitch) * partialTicks;
|
|
|
|
// Add camera offset
|
|
let mode = this.minecraft.settings.thirdPersonView;
|
|
if (mode !== 0) {
|
|
let distance = WorldRenderer.THIRD_PERSON_DISTANCE;
|
|
let frontView = mode === 2;
|
|
|
|
// Calculate vector of yaw and pitch
|
|
let vector = player.getVectorForRotation(pitch, yaw);
|
|
|
|
// Calculate max possible position of the third person camera
|
|
let maxX = x - vector.x * distance * (frontView ? -1 : 1);
|
|
let maxY = y - vector.y * distance * (frontView ? -1 : 1);
|
|
let maxZ = z - vector.z * distance * (frontView ? -1 : 1);
|
|
|
|
// Make 8 different ray traces to make sure we don't get stuck in walls
|
|
for (let i = 0; i < 8; i++) {
|
|
// Calculate all possible offset variations (Basically a binary counter)
|
|
let offsetX = ((i & 1) * 2 - 1) * 0.1;
|
|
let offsetY = ((i >> 1 & 1) * 2 - 1) * 0.1;
|
|
let offsetZ = ((i >> 2 & 1) * 2 - 1) * 0.1;
|
|
|
|
// Calculate ray trace from and to position
|
|
let from = new Vector3(x, y, z);
|
|
let to = new Vector3(maxX, maxY, maxZ);
|
|
|
|
// Add offset of this variation
|
|
from = from.addVector(offsetX, offsetY, offsetZ);
|
|
to = to.addVector(offsetX, offsetY, offsetZ);
|
|
|
|
// Make ray trace
|
|
let target = this.minecraft.world.rayTraceBlocks(from, to);
|
|
if (target === null) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate distance to collision
|
|
let distanceToCollision = target.vector.distanceTo(new Vector3(x, y, z));
|
|
if (distanceToCollision < distance) {
|
|
distance = distanceToCollision;
|
|
}
|
|
}
|
|
|
|
// Move camera to third person sphere
|
|
x -= vector.x * distance * (frontView ? -1 : 1);
|
|
y -= vector.y * distance * (frontView ? -1 : 1);
|
|
z -= vector.z * distance * (frontView ? -1 : 1);
|
|
|
|
// Flip camera around if front view is enabled
|
|
if (frontView) {
|
|
pitch *= -1;
|
|
yaw += 180;
|
|
}
|
|
}
|
|
|
|
// Update camera rotation
|
|
stack.rotation.x = -MathHelper.toRadians(pitch);
|
|
stack.rotation.y = -MathHelper.toRadians(yaw + 180);
|
|
stack.rotation.z = 0;
|
|
|
|
// Update camera position
|
|
stack.position.set(x, y, z);
|
|
|
|
// Apply bobbing animation
|
|
if (mode === 0 && this.minecraft.settings.viewBobbing) {
|
|
this.bobbingAnimation(player, stack, partialTicks);
|
|
}
|
|
|
|
// Update FOV
|
|
this.camera.fov = this.minecraft.settings.fov + player.getFOVModifier();
|
|
this.camera.updateProjectionMatrix();
|
|
|
|
// Update frustum
|
|
this.frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse));
|
|
|
|
// Setup fog
|
|
this.setupFog(x, z, player.isHeadInWater(), partialTicks);
|
|
}
|
|
|
|
generateSky() {
|
|
// Create background center group
|
|
this.backgroundCenter = new THREE.Object3D();
|
|
this.background.add(this.backgroundCenter);
|
|
|
|
let size = 64;
|
|
let scale = 256 / size + 2;
|
|
|
|
// Generate sky color
|
|
{
|
|
let y = 16;
|
|
this.listSky = new THREE.Object3D();
|
|
this.tessellator.startDrawing();
|
|
this.tessellator.setColor(1, 1, 1);
|
|
for (let x = -size * scale; x <= size * scale; x += size) {
|
|
for (let z = -size * scale; z <= size * scale; z += size) {
|
|
this.tessellator.addVertex(x + size, y, z);
|
|
this.tessellator.addVertex(x, y, z);
|
|
this.tessellator.addVertex(x, y, z + size);
|
|
this.tessellator.addVertex(x + size, y, z + size);
|
|
}
|
|
}
|
|
let mesh = this.tessellator.draw(this.listSky);
|
|
mesh.material.depthTest = false;
|
|
this.backgroundCenter.add(this.listSky);
|
|
}
|
|
|
|
// Generate sunrise/sunset color
|
|
{
|
|
this.listSunset = new THREE.Object3D();
|
|
this.tessellator.startDrawing();
|
|
|
|
let amount = 16;
|
|
let width = (Math.PI * 2.0) / amount;
|
|
|
|
for (let index = 0; index < amount; index++) {
|
|
let rotation = (index * Math.PI * 2.0) / amount;
|
|
|
|
let x1 = Math.sin(rotation);
|
|
let y1 = Math.cos(rotation);
|
|
|
|
let x2 = Math.sin(rotation + width);
|
|
let y2 = Math.cos(rotation + width);
|
|
|
|
this.tessellator.setColor(1, 1, 1, 1);
|
|
this.tessellator.addVertex(0.0, 100, 0.0);
|
|
this.tessellator.addVertex(0.0, 100, 0.0);
|
|
|
|
this.tessellator.setColor(1, 1, 1, 0);
|
|
this.tessellator.addVertex(x1 * 120, y1 * 120, -y1 * 40);
|
|
this.tessellator.addVertex(x2 * 120, y2 * 120, -y2 * 40);
|
|
}
|
|
|
|
let mesh = this.tessellator.draw(this.listSunset);
|
|
mesh.material = mesh.material.clone();
|
|
mesh.material.depthTest = false;
|
|
mesh.material.opacity = 0.6;
|
|
mesh.material.side = THREE.DoubleSide;
|
|
this.backgroundCenter.add(this.listSunset);
|
|
}
|
|
|
|
// Create cycle group
|
|
this.cycleGroup = new THREE.Object3D();
|
|
|
|
// Generate stars
|
|
{
|
|
this.listStars = new THREE.Object3D();
|
|
this.tessellator.startDrawing();
|
|
this.tessellator.setColor(1, 1, 1);
|
|
|
|
// Generate 1500 stars
|
|
let random = new Random(10842);
|
|
for (let i = 0; i < 1500; i++) {
|
|
// Random vector
|
|
let vectorX = random.nextFloat() * 2.0 - 1.0;
|
|
let vectorY = random.nextFloat() * 2.0 - 1.0;
|
|
let vectorZ = random.nextFloat() * 2.0 - 1.0;
|
|
|
|
// Skip invalid vectors
|
|
let distance = vectorX * vectorX + vectorY * vectorY + vectorZ * vectorZ;
|
|
if (distance >= 1.0 || distance <= 0.01) {
|
|
continue;
|
|
}
|
|
|
|
// Create sphere
|
|
distance = 1.0 / Math.sqrt(distance);
|
|
vectorX *= distance;
|
|
vectorY *= distance;
|
|
vectorZ *= distance;
|
|
|
|
// Increase sphere size
|
|
let x = vectorX * 100;
|
|
let y = vectorY * 100;
|
|
let z = vectorZ * 100;
|
|
|
|
// Rotate the stars on the sphere
|
|
let rotationX = Math.atan2(vectorX, vectorZ);
|
|
let sinX = Math.sin(rotationX);
|
|
let cosX = Math.cos(rotationX);
|
|
|
|
// Face the stars to the middle of the sphere
|
|
let rotationY = Math.atan2(Math.sqrt(vectorX * vectorX + vectorZ * vectorZ), vectorY);
|
|
let sinY = Math.sin(rotationY);
|
|
let cosY = Math.cos(rotationY);
|
|
|
|
// Tilt the stars randomly
|
|
let rotationZ = random.nextFloat() * Math.PI * 2;
|
|
let sinZ = Math.sin(rotationZ);
|
|
let cosZ = Math.cos(rotationZ);
|
|
|
|
// Random size of the star
|
|
let size = 0.25 + random.nextFloat() * 0.25;
|
|
|
|
// Add vertices for each edge of the star
|
|
for (let edge = 0; edge < 4; edge++) {
|
|
// Calculate the position of the edge on a 2D plane
|
|
let tileX = ((edge & 2) - 1) * size;
|
|
let tileZ = ((edge + 1 & 2) - 1) * size;
|
|
|
|
// Project tile position onto the sphere
|
|
let sphereX = tileX * cosZ - tileZ * sinZ;
|
|
let sphereY = tileZ * cosZ + tileX * sinZ;
|
|
let sphereZ = -sphereX * cosY;
|
|
|
|
// Calculate offset of the edge on the sphere
|
|
let offsetX = sphereZ * sinX - sphereY * cosX;
|
|
let offsetY = sphereX * sinY;
|
|
let offsetZ = sphereY * sinX + sphereZ * cosX;
|
|
|
|
// Add vertex for the edge of the star
|
|
this.tessellator.addVertex(x + offsetX, y + offsetY, z + offsetZ);
|
|
}
|
|
}
|
|
|
|
let mesh = this.tessellator.draw(this.listStars);
|
|
mesh.material = mesh.material.clone();
|
|
mesh.material.depthTest = true;
|
|
mesh.material.side = THREE.BackSide;
|
|
this.cycleGroup.add(this.listStars);
|
|
}
|
|
|
|
// Create sun
|
|
let geometry = new THREE.PlaneGeometry(1, 1);
|
|
let materialSun = new THREE.MeshBasicMaterial({
|
|
side: THREE.FrontSide,
|
|
map: this.textureSun,
|
|
alphaMap: this.textureSun,
|
|
blending: THREE.AdditiveBlending,
|
|
transparent: true
|
|
});
|
|
this.sun = new THREE.Mesh(geometry, materialSun);
|
|
this.sun.translateZ(-2);
|
|
this.sun.material.depthTest = false;
|
|
this.cycleGroup.add(this.sun);
|
|
|
|
// Create moon
|
|
let materialMoon = new THREE.MeshBasicMaterial({
|
|
side: THREE.BackSide,
|
|
map: this.textureMoon,
|
|
alphaMap: this.textureMoon,
|
|
blending: THREE.AdditiveBlending,
|
|
transparent: true
|
|
});
|
|
this.moon = new THREE.Mesh(geometry, materialMoon);
|
|
this.moon.translateZ(2);
|
|
this.moon.material.depthTest = false;
|
|
this.cycleGroup.add(this.moon);
|
|
|
|
// Add cycle group before the void to hide the cycling elements behind the void
|
|
this.backgroundCenter.add(this.cycleGroup);
|
|
|
|
// Generate void color
|
|
{
|
|
let y = -16;
|
|
this.listVoid = new THREE.Object3D();
|
|
this.tessellator.startDrawing();
|
|
this.tessellator.setColor(1, 1, 1);
|
|
for (let x = -size * scale; x <= size * scale; x += size) {
|
|
for (let z = -size * scale; z <= size * scale; z += size) {
|
|
this.tessellator.addVertex(x, y, z);
|
|
this.tessellator.addVertex(x + size, y, z);
|
|
this.tessellator.addVertex(x + size, y, z + size);
|
|
this.tessellator.addVertex(x, y, z + size);
|
|
}
|
|
}
|
|
let mesh = this.tessellator.draw(this.listVoid);
|
|
mesh.material = mesh.material.clone();
|
|
mesh.material.depthTest = false;
|
|
mesh.material.opacity = 1;
|
|
this.backgroundCenter.add(this.listVoid);
|
|
}
|
|
}
|
|
|
|
renderSky(partialTicks) {
|
|
// Center sky
|
|
this.backgroundCenter.position.copy(this.camera.position);
|
|
|
|
// Rotate sky cycle
|
|
let angle = this.minecraft.world.getCelestialAngle(partialTicks);
|
|
this.cycleGroup.rotation.set(angle * Math.PI * 2 + Math.PI / 2, 0, 0);
|
|
}
|
|
|
|
setupFog(x, z, inWater, partialTicks) {
|
|
if (inWater) {
|
|
let color = new THREE.Color(0.2, 0.2, 0.4);
|
|
this.background.background = color;
|
|
this.scene.fog = new THREE.Fog(color, 0.0025, 5);
|
|
} else {
|
|
let world = this.minecraft.world;
|
|
|
|
let viewDistance = this.minecraft.settings.viewDistance * ChunkSection.SIZE;
|
|
let viewFactor = 1.0 - Math.pow(0.25 + 0.75 * this.minecraft.settings.viewDistance / 32.0, 0.25);
|
|
|
|
let angle = world.getCelestialAngle(partialTicks);
|
|
|
|
let skyColor = world.getSkyColor(x, z, partialTicks);
|
|
let fogColor = world.getFogColor(partialTicks);
|
|
let sunsetColor = world.getSunriseSunsetColor(partialTicks);
|
|
|
|
let starBrightness = world.getStarBrightness(partialTicks);
|
|
let brightness = this.prevFogBrightness + (this.fogBrightness - this.prevFogBrightness) * partialTicks;
|
|
|
|
let red = (fogColor.x + (skyColor.x - fogColor.x) * viewFactor) * brightness;
|
|
let green = (fogColor.y + (skyColor.y - fogColor.y) * viewFactor) * brightness;
|
|
let blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * brightness;
|
|
|
|
// Update background color
|
|
this.background.background = new THREE.Color(red, green, blue);
|
|
|
|
// Update fog color
|
|
this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2);
|
|
|
|
let skyMesh = this.listSky.children[0];
|
|
let voidMesh = this.listVoid.children[0];
|
|
let starsMesh = this.listStars.children[0];
|
|
let sunsetMesh = this.listSunset.children[0];
|
|
|
|
// Update sky and void color
|
|
skyMesh.material.color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z));
|
|
voidMesh.material.color.set(new THREE.Color(
|
|
skyColor.x * 0.2 + 0.04,
|
|
skyColor.y * 0.2 + 0.04,
|
|
skyColor.z * 0.6 + 0.1
|
|
));
|
|
|
|
// Update star brightness
|
|
if (starBrightness > 0) {
|
|
starsMesh.material.opacity = starBrightness;
|
|
starsMesh.material.color.set(new THREE.Color(starBrightness, starBrightness, starBrightness));
|
|
}
|
|
this.listStars.visible = starBrightness > 0;
|
|
|
|
// Update sunset
|
|
if (sunsetColor !== null) {
|
|
sunsetMesh.material.opacity = sunsetColor.w;
|
|
sunsetMesh.material.color.set(new THREE.Color(sunsetColor.x, sunsetColor.y, sunsetColor.z));
|
|
sunsetMesh.rotation.x = MathHelper.toRadians(angle <= 0.5 ? 90 : 135);
|
|
}
|
|
sunsetMesh.visible = sunsetColor !== null;
|
|
}
|
|
|
|
this.background.fog = this.scene.fog;
|
|
}
|
|
|
|
renderChunks(cameraChunkX, cameraChunkZ) {
|
|
let world = this.minecraft.world;
|
|
let renderDistance = this.minecraft.settings.viewDistance;
|
|
|
|
// Update chunks
|
|
for (let [index, chunk] of world.chunks) {
|
|
let distanceX = Math.abs(cameraChunkX - chunk.x);
|
|
let distanceZ = Math.abs(cameraChunkZ - chunk.z);
|
|
|
|
// Is in render distance check
|
|
if (distanceX < renderDistance && distanceZ < renderDistance) {
|
|
// Make chunk visible
|
|
chunk.group.visible = true;
|
|
chunk.loaded = true;
|
|
|
|
// For all chunk sections
|
|
for (let y in chunk.sections) {
|
|
let chunkSection = chunk.sections[y];
|
|
|
|
// Is in camera view check
|
|
if (this.frustum.intersectsBox(chunkSection.boundingBox) && !chunkSection.isEmpty()) {
|
|
// Make section visible
|
|
chunkSection.group.visible = true;
|
|
|
|
// Render chunk section
|
|
chunkSection.render();
|
|
|
|
// Queue for rebuild
|
|
if (chunkSection.isModified && !this.chunkSectionUpdateQueue.includes(chunkSection)) {
|
|
this.chunkSectionUpdateQueue.push(chunkSection);
|
|
}
|
|
} else {
|
|
// Hide section
|
|
chunkSection.group.visible = false;
|
|
}
|
|
}
|
|
} else {
|
|
// Hide chunk
|
|
chunk.group.visible = false;
|
|
|
|
// Unload chunk
|
|
if (chunk.loaded) {
|
|
chunk.unload();
|
|
|
|
// TODO Implement chunk unloading
|
|
//let index = chunk.x + (chunk.z << 16);
|
|
//world.chunks.delete(index);
|
|
//world.group.remove(chunk.group);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort update queue, chunk sections that are closer to the camera get a higher priority
|
|
this.chunkSectionUpdateQueue.sort((section1, section2) => {
|
|
let distance1 = Math.floor(Math.pow(section1.x - cameraChunkX, 2) + Math.pow(section1.z - cameraChunkZ, 2));
|
|
let distance2 = Math.floor(Math.pow(section2.x - cameraChunkX, 2) + Math.pow(section2.z - cameraChunkZ, 2));
|
|
return distance1 - distance2;
|
|
});
|
|
|
|
// Update render order of chunks
|
|
world.group.children.sort((a, b) => {
|
|
let distance1 = Math.floor(Math.pow(a.chunkX - cameraChunkX, 2) + Math.pow(a.chunkZ - cameraChunkZ, 2));
|
|
let distance2 = Math.floor(Math.pow(b.chunkX - cameraChunkX, 2) + Math.pow(b.chunkZ - cameraChunkZ, 2));
|
|
return distance2 - distance1;
|
|
});
|
|
|
|
// Flush by rebuilding 8 chunk sections
|
|
if (this.flushRebuild) {
|
|
this.flushRebuild = false;
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
if (this.chunkSectionUpdateQueue.length !== 0) {
|
|
let chunkSection = this.chunkSectionUpdateQueue.shift();
|
|
if (chunkSection != null) {
|
|
// Rebuild chunk
|
|
chunkSection.rebuild(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
rebuildAll() {
|
|
let world = this.minecraft.world;
|
|
for (let [index, chunk] of world.chunks) {
|
|
chunk.setModifiedAllSections();
|
|
}
|
|
}
|
|
|
|
renderHand(partialTicks) {
|
|
// Hide hand before rendering
|
|
let player = this.minecraft.player;
|
|
let stack = player.renderer.firstPersonGroup;
|
|
stack.visible = false;
|
|
|
|
let firstPerson = this.minecraft.settings.thirdPersonView === 0;
|
|
let itemId = firstPerson ? this.itemToRender : player.inventory.getItemInSelectedSlot();
|
|
let hasItem = itemId !== 0;
|
|
|
|
// Hide in third person
|
|
if (!firstPerson) {
|
|
return;
|
|
}
|
|
|
|
// Apply matrix mode (Put object in front of camera)
|
|
stack.position.copy(this.camera.position);
|
|
stack.rotation.copy(this.camera.rotation);
|
|
stack.rotation.order = 'ZYX';
|
|
|
|
// Scale down
|
|
stack.scale.set(0.0625, 0.0625, 0.0625);
|
|
|
|
let equipProgress = this.prevEquippedProgress + (this.equippedProgress - this.prevEquippedProgress) * partialTicks;
|
|
let swingProgress = player.getSwingProgress(partialTicks);
|
|
|
|
let pitchArm = player.prevRenderArmPitch + (player.renderArmPitch - player.prevRenderArmPitch) * partialTicks;
|
|
let yawArm = player.prevRenderArmYaw + (player.renderArmYaw - player.prevRenderArmYaw) * partialTicks;
|
|
|
|
// Bobbing animation
|
|
if (this.minecraft.settings.viewBobbing) {
|
|
this.bobbingAnimation(player, stack, partialTicks);
|
|
}
|
|
|
|
let factor = 0.8;
|
|
let zOffset = Math.sin(swingProgress * Math.PI);
|
|
let yOffset = Math.sin(Math.sqrt(swingProgress) * Math.PI * 2.0);
|
|
let xOffset = Math.sin(Math.sqrt(swingProgress) * Math.PI);
|
|
|
|
let sqrtRotation = Math.sin(Math.sqrt(swingProgress) * Math.PI);
|
|
let powRotation = Math.sin(swingProgress * swingProgress * Math.PI);
|
|
|
|
// Camera rotation movement
|
|
stack.rotateX(MathHelper.toRadians((player.rotationPitch - pitchArm) * 0.1));
|
|
stack.rotateY(MathHelper.toRadians((player.rotationYaw - yawArm) * 0.1));
|
|
|
|
if (hasItem) {
|
|
// Initial offset on screen
|
|
this.translate(stack, -xOffset * 0.4, yOffset * 0.2, -zOffset * 0.2);
|
|
this.translate(stack, 0.7 * factor, -0.65 * factor - (1.0 - equipProgress) * 0.6, -0.9 * factor);
|
|
|
|
// Rotation of hand
|
|
stack.rotateY(MathHelper.toRadians(45));
|
|
stack.rotateY(MathHelper.toRadians(-powRotation * 20));
|
|
stack.rotateZ(MathHelper.toRadians(-sqrtRotation * 20));
|
|
stack.rotateX(MathHelper.toRadians(-sqrtRotation * 80));
|
|
|
|
// Scale down
|
|
stack.scale.x *= 0.4;
|
|
stack.scale.y *= 0.4;
|
|
stack.scale.z *= 0.4;
|
|
|
|
// Render item
|
|
player.renderer.updateFirstPerson(player);
|
|
} else {
|
|
// Initial offset on screen
|
|
this.translate(stack, -xOffset * 0.3, yOffset * 0.4, -zOffset * 0.4);
|
|
this.translate(stack, 0.8 * factor, -0.75 * factor - (1.0 - equipProgress) * 0.6, -0.9 * factor);
|
|
|
|
// Rotation of hand
|
|
stack.rotateY(MathHelper.toRadians(45));
|
|
stack.rotateY(MathHelper.toRadians(sqrtRotation * 70));
|
|
stack.rotateZ(MathHelper.toRadians(-powRotation * 20));
|
|
|
|
// Post transform
|
|
this.translate(stack, -1, 3.6, 3.5);
|
|
stack.rotateZ(MathHelper.toRadians(120));
|
|
stack.rotateX(MathHelper.toRadians(200));
|
|
stack.rotateY(MathHelper.toRadians(-135));
|
|
this.translate(stack, 5.6, 0.0, 0.0);
|
|
|
|
// Render hand
|
|
player.renderer.renderRightHand(player, partialTicks);
|
|
}
|
|
}
|
|
|
|
renderBlockHitBox(player, partialTicks) {
|
|
let hitResult = player.rayTrace(5, partialTicks);
|
|
let hitBoxVisible = !(hitResult === null);
|
|
if ((this.blockHitBox.visible = hitBoxVisible)) {
|
|
let x = hitResult.x;
|
|
let y = hitResult.y;
|
|
let z = hitResult.z;
|
|
|
|
// Get block type
|
|
let world = this.minecraft.world;
|
|
let typeId = world.getBlockAt(x, y, z);
|
|
let block = Block.getById(typeId);
|
|
|
|
if (typeId !== 0) {
|
|
let boundingBox = block.getBoundingBox(world, x, y, z);
|
|
|
|
let offset = 0.01;
|
|
|
|
let width = boundingBox.width() + offset;
|
|
let height = boundingBox.height() + offset;
|
|
let depth = boundingBox.depth() + offset;
|
|
|
|
// Update size of hit box
|
|
this.blockHitBox.scale.set(
|
|
width,
|
|
height,
|
|
depth
|
|
);
|
|
|
|
// Update position of hit box
|
|
this.blockHitBox.position.set(
|
|
x + width / 2 / width - 0.5 + boundingBox.maxX - width / 2 + offset / 2,
|
|
y + height / 2 / height - 0.5 + boundingBox.maxY - height / 2 + offset / 2,
|
|
z + depth / 2 / depth - 0.5 + boundingBox.maxZ - depth / 2 + offset / 2,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
translate(stack, x, y, z) {
|
|
stack.translateX(x);
|
|
stack.translateY(y);
|
|
stack.translateZ(z);
|
|
}
|
|
|
|
bobbingAnimation(player, stack, partialTicks) {
|
|
let walked = -(player.prevDistanceWalked + (player.distanceWalked - player.prevDistanceWalked) * partialTicks);
|
|
let yaw = player.prevCameraYaw + (player.cameraYaw - player.prevCameraYaw) * partialTicks;
|
|
let pitch = player.prevCameraPitch + (player.cameraPitch - player.prevCameraPitch) * partialTicks;
|
|
|
|
this.translate(
|
|
stack,
|
|
Math.sin(walked * 3.141593) * yaw * 0.5,
|
|
-Math.abs(Math.cos(walked * Math.PI) * yaw),
|
|
0.0
|
|
);
|
|
|
|
stack.rotateZ(MathHelper.toRadians(Math.sin(walked * Math.PI) * yaw * 3.0));
|
|
stack.rotateX(MathHelper.toRadians(Math.abs(Math.cos(walked * Math.PI - 0.2) * yaw) * 5.0));
|
|
stack.rotateX(MathHelper.toRadians(pitch));
|
|
}
|
|
|
|
reset() {
|
|
if (this.minecraft.world !== null) {
|
|
this.scene.remove(this.minecraft.world.group);
|
|
}
|
|
this.webRenderer.clear();
|
|
this.overlay.clear();
|
|
}
|
|
} |