@croquet/microverse-library
Version:
An npm package version of Microverse
664 lines (587 loc) • 27.4 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 GizmoActor extends ActorBehavior {
setup() {
this.target = this._cardData.target;
this.creatorId = this._cardData.creatorId;
let box = this.target.editBox;
let scale = this.target.scale;
this.isGizmoManipulator = true;
this.editScale = Math.max(scale[0] * (box[3] - box[0]), scale[2] * (box[5] - box[2]));
this.editFloor = scale[1] * box[1] - 0.1;
this.addHorizontalDragGizmo();
this.addVerticalDragGizmo();
this.addSpinGizmo();
this.addPropertySheetButton();
this.subscribe(this.target.id, "translationSet", "translateTarget");
this.subscribe(this.target.id, "rotationSet", "rotateTarget");
this.subscribe(this.target.id, "scaleSet", "scaleTarget");
}
/*
initializeGizmo(data) {
let {parent, target, creatorId} = data;
// if (parent) {
// this.set({parent});
// }
this.target = target;
this.creatorId = creatorId;
}
*/
getScale(m) {
let x = [m[0], m[1], m[2]];
let y = [m[4], m[5], m[6]];
let z = [m[8], m[9], m[10]];
let length = (v) => Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
return [length(x), length(y), length(z)];
}
translateTarget() {
//let t = Microverse.m4_getTranslation(this.actor.parent.target.global);
//let s = this.target.parent ? this.getScale(this.target.parent.global) : [1, 1, 1];
// this.set({translation: t}); //, scale: [1 / s[0], 1 / s[1], 1 / s[2]]});
// this.target.set({translation:t});
}
rotateTarget() {
// in this case, gizmo itself should not rotate but the gyro should rotate so the three rings
// show the euler angle of them in a sane way.
//let r = this.target.rotation;
// this.set({rotation: r});
/*
if (this.gizmoMode === "move") {
this.set({rotation: [0, 0, 0, 1]});
} else {
let r = this.target.rotation;
this.set({rotation: r});
}*/
}
scaleTarget() {}
closestCorner(creatorId) {
let avatar = [...this.service("ActorManager").actors].find(([_k, actor]) => {
return actor.playerId === creatorId;
});
if (avatar) {
avatar = avatar[1];
}
if (!avatar) {return;}
let {m4_identity, v3_transform, v3_magnitude, v3_sub, v3_add} = Microverse;
let target = this.target;
let parentGlobal = target._parent ? target._parent.global : m4_identity();
let t = target.translation;
let g = v3_transform(t, parentGlobal);
let a = avatar.translation;
let offsets = [
[ 2, 1, 2],
[-2, 1, 2],
[ 2, -1, 2],
[-2, -1, 2],
[ 2, 1, -2],
[-2, 1, -2],
[ 2, -1, -2],
[-2, -1, -2]
];
let locals = offsets.map((o) => v3_add(t, o));
let parents = locals.map((l) => v3_transform(l, parentGlobal));
let dist = Number.MAX_VALUE;
let min = -1;
for (let i = 0; i < parents.length; i++) {
let thisDist = v3_magnitude(v3_sub(a, parents[i]));
if (thisDist <= dist && parents[i][1] > g[1]) {
dist = thisDist;
min = i;
}
}
return offsets[min];
}
addHorizontalDragGizmo(){
if(this.horzGizmo)this.horzGizmo.destroy();
let dotCircle = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjU2IDhDMTE5LjAzMyA4IDggMTE5LjAzMyA4IDI1NnMxMTEuMDMzIDI0OCAyNDggMjQ4IDI0OC0xMTEuMDMzIDI0OC0yNDhTMzkyLjk2NyA4IDI1NiA4em04MCAyNDhjMCA0NC4xMTItMzUuODg4IDgwLTgwIDgwcy04MC0zNS44ODgtODAtODAgMzUuODg4LTgwIDgwLTgwIDgwIDM1Ljg4OCA4MCA4MHoiLz48L3N2Zz4=";
this.poseGizmo = this.createCard({
name: "drag horizontal gizmo",
dataLocation: dotCircle,
fileName:`./assets/SVG/dot-circle.svg`,
modelType: 'svg',
shadow:true,
singleSided: true,
scale:[1,1,0.25],
rotation:[Math.PI / 2, 0, 0],
translation:[0, this.editFloor - 0.5, 0],
type:'2d',
fullBright: false,
behaviorModules: ["PoseGizmo"],
parent: this,
noSave: true,
action: 'dragHorizontal',
plane: [0,1,0],
color: 0x88ff88,
frameColor: 0xaaaaaa
});
}
addVerticalDragGizmo(){
if(this.vertGizmo)this.vertGizmo.destroy();
this.vertGizmo = this.createCard({
name: "drag vertical gizmo",
//dataLocation:`./assets/SVG/arrows-alt.svg`,
//fileName:`./assets/SVG/arrows-alt.svg`,
//modelType: 'svg',
radius: 0.25,
shadow:true,
singleSided: true,
//scale:[1,1,0.25],
//rotation:[0, Math.PI/2, 0 ],
translation:[0, this.editFloor - 0.25, 0],
type:'object',
fullBright: false,
behaviorModules: ["PoseGizmo"],
parent: this,
noSave: true,
action: 'dragVertical',
color: 0xffff88,
plane: [0,0,1]
});
}
addSpinGizmo(){
if (this.spinGizmo) this.spinGizmo.destroy();
let cog = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iODg4cHgiIGhlaWdodD0iODg4cHgiIHZpZXdCb3g9IjAgMCA4ODggODg4IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA1My4xICg3MjYzMSkgLSBodHRwczovL3NrZXRjaGFwcC5jb20gLS0+CiAgICA8dGl0bGU+U2hhcGU8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8cGF0aCBkPSJNNDgzLjE5MTg3Nyw4ODYuMjk0MDA5IEw1NDAuMjU2NDQ1LDgwNS4xMTg1NyBMNjMwLjU1NzY5Myw4NDcuMDIyMjgzIEM2NTQuNTE2OTkzLDgzNS45MTI5NDkgNjc3LjMxOTA4Nyw4MjIuNzI5OTcxIDY5OC43MzM1NTYsODA3LjcwMzc2OCBMNzA3LjYyODE1LDcwOC40MTQ4NTIgTDgwNy4xODA4MTQsNjk5LjQ3NzIyNCBDODIyLjEzOTAwNyw2NzguMjUxOTk0IDgzNS4yODM1ODMsNjU1LjY1NjgyIDg0Ni4zODkxNjUsNjMxLjkxNzA3OCBMODA0LjIyNjkzMSw1NDAuODYxMzg5IEw4ODYuMzA3NzAzLDQ4My4wMzQ5ODYgQzg4Ny40MjgwNjMsNDcwLjE3MjU4MiA4ODgsNDU3LjE1MzA4MyA4ODgsNDQ0IEM4ODgsNDMwLjk0ODg0NCA4ODcuNDM2ODkzLDQxOC4wMjkyMDggODg2LjMzMzY3OSw0MDUuMjY0MDg1IEw4MDQuMTY5MjIyLDM0Ny4zNTM5OTUgTDg0Ni4zODg3OTEsMjU2LjA4MjEyMyBDODM1LjMyNjI4MywyMzIuNDM0NTggODIyLjI0MDU4NSwyMDkuOTIyNzM2IDgwNy4zNTQ0NjEsMTg4Ljc2OTM1MyBMNzA3LjQ3MDQ4NiwxNzkuNzQyODIgTDY5OC40ODczNjYsODAuMTIzNjA3OSBDNjc3LjE0NDQxOCw2NS4xNjkwOTIyIDY1NC40MjUzNDEsNTIuMDQ0NTcgNjMwLjU1NzkxNSw0MC45Nzc4MTk5IEw1NDAuMDQxMDcyLDgyLjkzOTE0MjUgTDQ4Mi44OTMxNjEsMS42Nzk5NjYzIEM0NzAuMDc2ODgsMC41Njc3NDAyOTkgNDU3LjEwNDc0MSwwIDQ0NCwwIEM0MzAuNDI5NzUyLDAgNDE3LjAwMTY5NCwwLjYwODc5MTI2NiA0MDMuNzQxNjQ5LDEuODAwNTUxOTcgTDM0Ni43NDM1NTUsODIuODgxNDMwNSBMMjU2Ljk0MjUxNyw0MS4yMDk4MzYxIEMyMzIuNzY3NjYsNTIuNDU1Nzc0OCAyMDkuNzc0MTQ2LDY1LjgxNDA0NTUgMTg4LjE5OTE0NSw4MS4wNDc0NzYzIEwxNzkuMzcxODUsMTc5LjU4NTE0OCBMODAuODg2NzQ5MywxODguNDI2OTMzIEM2NS42NzQ2MjE1LDIxMC4wMDA0MTkgNTIuMzM2NDkwOCwyMzIuOTkwMDc1IDQxLjEwOTE1MDcsMjU3LjE1OTEwOSBMODIuNzczMDY5NCwzNDcuMTM4NjExIEwxLjc1ODM1NDQ0LDQwNC4yMTM5NzEgQzAuNTk0NDIyNTAzLDQxNy4zMjA1MzcgMCw0MzAuNTkwODUxIDAsNDQ0IEMwLDQ1Ny4zMDcxNiAwLjU4NTQxNDY3MSw0NzAuNDc3NTkgMS43MzE4OTUwOSw0ODMuNDg2OTQgTDgyLjgzMDc3ODQsNTQwLjY0NjAwNSBMNDEuMTA5MjcwNiw2MzAuODQxMTUgQzUyLjI5MzkzNzEsNjU0LjkxODI3OSA2NS41NzMzNzI1LDY3Ny44MjQ5ODIgODAuNzEzNDc0MSw2OTkuMzI3MTU2IEwxNzkuNTI5NTE0LDcwOC4yNTcxOCBMMTg4LjQ0NDk2NSw4MDcuMTI1OTY1IEMyMDkuOTQ4NzgzLDgyMi4yODczNzIgMjMyLjg1OTQ3MSw4MzUuNTg2OTM3IDI1Ni45NDI1NDgsODQ2Ljc5MDE3OCBMMzQ2Ljk1ODkyOCw4MDUuMDYwODU4IEw0MDQuMDQwODc2LDg4Ni4yMjYyNCBDNDE3LjIwMzY5NSw4ODcuNDAwMzMyIDQzMC41MzE4MTgsODg4IDQ0NCw4ODggQzQ1Ny4yMDY1NjQsODg4IDQ3MC4yNzg0NTcsODg3LjQyMzQwMyA0ODMuMTkxODc3LDg4Ni4yOTQwMDkgWiBNNDQzLjk0NDUwOSw3MTcuMzQ4MiBDMjkzLjcyMzA1Nyw3MTcuMzQ4MiAxNzEuOTQ0NTA5LDU5NS4zNzE4OTMgMTcxLjk0NDUwOSw0NDQuOTA2NDkzIEMxNzEuOTQ0NTA5LDI5NC40NDEwOTMgMjkzLjcyMzA1NywxNzIuNDY0Nzg2IDQ0My45NDQ1MDksMTcyLjQ2NDc4NiBDNTk0LjE2NTk2MSwxNzIuNDY0Nzg2IDcxNS45NDQ1MDksMjk0LjQ0MTA5MyA3MTUuOTQ0NTA5LDQ0NC45MDY0OTMgQzcxNS45NDQ1MDksNTk1LjM3MTg5MyA1OTQuMTY1OTYxLDcxNy4zNDgyIDQ0My45NDQ1MDksNzE3LjM0ODIgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjM0YzRjNGIiBmaWxsLXJ1bGU9Im5vbnplcm8iPjwvcGF0aD4KICAgIDwvZz4KPC9zdmc+";
this.vspinGizmo = this.createCard({
name: "spin horizontal gizmo",
dataLocation: cog,
fileName: "./assets/SVG/cog.svg",
modelType: "svg",
shadow:true,
singleSided: true,
scale: [2,2,0.25],
rotation: [Math.PI / 2, 0, 0 ],
translation: [0, this.editFloor - 0.5, 0],
type: '2d',
fullBright: false,
behaviorModules: ["PoseGizmo"],
parent: this,
noSave: true,
action: "spinHorizontal",
plane: [0,0,1],
color: 0x8888ff,
frameColor: 0xaaaaaa
});
}
addPropertySheetButton() {
if (this.propertySheetButton) {
this.propertySheetButton.destroy();
}
// let t = this.closestCorner(this.creatorId);
this.dataLocation = "3ryPlwPIvSHXjABwnuJjrMQYPp1JH2OnghLGR_cdAbCEGgYGAgFIXV0UGx4XAVwHAVwRAB0DBxcGXBsdXQddNRYkEAseOwEzGSMRMCoWQTUKEwQLBSc5JSsrQF0bHVwRAB0DBxcGXB8bEQAdBBcAARddBRckQ0E8AEUcRwABPTExOhMCHEMeNENFIAglOitFNBg0OjNGPiYQIl81Sl0WEwYTXUMeCgYrHDc7HkECPScbJxYKOSE6RCgXK0YgFwYgCzAYPioRCkEkAEYVLRU";
this.propertySheetButton = this.createCard({
name: "property sheet button",
dataLocation: this.dataLocation,
fileName: "/prop-plain.svg",
modelType: "svg",
shadow: true,
singleSided: true,
scale: [0.25, 0.25, 0.25],
type: "2d",
fullBright: true,
behaviorModules: ["GizmoPropertySheetButton", "Billboard"],
noSave: true,
translation:[1.25, this.editFloor - 0.25, 0],
parent: this
});
this.subscribe(this.propertySheetButton.id, "openPropertySheet", "openPropertySheet");
}
openPropertySheet(toWhom) {
console.log(toWhom);
this.target.showControls(toWhom);
}
teardown() {
if (this.target) {
this.target.sayUnselectEdit();
}
}
}
class GizmoPawn extends PawnBehavior {
setup() {
this.lastTime = this.now();
this.isMine = this.actor.creatorId === this.viewId;
if (this.isMine && !this.interval) {
this.interval = setInterval(() => this.checkInteraction(), 1000);
}
let assetManager = this.service("AssetManager").assetManager;
let dataLocation = this.actor.dataLocation;
this.getBuffer(dataLocation).then((buffer) => {
assetManager.setCache(dataLocation, buffer, "global");
});
this.subscribe(this.id, "interaction", "interaction");
}
checkInteraction() {
let now = this.now();
if (now - this.lastTime > 15000) {
if (this.interval) {
clearInterval(this.interval);
}
let avatar = this.getMyAvatar();
if ((!avatar.actor.gizmo) || avatar.actor.gizmo.id !== this.actor.id) {return;}
this.publish(avatar.actor.id, "goodByeGizmo", this.actor.id);
}
}
interaction() {
this.lastTime = this.now();
}
forceOnTop(obj3d) {
obj3d.traverse((obj) => {
if (obj.geometry) {
obj.renderOrder = 10000; // draw last
let m = obj.material;
let mat;
mat = Array.isArray(m) ? m : [m];
mat.forEach(m => {
m.opacity = 0.9999;
m.transparent = true;
m.depthTest = false;
m.depthWrite = false;
});
}
});
}
}
class GizmoPropertySheetButtonPawn extends PawnBehavior {
setup() {
let isMine = this.parent?.actor.creatorId === this.viewId;
this.isMine = isMine;
this.subscribe(this.id, "2dModelLoaded", "svgLoaded");
this.parent.call("Gizmo$GizmoPawn", "forceOnTop", this.shape);
if (isMine) {
this.addEventListener("pointerMove", "nop");
this.addEventListener("pointerEnter", "hilite");
this.addEventListener("pointerLeave", "unhilite");
this.addEventListener("pointerTap", "openPropertySheet");
// effectively prevent propagation
this.addEventListener("pointerDown", "nop");
this.addEventListener("pointerUp", "nop");
}
}
svgLoaded() {
if (this.shape.children[0]) {
this.parent.call("Gizmo$GizmoPawn", "forceOnTop", this.shape.children[0]);
}
}
setColor() {
let svg = this.shape.children[0];
let backdrop;
if (svg) {
backdrop = svg.children[0];
}
let baseColor = this.entered ? 0x888888 : 0x4a4a4a;
if (backdrop.material && backdrop.material[0]) {
backdrop.material && backdrop.material[0].color.setHex(baseColor);
}
}
hilite() {
this.entered = true;
this.setColor();
this.publish(this.parent.id, "interaction");
}
unhilite() {
this.entered = false;
this.setColor();
this.publish(this.parent.id, "interaction");
}
openPropertySheet(event) {
let avatar = Microverse.GetPawn(event.avatarId);
this.publish(this.actor.id, "openPropertySheet", {avatar: event.avatarId, distance: avatar.targetDistance});
this.destroy();
}
}
class PoseGizmoActor extends ActorBehavior {
setup() {
console.log("PoseGizmo", this.id);
this.isGizmoManipulator = true;
if (this._cardData.action === "dragHorizontal" || this._cardData.action === "dragVertical") {
this.subscribe(this.parent.id, "translateTarget" + this.id, "translateTarget");
} else if (this._cardData.action === "spinHorizontal") {
this.subscribe(this.parent.id, "spinTarget" + this.id, "spinTarget");
}
this.baseRotation = [...this.parent.target.rotation];
}
translateTarget(translation) {
let target = this.parent.target;
if(target.parent){
let mInverse = Microverse.m4_invert(target.parent.global);
let t = Microverse.v3_transform(translation, mInverse);
target.set({translation: t});
}else target.set({translation: translation});
this.parent.set({translation: translation});
}
q_toAxisAngle(quat) {
let q = Microverse.q_normalize(quat);
let angle = 2 * Math.acos(q[3]);
let axis = [];
let s = Math.sqrt( 1 - q[3] * q[3]);
// assuming quaternion normalised then w is less than 1, so term always positive.
if (s < 0.001) { // test to avoid divide by zero, s is always positive due to sqrt
// if s is close to zero then direction of axis not important
axis[0] = 0;
axis[1] = 1;
axis[2] = 0;
} else {
axis[0] = q[0] / s; // normalise axis
axis[1] = q[1] / s;
axis[2] = q[2] / s;
}
return {axis: axis, angle:angle};
}
spinTarget(rotation){
let target = this.parent.target;
let r;
if(target.parent){
// compute the axis of the rotation so we can transform that into the target frame
let axisAngle = this.q_toAxisAngle(rotation);
let mNormal = Microverse.m4_toNormal4(target.parent.global);
let mInverse = Microverse.m4_invert(mNormal);
let axis = Microverse.v3_transform(axisAngle.axis, mInverse);
axis = Microverse.v3_normalize(axis);
// axis is now in the frame of the target object - the rest is easy
let r = Microverse.q_axisAngle(axis, axisAngle.angle);
r = Microverse.q_multiply(this.baseRotation, r);
target.set({rotation: r});
}else {
r = Microverse.q_multiply(this.baseRotation, rotation);
target.set({rotation: r});
}
this.parent.set({rotation: rotation});
}
}
class PoseGizmoPawn extends PawnBehavior {
setup() {
this.originalColor = this.actor._cardData.color;
this.action = this.actor._cardData.action;
this.plane = this.actor._cardData.plane;
let isMine = this.parent?.actor.creatorId === this.viewId;
this.isMine = isMine;
let THREE = Microverse.THREE;
this.baseVector = new THREE.Vector3();
this.vec = new THREE.Vector3();
this.vec2 = new THREE.Vector3();
this.toVector = new THREE.Vector3();
this.deltaYaw = 0;
this.newRotation = Microverse.q_euler(0, this.deltaYaw, 0);
if (isMine) {
if (this.action === "spinHorizontal") {
console.log("spin horizontal pose")
this.addEventListener("pointerDown", "startDrag");
this.addEventListener("pointerMove", "hSpin");
this.addEventListener("pointerUp", "endDrag");
}else if (this.action === "dragHorizontal") {
console.log("drag horizontal pose")
this.addEventListener("pointerDown", "startDrag");
this.addEventListener("pointerMove", "drag");
this.addEventListener("pointerUp", "endDrag");
} else if (this.action === "dragVertical") {
console.log("drag pose")
this.addEventListener("pointerDown", "startVDrag");
this.addEventListener("pointerMove", "drag");
this.addEventListener("pointerUp", "endDrag");
}
this.addEventListener("pointerEnter", "pointerEnter");
this.addEventListener("pointerLeave", "pointerLeave");
}
if (this.actor._cardData.radius) {
this.makeSphere(this.actor._cardData.radius, this.actor._cardData.color);
} else {
this.subscribe(this.id, "2dModelLoaded", "modelLoaded");
}
}
makeSphere(radius, color) {
const geometry = new Microverse.THREE.SphereGeometry(radius, 32, 16);
const material = new Microverse.THREE.MeshStandardMaterial({color: color || 0xffff00});
this.gizmo3d = new Microverse.THREE.Mesh(geometry, material);
this.shape.add(this.gizmo3d);
this.constructOutline(10001);
}
modelLoaded(){
console.log("modelLoaded")
this.gizmo3d = this.shape.children[0];
this.constructOutline(10000);
}
constructOutline(renderOrder){
let outlineMat = new Microverse.THREE.MeshStandardMaterial({
color: 0x444444,
opacity: 0.25,
transparent: true,
depthTest: false,
depthWrite: false,
// side: THREE.BackSide,
});
/*
// leaving this here because it would be nice to make it work
outlineMat.onBeforeCompile = (shader) => {
const token = '#include <begin_vertex>';
const customTransform = 'vec3 transformed = position + objectNormal*0.02;';
shader.vertexShader =
shader.vertexShader.replace(token,customTransform)
}
*/
this.outline3d = this.gizmo3d.clone(true);
this.outline3d.traverse((m) => {
if (m.material) {
m.material = outlineMat;
if (!Array.isArray(m.material)) {
console.log("single material")
m.material = outlineMat;
} else {
console.log("multiple material", m.material.length);
let mArray = m.material.map((_m) => outlineMat);
m.material = mArray;
}
}
});
this.outline3d.renderOrder = renderOrder;
console.log(this.outline3d);
this.shape.add(this.outline3d);
}
makeRay(r) {
let origin = new Microverse.THREE.Vector3(...r.origin);
let direction = new Microverse.THREE.Vector3(...r.direction);
return new Microverse.THREE.Ray(origin, direction);
}
startDrag(pEvt) {
// initiate drag for horizontal drag and for rotation around Y
let THREE = Microverse.THREE;
let avatar = Microverse.GetPawn(pEvt.avatarId);
avatar.addFirstResponder("pointerMove", {}, this);
this.baseVector.set(...pEvt.xyz);
this.plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), -this.baseVector.y);
this.targetStartVector = new THREE.Vector3(...Microverse.m4_getTranslation(this.actor.parent.target.global));
this.startRotation = this.newRotation;
this.publish(this.parent.id, "interaction");
}
startVDrag(pEvt) {
// initiate drag for vertical using avatar's look direction to define plane
let THREE = Microverse.THREE;
let avatar = Microverse.GetPawn(pEvt.avatarId);
avatar.addFirstResponder("pointerMove", {}, this);
this.baseVector.set(...pEvt.xyz);
let lookNorm = avatar.actor.lookNormal;
this.vec.copy(this.baseVector);
this.vec.normalize();
this.vec2.set(...lookNorm);
let cos = this.vec2.dot(this.vec);
this.plane = new THREE.Plane( this.vec2, -cos * this.baseVector.length());
this.targetStartVector = new THREE.Vector3(...Microverse.m4_getTranslation(this.actor.parent.target.global));
this.publish(this.parent.id, "interaction");
}
drag(pEvt) {
let ray = this.makeRay(pEvt.ray);
let intersectionPoint = ray.intersectPlane( this.plane, this.toVector );
if(!intersectionPoint)return; // not touching the plane...
this.vec.copy( this.toVector );
this.vec.sub( this.baseVector );
this.vec.add(this.targetStartVector);
this.publish(this.parent.actor.id, "translateTarget" + this.actor.id, this.vec.toArray());
this.publish(this.parent.id, "interaction");
}
endDrag(pEvt){
const avatar = Microverse.GetPawn(pEvt.avatarId);
avatar.removeFirstResponder("pointerMove", {}, this);
this.publish(this.parent.id, "interaction");
}
hSpin(pEvt) {
this.vec.set(0, 1, 0);
// make a horizontal plane through where we think the widget is (either
// the point reported in the pointerDown pEvt, or the last point of
// contact on pointerMove)
//this.plane = new THREE.Plane(this.vec, -this.dragVector.y);
// toVector gets the point where the laser intercepts that plane
let ray = this.makeRay(pEvt.ray);
let intersectPoint = ray.intersectPlane( this.plane, this.toVector );
// vec is now given the world position of the centre of the rotator
//this.getWorldPosition(this.vec);
if(!intersectPoint)return;
// angle is an angular delta, which will be used as a constant angular
// step until it is reset by another onPointerMove, or until cancelled
// by a click.
this.angle = this.computeAngle(this.targetStartVector, this.baseVector, this.toVector);
// then dragVector takes the most recent value of toVector
// this.dragVector.copy(this.toVector);
// ESLint complained about isNaN. See https://stackoverflow.com/questions/46677774/eslint-unexpected-use-of-isnan
if (Number.isNaN(this.angle)) { console.log(this.vec, this.dragVector, this.toVector); this.angle = 0; return false;}
let axisAngle = Microverse.q_axisAngle([0,1,0], this.angle);
//let targetAngle = Microverse.q_euler(0,this.targetStartAngle+this.angle,0);
const nextRotation = Microverse.q_multiply( this.startRotation, axisAngle);
this.publish(this.parent.actor.id, "spinTarget" + this.actor.id, nextRotation);
this.publish(this.parent.id, "interaction");
this.newRotation = nextRotation; // prep for next startDrag
return true;
}
computeAngle(c, v1, v2) {
let THREE = Microverse.THREE;
// c = vec (position of centre); v1 = dragVector (previous used point in this drag, or pointer-down location); v2 = toVector (current hit position)
c = new THREE.Vector3().copy(c); // take copies that can be mutated
v1 = new THREE.Vector3().copy(v1);
v2 = new THREE.Vector3().copy(v2);
let test = new THREE.Vector3().copy(v1);
test.sub(v2);
if(test.length() < 0.0000000001) return 0;
c.y = v1.y = v2.y = 0; // don't want the y
v1.sub(c); // vector from center to previous hit point
if (v1.length() === 0) return 0; // don't die if we are in the center
v1.normalize();
v2.sub(c); // vector from center to new hit point
if (v2.length() === 0) return 0;
v2.normalize();
let angle = Math.acos(v1.dot(v2)); // how far the hit point has rotated
let sign = Math.sign(v1.cross(v2).y); // whether a clockwise (+ve y) or anticlockwise rotation
return angle * sign;
}
pointerEnter() {
// console.log("pointerEnter")
let hilite = this.actor._cardData.hiliteColor || 0xffaaa;
this.doHilite(hilite); // hilite in yellow
}
pointerLeave() {
// console.log("pointerLeave")
this.doHilite(null);
}
doHilite(hval) {
this.shape.traverse((m) => {
if (m.material) {
if (!Array.isArray(m.material)) {
this.setColor(m.material, hval);
} else {
m.material.forEach((mm) => this.setColor(mm, hval));
}
}
});
}
setColor(material, color){
if(color){
if(material.saveColor)return;
let c = new Microverse.THREE.Color(color);
material.saveColor = material.color;
material.color = c;
}else{
material.color = material.saveColor;
delete material.saveColor;
}
}
}
export default {
modules: [
{
name: "Gizmo",
actorBehaviors: [GizmoActor],
pawnBehaviors: [GizmoPawn],
},
{
name: "GizmoPropertySheetButton",
pawnBehaviors: [GizmoPropertySheetButtonPawn],
},
{
name: "PoseGizmo",
actorBehaviors: [PoseGizmoActor],
pawnBehaviors: [PoseGizmoPawn],
}
]
}
/* globals Microverse */