@croquet/microverse-library
Version:
An npm package version of Microverse
335 lines (271 loc) • 12.6 kB
JavaScript
// the following import statement is solely for the type checking and
// autocompletion features in IDE. A Behavior cannot inherit from
// another behavior or a base class but can use the methods and
// properties of the card to which it is installed.
// The prototype classes ActorBehavior and PawnBehavior provide
// the features defined at the card object.
import {ActorBehavior, PawnBehavior} from "../PrototypeBehavior";
class AvatarActor extends ActorBehavior {
setup() {
this._cardData.animationClipIndex = 9;
this.say("animationStateChanged");
this.listen("poseAvatarRequest", "poseAvatar");
this.listen("setAvatarData", "resetPose");
}
poseAvatar(data) {
this.lastPose = data;
this.say("avatarPosed", data);
}
resetPose() {
this.lastPose = {type: "move", coordinates: [0, 1, -100], pointing: false};
this.say("avatarPosed", this.lastPose);
}
}
class AvatarPawn extends PawnBehavior {
setup() {
this.subscribe(this.id, "3dModelLoaded", "modelLoaded");
this.listen("avatarPosed", "avatarPosed");
if (this.avatarModel) {
this.modelLoaded();
}
if (!this.isMyPlayerPawn) {return;}
this.addFirstResponder("pointerTap", {ctrlKey: true, altKey: true}, this);
this.addEventListener("pointerTap", this.pointerTap);
this.addFirstResponder("pointerDown", {ctrlKey: true, altKey: true}, this);
this.addLastResponder("pointerDown", {}, this);
this.addEventListener("pointerDown", this.pointerDown);
this.addFirstResponder("pointerMove", {ctrlKey: true, altKey: true}, this);
this.addLastResponder("pointerMove", {}, this);
this.addEventListener("pointerMove", this.pointerMove);
this.addLastResponder("pointerUp", {ctrlKey: true, altKey: true}, this);
this.addEventListener("pointerUp", this.pointerUp);
this.addLastResponder("pointerWheel", {ctrlKey: true, altKey: true}, this);
this.addEventListener("pointerWheel", this.pointerWheel);
this.removeEventListener("pointerDoubleDown", "onPointerDoubleDown");
this.addFirstResponder("pointerDoubleDown", {shiftKey: true}, this);
this.addEventListener("pointerDoubleDown", this.addSticky);
this.addLastResponder("keyDown", {ctrlKey: true}, this);
this.addEventListener("keyDown", this.keyDown);
this.addLastResponder("keyUp", {ctrlKey: true}, this);
this.addEventListener("keyUp", this.keyUp);
this.addUpdateRequest(["HalfBodyAvatarEventHandler$AvatarPawn", "maybeMove"]);
}
handlingEvent(type, target, event) {
if (type.startsWith("pointer")) {
const render = this.service("ThreeRenderManager");
let rc;
if (type === "pointerDown") {
rc = this.pointerRaycast(event, render.threeLayerUnion('pointer', "walk"));
} else {
rc = this.pointerRaycast(event, render.threeLayerUnion("walk"));
}
let p3e = this.pointerEvent(rc, event);
this.move(type, p3e.xyz);
}
}
modelLoaded() {
this.avatarModel = this.shape.children[0];
let found = false;
if (this.avatarModel) {
this.bones = new Map();
this.avatarModel.traverse((mesh) => {
if (mesh.isBone) {
this.bones.set(mesh.name, mesh);
if (mesh.name === "Spine") {
found = true;
}
}
});
}
if (found) {
console.log("ready player me avatar found");
}
this.addFoot();
if (this.actor.lastPose) {
this.avatarPosed(this.actor.lastPose);
}
/*
[
"Hips", "Spine", "Neck", "Head", "RightEye", "LeftEye",
"RightHand", "RightHandIndex1", "RightHandIndex2", "RightHandIndex3", "RightHandMiddle1",
"RightHandMiddle2", "RightHandMiddle3", "RightHandRing1", "RightHandRing2", "RightHandRing3",
"RightHandPinky1", "RightHandPinky2", "RightHandPinky3", "RightHandThumb1", "RightHandThumb2",
"RightHandThumb3",
"LeftHand", "LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "LeftHandMiddle1",
"LeftHandMiddle2", "LeftHandMiddle3", "LeftHandRing1", "LeftHandRing2", "LeftHandRing3",
"LeftHandPinky1", "LeftHandPinky2", "LeftHandPinky3", "LeftHandThumb1", "LeftHandThumb2",
"LeftHandThumb3"
];
*/
}
addFoot() {
let foot = this.shape.children.find((c) => c.name === "ghostfoot");
if (foot) {foot.removeFromParent();}
let circle = new Microverse.THREE.CircleGeometry(0.3, 32);
circle.rotateX(-Math.PI / 2);
let material = new Microverse.THREE.MeshBasicMaterial({color: 0x666666, opacity: 0.3, transparent: true});
foot = new Microverse.THREE.Mesh(circle, material);
foot.position.set(0, -1.6, 0);
foot.name = "ghostfoot";
this.shape.add(foot);
}
move(type, xyz) {
if (!xyz) {return;}
this.say("poseAvatarRequest", {type, coordinates: xyz, pointing: true}, 30);
}
avatarPosed(data) {
if (!this.bones) {return;}
this.handedness = this.actor._cardData.handedness === "Left" ? "Left" : "Right";
this.otherHandName = this.actor._cardData.handedness === "Left" ? "RightHand" : "LeftHand";
let otherHand = this.bones.get(this.otherHandName);
otherHand.position.set();
let {type, coordinates, pointing} = data;
if (type === "pointerMove" || type === "move") {
this.moveHead(coordinates);
}
let pointingChanged = this.isPointing !== pointing;
if (pointingChanged) {
this.isPointing = pointing;
}
if (type === "keyDown" || type === "pointerDown" || type === "pointerUp" || type === "pointerTap" || pointingChanged) {
this.moveHand(coordinates, pointing);
}
}
moveHead(xyz) {
let {
THREE,
q_lookAt, q_pitch, q_euler, q_yaw, q_roll, q_multiply, q_slerp, q_identity,
v3_normalize
} = Microverse;
let head = this.bones.get("Head");
let neck = this.bones.get("Neck");
let global = neck.matrixWorld.clone();
let dataRotation = new THREE.Matrix4();
dataRotation.makeRotationY(-Math.PI);
let headOffset = new THREE.Matrix4();
headOffset.makeTranslation(...head.position.toArray());
global.multiply(dataRotation);
global.multiply(headOffset);
global.invert();
let local = new Microverse.THREE.Vector3(...xyz);
local.applyMatrix4(global);
let normLocal = v3_normalize(local.toArray());
let normHere = [0, 0, -1];
let allQ = q_lookAt(normHere, [0, 1, 0], normLocal);
if (Number.isNaN(allQ[0]) || Number.isNaN(allQ[1]) || Number.isNaN(allQ[2]) || Number.isNaN(allQ[3])) {
console.log("nande?");
return;
}
let halfQ = q_slerp(q_identity(), allQ, 0.5);
let leftEye = this.bones.get("LeftEye");
let rightEye = this.bones.get("RightEye");
head.rotation.set(-q_pitch(halfQ), q_yaw(halfQ), q_roll(halfQ));
let eyeQ = q_multiply(q_euler(-1.57, 0, Math.PI), halfQ);
leftEye.rotation.set(q_pitch(eyeQ), q_yaw(eyeQ), q_roll(eyeQ));
rightEye.rotation.set(q_pitch(eyeQ), q_yaw(eyeQ), q_roll(eyeQ));
}
moveHand(xyz, pointing) {
let {
THREE,
q_euler, q_pitch, q_yaw, q_roll, q_lookAt, q_multiply,// q_identity,
v3_normalize, v3_rotate, v3_add} = Microverse;
let len = 0.6582642197608948 * 0.3;
let hFactor = this.handedness === "Left" ? -1 : 1;
let elbowPos = [-len * hFactor, 0, 0.2];
let handPos = [0, 0, hFactor * len * 0.3];
let hand = this.bones.get(`${this.handedness}Hand`);
let spine = this.bones.get("Spine");
let global = spine.matrixWorld.clone();
let dataRotation = new THREE.Matrix4();
dataRotation.makeRotationY(-Math.PI);
let elbowOffset = new THREE.Matrix4();
elbowOffset.makeTranslation(...elbowPos);
global.multiply(dataRotation);
global.multiply(elbowOffset);
global.invert();
// console.log(this.actor._cardData.animationClipIndex);
if (!pointing) {
hand.rotation.set(Math.PI, hFactor * Math.PI / 2, 0);
hand.position.set();//...v3_add(elbowPos, [0, -0.25, -0.3]));
this.say("setAnimationClipIndex", 0);
} else {
this.say("setAnimationClipIndex", this.handedness === "Left" ? 12 : 9);
let local = new Microverse.THREE.Vector3(...xyz);
local.applyMatrix4(global);
local = local.toArray();
local[2] = Math.min(local[2], -1);
let normLocal = v3_normalize(local);
let normHere = [0, 0, -1];
let allQ = q_lookAt(normHere, [0, 1, 0], normLocal);
// console.log("hand", q_pitch(allQ), q_yaw(allQ), q_roll(allQ));
let tQ = q_multiply(allQ, q_euler(-Math.PI / 2, 0, 0));
hand.rotation.set(-q_pitch(tQ), q_yaw(tQ), q_roll(tQ));
hand.position.set(...v3_add(elbowPos, v3_rotate(handPos, tQ)));
}
}
up(p3d) {
this._plane = null;
let avatar = Microverse.GetPawn(p3d.avatarId);
avatar.removeFirstResponder("pointerMove", {}, this);
}
walkLook() {
let {m4_translation, q_axisAngle, m4_rotationQ, m4_multiply} = Microverse;
const pitchRotation = q_axisAngle([1,0,0], this.lookPitch);
const m0 = m4_translation(this.lookOffset);
const m1 = m4_translation([0, 0.2, 0]); // needs to be eye height;
const tr = m4_multiply(m1, m0);
const m2 = m4_rotationQ(pitchRotation);
const m3 = m4_multiply(m2, tr);
return m4_multiply(m3, this.global);
}
maybeMove() {
let velocity = Microverse.v3_magnitude(this.velocity);
let moving = velocity > 0.001;
let movingChanged = this.moving !== moving;
if (movingChanged) {
this.moving = moving;
let xyz = Microverse.v3_rotate([0, 0, -10], this.rotation);
this.say("poseAvatarRequest", {type: "move", coordinates: xyz, pointing: !this.moving}, 30);
}
}
mapOpacity(avatar, opacity) {
if (this._target === avatar && Microverse.v3_magnitude(this.lookOffset) < 0.8) {return 0;}
if (opacity === 0 || opacity === 1) {return opacity;}
return 1;
}
teardown() {
delete this.bones;
this.removeUpdateRequest(["HalfBodyAvatarEventHandler$AvatarPawn", "maybeMove"]);
if (!this.isMyPlayerPawn) {return;}
console.log("avatar event handler detached");
this.removeFirstResponder("pointerTap", {ctrlKey: true, altKey: true}, this);
this.removeEventListener("pointerTap", this.pointerTap);
this.removeFirstResponder("pointerDown", {ctrlKey: true, altKey: true}, this);
this.removeLastResponder("pointerDown", {}, this);
this.removeEventListener("pointerDown", this.pointerDown);
this.removeFirstResponder("pointerMove", {ctrlKey: true, altKey: true}, this);
this.removeLastResponder("pointerMove", {}, this);
this.removeEventListener("pointerMove", this.pointerMove);
this.removeLastResponder("pointerUp", {ctrlKey: true, altKey: true}, this);
this.removeEventListener("pointerUp", this.pointerUp);
this.removeLastResponder("pointerWheel", {ctrlKey: true, altKey: true}, this);
this.removeEventListener("pointerWheel", this.pointerWheel);
this.removeEventListener("pointerDoubleDown", "onPointerDoubleDown");
this.removeFirstResponder("pointerDoubleDown", {shiftKey: true}, this);
this.removeEventListener("pointerDoubleDown", this.addSticky);
this.removeLastResponder("keyDown", {ctrlKey: true}, this);
this.removeEventListener("keyDown", this.keyDown);
this.removeLastResponder("keyUp", {ctrlKey: true}, this);
this.removeEventListener("keyUp", this.keyUp);
}
}
export default {
modules: [
{
name: "HalfBodyAvatarEventHandler",
actorBehaviors: [AvatarActor],
pawnBehaviors: [AvatarPawn],
}
]
}
/* globals Microverse */