implement first person hand

(cherry picked from commit bfe5a2eaca2858a7ccde1df847e2148dd79f4045)
This commit is contained in:
LabyStudio
2022-05-02 02:45:24 +02:00
parent 230ea8d11e
commit 800134b26b
18 changed files with 345 additions and 114 deletions
@@ -260,7 +260,7 @@ export default class BlockRenderer {
}
}
renderBlockInHand(group, block, brightness) {
renderBlockInHandThirdPerson(group, block, brightness) {
this.tessellator.startDrawing();
// Render block
@@ -35,6 +35,10 @@ export default class WorldRenderer {
// Entity render manager
this.entityRenderManager = new EntityRenderManager(this);
this.equippedProgress = 0;
this.prevEquippedProgress = 0;
this.itemToRender = 0;
this.initialize();
}
@@ -51,6 +55,10 @@ export default class WorldRenderer {
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,
@@ -77,6 +85,9 @@ export default class WorldRenderer {
color: 0x000000,
}));
this.scene.add(this.blockHitBox);
// Hand group
this.handGroup = new THREE.Object3D();
}
render(partialTicks) {
@@ -95,18 +106,57 @@ export default class WorldRenderer {
// Render target block
this.renderBlockHitBox(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) {
entity.group.clear(); // Remove entity from scene
delete entity.group.buildMeta; // To trigger a rebuild on the next render
continue;
}
this.renderEntity(entity, partialTicks);
// Render entity
entity.renderer.render(entity, partialTicks);
entity.renderer.group.visible = true;
}
// Render hand
this.renderHand(partialTicks);
// Render actual scene
this.webRenderer.render(this.scene, this.camera);
// Render overlay with a static FOV
this.camera.fov = this.minecraft.settings.fov;
this.camera.updateProjectionMatrix();
this.webRenderer.render(this.overlay, this.camera);
}
onTick() {
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;
}
}
orientCamera(partialTicks) {
@@ -151,7 +201,7 @@ export default class WorldRenderer {
this.frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse));
// Update FOV
this.camera.fov = 85 + player.getFOVModifier();
this.camera.fov = this.minecraft.settings.fov + player.getFOVModifier();
this.camera.updateProjectionMatrix();
// Setup fog
@@ -294,6 +344,83 @@ export default class WorldRenderer {
this.skyGroup.rotation.set(angle * Math.PI * 2 + Math.PI / 2, 0, 0);
}
renderHand(partialTicks) {
// Hide hand before rendering
let player = this.minecraft.player;
let stack = player.renderer.handGroup;
stack.visible = false;
// Hide in third person
if (this.minecraft.settings.thirdPersonView !== 0) {
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;
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 yRotation = Math.sin(Math.sqrt(swingProgress) * Math.PI);
let zRotation = Math.sin(swingProgress * swingProgress * Math.PI);
// Bobbing animation
if (this.minecraft.settings.viewBobbing) {
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));
}
// Camera rotation movement
stack.rotateX(MathHelper.toRadians((player.rotationPitch - pitchArm) * 0.1));
stack.rotateY(MathHelper.toRadians((player.rotationYaw - yawArm) * 0.1));
// 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(yRotation * 70));
stack.rotateZ(MathHelper.toRadians(-zRotation * 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);
if (this.itemToRender === 0) {
// Render hand
player.renderer.renderRightArm(player, partialTicks);
} else {
// Render item
}
}
renderBlockHitBox(player, partialTicks) {
let hitResult = player.rayTrace(5, partialTicks);
let hitBoxVisible = !(hitResult === null);
@@ -333,8 +460,9 @@ export default class WorldRenderer {
}
}
renderEntity(entity, partialTicks) {
let entityRenderer = this.entityRenderManager.getEntityRendererByEntity(entity);
entityRenderer.render(entity, partialTicks);
translate(stack, x, y, z) {
stack.translateX(x);
stack.translateY(y);
stack.translateZ(z);
}
}
@@ -7,14 +7,14 @@ export default class EntityRenderManager {
this.worldRenderer = worldRenderer;
this.renderers = [];
this.push(PlayerEntity, new PlayerRenderer(worldRenderer));
this.push(PlayerEntity, PlayerRenderer);
}
push(entityType, entityRenderer) {
this.renderers[entityType.name] = entityRenderer;
}
getEntityRendererByEntity(entity) {
return this.renderers[entity.constructor.name];
createEntityRendererByEntity(entity) {
return new this.renderers[entity.constructor.name].prototype.constructor(this.worldRenderer);
}
}
@@ -6,22 +6,22 @@ export default class EntityRenderer {
constructor(model) {
this.model = model;
this.tessellator = new Tessellator();
this.group = new THREE.Object3D();
}
rebuild(entity) {
// Create meta for group
let group = entity.group;
let meta = {};
this.fillMeta(entity, meta);
group.buildMeta = meta;
this.group.buildMeta = meta;
// Clear meshes
group.clear();
this.group.clear();
// Apply brightness and rebuild
let brightness = group.buildMeta.brightness;
let brightness = this.group.buildMeta.brightness;
this.tessellator.setColor(brightness, brightness, brightness);
this.model.rebuild(this.tessellator, group);
this.model.rebuild(this.tessellator, this.group);
}
fillMeta(entity, meta) {
@@ -30,30 +30,25 @@ export default class EntityRenderer {
}
isRebuildRequired(entity) {
let group = entity.group;
if (typeof group.buildMeta === "undefined") {
if (typeof this.group.buildMeta === "undefined") {
return true;
}
// Compare meta of group
let currentMeta = {};
this.fillMeta(entity, currentMeta);
let previousMeta = group.buildMeta;
let previousMeta = this.group.buildMeta;
return JSON.stringify(currentMeta) !== JSON.stringify(previousMeta);
}
render(entity, partialTicks) {
if (this.isRebuildRequired(entity)) {
this.rebuild(entity);
}
let group = entity.group;
this.prepareModel(entity);
let rotationBody = this.interpolateRotation(entity.prevRenderYawOffset, entity.renderYawOffset, partialTicks);
let rotationHead = this.interpolateRotation(entity.prevRotationYawHead, entity.rotationYawHead, partialTicks);
let limbSwing = entity.prevLimbSwingAmount + (entity.limbSwingAmount - entity.prevLimbSwingAmount) * partialTicks;
let limbSwingAmount = entity.limbSwing - entity.limbSwingAmount * (1.0 - partialTicks);
let limbSwingStrength = entity.prevLimbSwingStrength + (entity.limbSwingStrength - entity.prevLimbSwingStrength) * partialTicks;
let limbSwing = entity.limbSwingProgress - entity.limbSwingStrength * (1.0 - partialTicks);
let yaw = rotationHead - rotationBody;
let pitch = entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * partialTicks;
@@ -64,20 +59,21 @@ export default class EntityRenderer {
let interpolatedZ = entity.prevZ + (entity.z - entity.prevZ) * partialTicks;
// Translate using interpolated position
group.position.setX(interpolatedX);
group.position.setY(interpolatedY + 1.4);
group.position.setZ(interpolatedZ);
this.group.position.setX(interpolatedX);
this.group.position.setY(interpolatedY + 1.4);
this.group.position.setZ(interpolatedZ);
// Actual size of the entity
let scale = 7.0 / 120.0;
group.scale.set(-scale, -scale, scale);
this.group.scale.set(-scale, -scale, scale);
// Rotate entity model
group.rotation.y = MathHelper.toRadians(-rotationBody + 180);
this.group.rotation.y = MathHelper.toRadians(-rotationBody + 180);
// Render entity model
let timeAlive = entity.ticksExisted + partialTicks;
this.model.render(entity, limbSwingAmount, limbSwing, timeAlive, yaw, pitch, partialTicks);
let stack = entity.renderer.group;
this.model.render(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks);
}
interpolateRotation(prevValue, value, partialTicks) {
@@ -90,4 +86,10 @@ export default class EntityRenderer {
return prevValue + partialTicks * factor;
}
prepareModel(entity) {
if (this.isRebuildRequired(entity)) {
this.rebuild(entity);
}
}
}
@@ -13,6 +13,11 @@ export default class PlayerRenderer extends EntityRenderer {
this.textureCharacter = new THREE.TextureLoader().load('src/resources/char.png');
this.textureCharacter.magFilter = THREE.NearestFilter;
this.textureCharacter.minFilter = THREE.NearestFilter;
// First person right-hand holder
this.handModel = null;
this.handGroup = new THREE.Object3D();
this.worldRenderer.overlay.add(this.handGroup);
}
rebuild(entity) {
@@ -22,16 +27,52 @@ export default class PlayerRenderer extends EntityRenderer {
// Render item in hand
let group = this.model.rightArm.bone;
let id = entity.inventory.getItemInSelectedSlot();
if (id !== 0) {
if (id !== 0 && this.worldRenderer.minecraft.settings.thirdPersonView !== 0) {
let block = Block.getById(id);
this.worldRenderer.blockRenderer.renderBlockInHand(group, block, entity.getEntityBrightness());
this.worldRenderer.blockRenderer.renderBlockInHandThirdPerson(group, block, entity.getEntityBrightness());
}
// Create first person right hand and attach it to the holder
this.handGroup.clear();
this.handModel = this.model.rightArm.clone();
this.handGroup.add(this.handModel.bone);
// Copy material and update depth test of the hand
let mesh = this.handModel.bone.children[0];
mesh.renerOrder = 999;
mesh.material = mesh.material.clone();
mesh.material.depthTest = false;
mesh.material.depthWrite = false;
}
render(entity, partialTicks) {
let swingProgress = entity.swingProgress - entity.prevSwingProgress;
if (swingProgress < 0.0) {
swingProgress++;
}
this.model.swingProgress = entity.prevSwingProgress + swingProgress * partialTicks;
this.model.hasItemInHand = entity.inventory.getItemInSelectedSlot() !== 0;
this.model.isSneaking = entity.sneaking;
super.render(entity, partialTicks);
}
renderRightArm(player, partialTicks) {
// Make sure the model is created
this.prepareModel(player);
// Set transform of renderer
this.model.swingProgress = 0;
this.model.hasItemInHand = false;
this.model.isSneaking = false;
this.model.setRotationAngles(player, 0, 0, 0, 0, 0, 0);
this.handModel.copyTransformOf(this.model.rightArm);
// Render the model
this.handGroup.visible = true;
this.handModel.render();
}
fillMeta(entity, meta) {
super.fillMeta(entity, meta);
meta.itemInHand = entity.inventory.getItemInSelectedSlot();
@@ -1,5 +1,3 @@
import Block from "../../world/block/Block.js";
export default class ItemRenderer {
constructor(minecraft, window) {
@@ -7,15 +5,13 @@ export default class ItemRenderer {
this.window = window;
this.items = [];
this.itemInHand = null;
this.itemInHandGroup = new THREE.Object3D();
}
initialize() {
// Create item camera
this.camera = new THREE.OrthographicCamera(0, 0, 0, 0, 0, 300);
this.camera.near = 0;
this.camera.far = 15;
this.camera.far = 100;
this.camera.rotation.order = 'ZYX';
this.camera.up = new THREE.Vector3(0, 1, 0);
@@ -37,8 +33,6 @@ export default class ItemRenderer {
this.webRenderer.sortObjects = false;
this.webRenderer.setClearColor(0x000000, 0);
this.webRenderer.clear();
this.scene.add(this.itemInHandGroup);
}
render(partialTicks) {
@@ -54,28 +48,6 @@ export default class ItemRenderer {
this.webRenderer.render(this.scene, this.camera);
}
renderItemInHand() {
let typeId = this.minecraft.player.inventory.getItemInSelectedSlot();
if (typeId === this.itemInHand) {
return; // Skip rebuilding if the item hasn't changed
}
// Clear previous mesh
this.itemInHandGroup.clear();
// Should render hand or item
if (typeId === 0) {
} else {
let block = Block.getById(typeId);
// this.minecraft.worldRenderer.blockRenderer.renderBlockInHand(block, this.itemInHandGroup, 1);
}
// Store item in hand meta
this.itemInHand = typeId;
}
renderItemInGui(renderId, block, x, y) {
let meta = this.items[renderId];
if (typeof meta === "undefined") {
@@ -10,8 +10,12 @@ export default class ModelBase {
}
render(entity, limbSwingAmount, limbSwing, timeAlive, yaw, pitch) {
entity.group.updateMatrix();
render(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks) {
stack.updateMatrix();
}
setRotationAngles(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks) {
}
}
@@ -10,6 +10,10 @@ export default class ModelPlayer extends ModelBase {
constructor() {
super();
this.swingProgress = 0;
this.hasItemInHand = false;
this.isSneaking = false;
let width = 64;
let height = 32;
@@ -59,20 +63,32 @@ export default class ModelPlayer extends ModelBase {
this.rightLeg.rebuild(tessellator, group);
}
render(entity, limbSwingAmount, limbSwing, timeAlive, yaw, pitch, partialTicks) {
let group = entity.group;
render(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks) {
this.setRotationAngles(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks);
// Render cubes
this.head.render();
this.body.render();
this.rightArm.render();
this.leftArm.render();
this.rightLeg.render();
this.leftLeg.render();
super.render(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks);
}
setRotationAngles(stack, limbSwing, limbSwingStrength, timeAlive, yaw, pitch, partialTicks) {
// Head rotation
this.head.rotateAngleY = MathHelper.toRadians(yaw);
this.head.rotateAngleX = MathHelper.toRadians(pitch);
// Limb swing leg animation
this.rightArm.rotateAngleX = Math.cos(limbSwingAmount * 0.6662 + Math.PI) * 2.0 * limbSwing * 0.5;
this.leftArm.rotateAngleX = Math.cos(limbSwingAmount * 0.6662) * 2.0 * limbSwing * 0.5;
this.rightArm.rotateAngleX = Math.cos(limbSwing * 0.6662 + Math.PI) * 2.0 * limbSwingStrength * 0.5;
this.leftArm.rotateAngleX = Math.cos(limbSwing * 0.6662) * 2.0 * limbSwingStrength * 0.5;
this.rightArm.rotateAngleZ = 0.0;
this.leftArm.rotateAngleZ = 0.0;
this.rightLeg.rotateAngleX = Math.cos(limbSwingAmount * 0.6662) * 1.4 * limbSwing;
this.leftLeg.rotateAngleX = Math.cos(limbSwingAmount * 0.6662 + Math.PI) * 1.4 * limbSwing;
this.rightLeg.rotateAngleX = Math.cos(limbSwing * 0.6662) * 1.4 * limbSwingStrength;
this.leftLeg.rotateAngleX = Math.cos(limbSwing * 0.6662 + Math.PI) * 1.4 * limbSwingStrength;
this.rightLeg.rotateAngleY = 0.0;
this.leftLeg.rotateAngleY = 0.0;
@@ -82,18 +98,12 @@ export default class ModelPlayer extends ModelBase {
this.leftArm.rotateAngleY = 0.0;
// Held item animation
if (entity.inventory.getItemInSelectedSlot() !== 0) {
if (this.hasItemInHand) {
this.rightArm.rotateAngleX = this.rightArm.rotateAngleX * 0.5 - (Math.PI / 10);
}
// Swing progress
let swingProgress = entity.swingProgress - entity.prevSwingProgress;
if (swingProgress < 0.0) {
swingProgress++;
}
let interpolatedSwingProgress = entity.prevSwingProgress + swingProgress * partialTicks;
if (interpolatedSwingProgress > -9990.0) {
let swingProgress = interpolatedSwingProgress;
if (this.swingProgress > -9990.0) {
let swingProgress = this.swingProgress;
this.body.rotateAngleY = Math.sin(Math.sqrt(swingProgress) * Math.PI * 2.0) * 0.2;
@@ -106,21 +116,21 @@ export default class ModelPlayer extends ModelBase {
this.leftArm.rotateAngleY += this.body.rotateAngleY;
this.leftArm.rotateAngleX += this.body.rotateAngleY;
swingProgress = 1.0 - interpolatedSwingProgress;
swingProgress = 1.0 - swingProgress;
swingProgress = swingProgress * swingProgress;
swingProgress = swingProgress * swingProgress;
swingProgress = 1.0 - swingProgress;
let value1 = Math.sin(swingProgress * Math.PI);
let value2 = Math.sin(interpolatedSwingProgress * Math.PI) * -(this.head.rotateAngleX - 0.7) * 0.75;
let value2 = Math.sin(swingProgress * Math.PI) * -(this.head.rotateAngleX - 0.7) * 0.75;
this.rightArm.rotateAngleX = (this.rightArm.rotateAngleX - (value1 * 1.2 + value2));
this.rightArm.rotateAngleY += this.body.rotateAngleY * 2.0;
this.rightArm.rotateAngleZ += Math.sin(interpolatedSwingProgress * Math.PI) * -0.4;
this.rightArm.rotateAngleZ += Math.sin(swingProgress * Math.PI) * -0.4;
}
// Sneaking animation
if (entity.sneaking) {
if (this.isSneaking) {
this.body.rotateAngleX = 0.5;
this.rightArm.rotateAngleX += 0.4;
this.leftArm.rotateAngleX += 0.4;
@@ -130,7 +140,7 @@ export default class ModelPlayer extends ModelBase {
this.leftLeg.rotationPointY = 9.0;
this.head.rotationPointY = 1.0;
group.translateY(-0.2);
stack.translateY(-0.2);
} else {
this.body.rotateAngleX = 0.0;
this.rightLeg.rotationPointZ = 0.1;
@@ -145,16 +155,6 @@ export default class ModelPlayer extends ModelBase {
this.leftArm.rotateAngleZ -= Math.cos(timeAlive * 0.09) * 0.05 + 0.05;
this.rightArm.rotateAngleX += Math.sin(timeAlive * 0.067) * 0.05;
this.leftArm.rotateAngleX -= Math.sin(timeAlive * 0.067) * 0.05;
// Render cubes
this.head.render();
this.body.render();
this.rightArm.render();
this.leftArm.render();
this.rightLeg.render();
this.leftLeg.render();
super.render(entity, limbSwingAmount, limbSwing, timeAlive, yaw, pitch);
}
}
@@ -262,4 +262,31 @@ export default class ModelRenderer {
this.bone.updateMatrix();
}
clone() {
let modelRenderer = new ModelRenderer(this.name, this.textureWidth, this.textureHeight);
modelRenderer.bone = this.bone.clone();
modelRenderer.textureOffsetX = this.textureOffsetX;
modelRenderer.textureOffsetY = this.textureOffsetY;
modelRenderer.cubes = this.cubes;
modelRenderer.copyTransformOf(this);
for (let i = 0; i < this.children.length; i++) {
let child = this.children[i];
modelRenderer.addChild(child.clone());
}
return modelRenderer;
}
copyTransformOf(modelRenderer) {
this.rotationPointX = modelRenderer.rotationPointX;
this.rotationPointY = modelRenderer.rotationPointY;
this.rotationPointZ = modelRenderer.rotationPointZ;
this.scaleX = modelRenderer.scaleX;
this.scaleY = modelRenderer.scaleY;
this.scaleZ = modelRenderer.scaleZ;
this.rotateAngleX = modelRenderer.rotateAngleX;
this.rotateAngleY = modelRenderer.rotateAngleY;
this.rotateAngleZ = modelRenderer.rotateAngleZ;
}
}