@croquet/worldcore-kernel
Advanced tools
Comparing version 1.2.0 to 1.3.0
@@ -7,2 +7,23 @@ # Changelog | ||
## [1.3.0] - 2022-6-16 | ||
### Changed | ||
- Parent/child relationships are included in the base functionality of Actors and Pawns | ||
- Actors aren't required to have Pawns | ||
- PM_Dynamic rolled into base Pawn class. | ||
- Pawns have a throttle parameter in their say() method by default. | ||
- The event published by Actor set() is "propertySet" instead of "_property". | ||
- q_equals() defaults to epsilon = 0; | ||
## Fixed | ||
- InputManager generates pointerDelta events during a drag. | ||
### Added | ||
- Pawn set() -- you can set actor options from the Pawn. | ||
- PlayerManager destroyPlayer() -- override to customize player destruction | ||
- PM_Driver mixin | ||
### Removed | ||
- PM_MouselookAvatar mixin | ||
## [1.2.0] - 2022-4-20 | ||
@@ -69,1 +90,4 @@ ### Added | ||
## Pending | ||
{ | ||
"name": "@croquet/worldcore-kernel", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "3D World Engine for Croquet (minimal install)", | ||
@@ -31,3 +31,3 @@ "keywords": [ | ||
"dependencies": { | ||
"@croquet/croquet": "^1.1.0-9" | ||
"@croquet/croquet": "^1.1.0-12" | ||
}, | ||
@@ -37,3 +37,3 @@ "publishConfig": { | ||
}, | ||
"gitHead": "7633b5da452e79cee2be15721d744e4cd9f9924c" | ||
"gitHead": "d5e33664baaaa1a3a153e0aa3721ad3a17c25dc3" | ||
} |
@@ -38,7 +38,10 @@ import { Pawn } from "./Pawn"; | ||
export class Actor extends WorldcoreModel { | ||
get pawn() {return Pawn} | ||
get pawn() {return null;} | ||
get doomed() {return this._doomed} // About to be destroyed. This is used to prevent creating new future messages. | ||
get parent() { return this._parent; } | ||
init(options) { | ||
super.init(); | ||
this.listen("parentSet", this.onParent); | ||
this.listen("_set", this.set); | ||
@@ -51,2 +54,4 @@ this.set(options); | ||
destroy() { | ||
new Set(this.children).forEach(child => child.destroy()); | ||
this.set({parent: null}); | ||
this._doomed = true; // About to be destroyed. This is used to prevent creating new future messages. | ||
@@ -58,20 +63,7 @@ this.say("destroyActor"); | ||
// Different implementations of javascript may store object properties in different orders, so we sort them | ||
// so they are always processed alphabetically | ||
// set(options = {}, prefix = '') { | ||
// const sorted = Object.entries(options).sort((a,b) => { return b[0] < a[0] ? 1 : -1 } ); | ||
// for (const option of sorted) { | ||
// const n = "_" + option[0]; | ||
// const v = option[1]; | ||
// const o = this[n]; | ||
// this[n] = v; | ||
// this.say(prefix+n, {v, o}); // Publish a local message whenever a property changes with its old and new value. | ||
// } | ||
// } | ||
set(options = {}) { | ||
const sorted = Object.entries(options).sort((a,b) => { return b[0] < a[0] ? 1 : -1 } ); | ||
for (const option of sorted) { | ||
const name = option[0]; | ||
const setEvent = name + "Set"; | ||
const n = "_" + option[0]; | ||
@@ -81,6 +73,20 @@ const v = option[1]; | ||
this[n] = v; | ||
this.say(n, {v, o}); // Publish a local message whenever a property changes with its old and new value. | ||
this.say(setEvent, {v, o}); // Publish a local message whenever a property changes with its old and new value. | ||
} | ||
} | ||
addChild(child) { | ||
if (!this.children) this.children = new Set(); | ||
this.children.add(child); | ||
} | ||
removeChild(child) { | ||
if (this.children) this.children.delete(child); | ||
} | ||
onParent(d) { | ||
if (d.o) d.o.removeChild(this); | ||
if (d.v) d.v.addChild(this); | ||
} | ||
say(event, data) { | ||
@@ -87,0 +93,0 @@ this.publish(this.id, event, data); |
@@ -58,2 +58,3 @@ import { v2_sub, v2_add, v2_scale, v2_magnitude, TAU, toRad, toDeg } from "./Vector"; | ||
this.synthesizedModifierKeys = new Map(); | ||
} | ||
@@ -215,2 +216,21 @@ | ||
modifierKeysFrom(event) { | ||
let {altKey, ctrlKey, metaKey, shiftKey} = event; | ||
altKey = !!(altKey || this.synthesizedModifierKeys.get("altKey")); | ||
ctrlKey = !!(ctrlKey || this.synthesizedModifierKeys.get("ctrlKey")); | ||
metaKey = !!(metaKey || this.synthesizedModifierKeys.get("metaKey")); | ||
shiftKey = !!(shiftKey || this.synthesizedModifierKeys.get("shiftKey")); | ||
return {altKey, ctrlKey, metaKey, shiftKey}; | ||
} | ||
setModifierKeys(obj) { | ||
for (let k in obj) { | ||
this.synthesizedModifierKeys.set(k, !!obj[k]) | ||
} | ||
} | ||
modifierEqual(e1, e2) { | ||
return !!e1.altKey === !!e2.altKey && !!e1.ctrlKey === !!e2.ctrlKey && !!e1.metaKey === !!e2.metaKey && !!e1.shiftKey === !!e2.shiftKey; | ||
} | ||
enterPointerLock() { | ||
@@ -255,10 +275,11 @@ if (this.inPointerLock) return; | ||
const key = event.key; | ||
let modKeys = this.modifierKeysFrom(event); | ||
keys.add(key); | ||
if (event.repeat) { | ||
this.publish("input", key + "Repeat", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", "keyRepeat", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", key + "Repeat", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, ...modKeys}); | ||
this.publish("input", "keyRepeat", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, ...modKeys}); | ||
// This can generate a lot of events! Don't subscribe to in model. | ||
} else { | ||
this.publish("input", key + "Down", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", "keyDown", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", key + "Down", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, ...modKeys}); | ||
this.publish("input", "keyDown", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, ...modKeys}); | ||
this.onChordDown(key); | ||
@@ -271,5 +292,6 @@ } | ||
const key = event.key; | ||
let modKeys = this.modifierKeysFrom(event); | ||
if (!KeyDown(key)) return; | ||
this.publish("input", key + "Up", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", "keyUp", {key, shift: event.shiftKey, alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey}); | ||
this.publish("input", key + "Up", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, ...modKeys}); | ||
this.publish("input", "keyUp", {key, shift: modKeys.shiftKey, alt: modKeys.altKey, ctrl: modKeys.ctrlKey, meta: modKeys.metaKey, modKeys}); | ||
keys.delete(key); | ||
@@ -280,18 +302,20 @@ this.onChordUp(key); | ||
onClick(event) { | ||
let modKeys = this.modifierKeysFrom(event); | ||
window.focus(); | ||
this.publish("input", "click", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "click", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
} | ||
onPointerDown(event) { | ||
this.presses.set(event.pointerId, {id: event.pointerId, time: event.timeStamp, start: [event.clientX, event.clientY], xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerDown", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
if (event.button === this.lastDown.button && event.timeStamp - this.lastDown.time < DOUBLE_DURATION) { | ||
if (event.button === this.penultimateDown.button && event.timeStamp - this.penultimateDown.time < TRIPLE_DURATION) { | ||
this.publish("input", "tripleDown", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
let modKeys = this.modifierKeysFrom(event); | ||
this.presses.set(event.pointerId, {id: event.pointerId, time: event.timeStamp, start: [event.clientX, event.clientY], ...modKeys, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerDown", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
if (event.button === this.lastDown.button && event.timeStamp - this.lastDown.time < DOUBLE_DURATION && this.modifierEqual(event, this.lastDown)) { | ||
if (event.button === this.penultimateDown.button && event.timeStamp - this.penultimateDown.time < TRIPLE_DURATION) { | ||
this.publish("input", "tripleDown", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
} else { | ||
this.publish("input", "doubleDown", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "doubleDown", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
} | ||
} | ||
this.penultimateDown = this.lastDown; | ||
this.lastDown = {id: event.pointerId, button: event.button, time: event.timeStamp} | ||
this.lastDown = {id: event.pointerId, button: event.button, buttons: event.buttons, ...modKeys, time: event.timeStamp}; | ||
this.zoomStart(); | ||
@@ -302,2 +326,3 @@ } | ||
const press = this.presses.get(event.pointerId); | ||
let modKeys = this.modifierKeysFrom(event); | ||
if (press) { | ||
@@ -311,9 +336,9 @@ press.xy = [event.clientX, event.clientY]; | ||
if (duration < TAP_DURATION && ax < TAP_DISTANCE && ay < TAP_DISTANCE) { | ||
this.publish("input", "tap", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "tap", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
} | ||
if (duration < SWIPE_DURATION && ax > SWIPE_DISTANCE) { | ||
this.publish("input", "swipeX", {id: event.pointerId, type: event.pointerType, button: event.button, distance: dx}); | ||
this.publish("input", "swipeX", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, distance: dx, ...modKeys}); | ||
} | ||
if (duration < SWIPE_DURATION && ay > SWIPE_DISTANCE) { | ||
this.publish("input", "swipeY", {id: event.pointerId, type: event.pointerType, button: event.button, distance: dy}); | ||
this.publish("input", "swipeY", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, distance: dy, ...modKeys}); | ||
} | ||
@@ -323,3 +348,3 @@ } | ||
this.presses.delete(event.pointerId); | ||
this.publish("input", "pointerUp", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerUp", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
this.zoomEnd(); | ||
@@ -330,2 +355,3 @@ } | ||
const press = this.presses.get(event.pointerId); | ||
let modKeys = this.modifierKeysFrom(event); | ||
if (press) { | ||
@@ -339,7 +365,8 @@ press.xy = [event.clientX, event.clientY]; | ||
if (duration > TAP_DURATION || ax > TAP_DISTANCE || ay > TAP_DISTANCE) { // Only publish pressed move events that aren't taps | ||
this.publish("input", "pointerMove", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerMove", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerDelta", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.movementX, event.movementY]}); | ||
} | ||
} else { | ||
this.publish("input", "pointerMove", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerDelta", {id: event.pointerId, type: event.pointerType, button: event.button, xy: [event.movementX, event.movementY]}); | ||
this.publish("input", "pointerMove", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "pointerDelta", {id: event.pointerId, type: event.pointerType, button: event.button, buttons: event.buttons, ...modKeys, xy: [event.movementX, event.movementY]}); | ||
} | ||
@@ -390,5 +417,6 @@ this.zoomUpdate(); | ||
onWheel(event) { | ||
let modKeys = this.modifierKeysFrom(event); | ||
event.preventDefault(); | ||
const y = event.deltaY; | ||
this.publish("input", "wheel", {deltaY: y, xy: [event.clientX, event.clientY]}); | ||
this.publish("input", "wheel", {deltaY: y, ...modKeys, xy: [event.clientX, event.clientY]}); | ||
} | ||
@@ -395,0 +423,0 @@ |
import { Constants } from "@croquet/croquet"; | ||
import { m4_identity } from ".."; | ||
// import { Constants } from "@croquet/worldcore-kernel"; | ||
import { PM_Dynamic, GetPawn } from "./Pawn"; | ||
// import { GetPawn } from "./Pawn"; | ||
import { v3_zero, q_identity, v3_unit, m4_scaleRotationTranslation, m4_translation, m4_rotationX, m4_multiply, v3_lerp, v3_equals, | ||
@@ -103,96 +103,6 @@ q_slerp, q_equals, v3_isZero, q_isZero, q_normalize, q_multiply, v3_add, v3_scale, m4_rotationQ, v3_transform, q_euler, TAU, clampRad, q_axisAngle } from "./Vector"; | ||
//------------------------------------------------------------------------------------------ | ||
//-- Tree ---------------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Tree actors can be put in a hierarchy with parents and children. The tree pawns maintain | ||
// their own hierarchy that mirrors the actor hierarchy. | ||
//-- Actor --------------------------------------------------------------------------------- | ||
export const AM_Tree = superclass => class extends superclass { | ||
init(...args) { | ||
this.listen("_parent", this.onChangeParent); | ||
super.init(...args); | ||
} | ||
destroy() { | ||
new Set(this.children).forEach(child => child.destroy()); | ||
this.set({parent: null}); | ||
super.destroy(); | ||
} | ||
onChangeParent(d) { | ||
if (d.o) d.o.removeChild(this); | ||
if (d.v) d.v.addChild(this); | ||
} | ||
get parent() { return this._parent; } | ||
addChild(c) { // This should never be called directly, use set instead | ||
if (!this.children) this.children = new Set(); | ||
this.children.add(c); | ||
} | ||
removeChild(c) { // This should never be called directly, use set instead | ||
if (this.children) this.children.delete(c); | ||
} | ||
}; | ||
RegisterMixin(AM_Tree); | ||
//-- Pawn ---------------------------------------------------------------------------------- | ||
export const PM_Tree = superclass => class extends superclass { | ||
constructor(...args) { | ||
super(...args); | ||
if (this.actor.parent) { | ||
const parent = GetPawn(this.actor.parent.id); | ||
parent.addChild(this.actor.id); | ||
} | ||
this.listen("_parent", this.onChangeParent); | ||
} | ||
get parent() { | ||
if (this.actor.parent && !this._parent) this._parent = GetPawn(this.actor.parent.id); | ||
return this._parent; | ||
} | ||
get children() { | ||
if (this.actor.children && !this._children) this.actor.children.forEach(child => { this.addChild(child.id); }) | ||
return this._children; | ||
} | ||
onChangeParent(d) { | ||
if (d.o) { | ||
GetPawn(d.o.id).removeChild(this.actor.id); | ||
} | ||
if (d.v) { | ||
GetPawn(d.v.id).addChild(this.actor.id); | ||
} | ||
} | ||
addChild(id) { | ||
const child = GetPawn(id); | ||
if (!child) return; | ||
if (!this._children) this._children = new Set(); | ||
this._children.add(child); | ||
child._parent = this; | ||
} | ||
removeChild(id) { | ||
const child = GetPawn(id); | ||
if (!child) return; | ||
if (this._children) this._children.delete(child); | ||
child._parent = null; | ||
} | ||
}; | ||
//------------------------------------------------------------------------------------------ | ||
//-- Spatial ------------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Spatial actors have a translation, rotation and scale in 3D space. They inherit from Tree | ||
// so they also maintain a hierarchy of transforms. | ||
// Spatial actors have a translation, rotation and scale in 3D space. | ||
// | ||
@@ -204,9 +114,9 @@ // They don't have any view-side smoothing, so the pawn will change its transform to exactly | ||
export const AM_Spatial = superclass => class extends AM_Tree(superclass) { | ||
export const AM_Spatial = superclass => class extends superclass { | ||
init(options) { | ||
super.init(options); | ||
this.listen("_scale", this.localChanged); | ||
this.listen("_rotation", this.localChanged); | ||
this.listen("_translation", this.localChanged); | ||
this.listen("scaleSet", this.localChanged); | ||
this.listen("rotationSet", this.localChanged); | ||
this.listen("translationSet", this.localChanged); | ||
} | ||
@@ -255,3 +165,3 @@ | ||
export const PM_Spatial = superclass => class extends PM_Tree(superclass) { | ||
export const PM_Spatial = superclass => class extends superclass { | ||
@@ -288,2 +198,10 @@ constructor(...args) { | ||
init(...args) { | ||
super.init(...args); | ||
this.listen("_scaleTo", this.scaleTo); | ||
this.listen("_rotateTo", this.rotateTo); | ||
this.listen("_translateTo", this.translateTo); | ||
this.listen("_positionTo", this.positionTo); | ||
} | ||
scaleTo(v) { | ||
@@ -293,3 +211,3 @@ this._scale = v; | ||
this.$global = null; | ||
this.say("scaling"); | ||
this.say("scaleTo", v); | ||
} | ||
@@ -301,3 +219,3 @@ | ||
this.$global = null; | ||
this.say("rotating"); | ||
this.say("rotateTo", q); | ||
} | ||
@@ -309,5 +227,15 @@ | ||
this.$global = null; | ||
this.say("translating"); | ||
this.say("translateTo", v); | ||
} | ||
positionTo(data) { | ||
this._translation = data.v; | ||
this._rotation = data.q; | ||
this.$local = null; | ||
this.$global = null; | ||
this.say("rotateTo", data.q); | ||
this.say("translateTo", data.v); | ||
} | ||
moveTo(v) { this.translateTo(v)} | ||
@@ -327,6 +255,4 @@ | ||
const DynamicSpatial = superclass => PM_Dynamic(PM_Spatial(superclass)); // Merge dynamic and spatial base mixins | ||
export const PM_Smoothed = superclass => class extends PM_Spatial(superclass) { | ||
export const PM_Smoothed = superclass => class extends DynamicSpatial(superclass) { | ||
constructor(...args) { | ||
@@ -341,18 +267,18 @@ super(...args); | ||
this.listenOnce("_scale", this.onScaleSet); | ||
this.listenOnce("scaling", () => this.scaling = true) | ||
this.listenOnce("scaleSet", this.onScaleSet); | ||
this.listenOnce("rotationSet", this.onRotationSet); | ||
this.listenOnce("translationSet", this.onTranslationSet); | ||
this.listenOnce("_rotation", this.onRotationSet); | ||
this.listenOnce("rotating", () => this.rotating = true) | ||
this.listenOnce("_translation", this.onTranslationSet); | ||
this.listenOnce("translating", () => this.translating = true) | ||
this.listenOnce("scaleTo", this.onScaleTo); | ||
this.listenOnce("rotateTo", this.onRotateTo); | ||
this.listenOnce("translateTo", this.onTranslateTo); | ||
} | ||
set tug(t) {this._tug = t} | ||
get tug() { return this._tug; } | ||
get tug() { return this._tug; } | ||
set localOffset(m4) { | ||
this._localOffset = m4; | ||
this.onLocalChanged(); | ||
this._local = null; | ||
this._global = null; | ||
} | ||
@@ -365,2 +291,27 @@ get localOffset() { return this._localOffset; } | ||
onLocalChanged(){ | ||
this._local = null; | ||
this.onGlobalChanged(); | ||
} | ||
onGlobalChanged(){ | ||
this._global = null; | ||
} | ||
scaleTo(v, throttle) { | ||
this.say("_scaleTo", v, throttle) | ||
} | ||
rotateTo(q, throttle) { | ||
this.say("_rotateTo", q, throttle) | ||
} | ||
translateTo(v, throttle) { | ||
this.say("_translateTo", v, throttle) | ||
} | ||
positionTo(v, q, throttle) { | ||
this.say("_positionTo", {v,q}, throttle) | ||
} | ||
onScaleSet() { | ||
@@ -381,11 +332,6 @@ this._scale = this.actor.scale; | ||
onLocalChanged() { | ||
this._local = null; | ||
this._global = null; | ||
} | ||
onScaleTo(q) { this.isScaling = true; } | ||
onRotateTo(q) { this.isRotating = true; } | ||
onTranslateTo(v) { this.isTranslating = true; } | ||
onGlobalChanged() { | ||
this._global = null; | ||
} | ||
get local() { | ||
@@ -408,3 +354,2 @@ if (this._local) return this. _local; | ||
} | ||
this.say("viewGlobalChanged"); | ||
return this._global; | ||
@@ -419,33 +364,48 @@ } | ||
if (!this.scaling || v3_equals(this._scale, this.actor.scale, .0001)) { | ||
this.scaling = false; | ||
} else { | ||
this._scale = v3_lerp(this._scale, this.actor.scale, tug); | ||
if (this.isScaling) { | ||
if (v3_equals(this._scale, this.actor.scale, .0001)) { | ||
this._scale = this.actor.scale; | ||
this.isScaling = false; | ||
} else { | ||
this._scale = v3_lerp(this._scale, this.actor.scale, tug); | ||
} | ||
this.onLocalChanged(); | ||
} | ||
if (!this.rotating || q_equals(this._rotation, this.actor.rotation, 0.000001)) { | ||
this.rotating = false; | ||
} else { | ||
this._rotation = q_slerp(this._rotation, this.actor.rotation, tug); | ||
if (this.isRotating) { | ||
if (q_equals(this._rotation, this.actor.rotation, 0.000001)) { | ||
this._rotation = this.actor.rotation; | ||
this.isRotating = false; | ||
} else { | ||
this._rotation = q_slerp(this._rotation, this.actor.rotation, tug); | ||
} | ||
this.onLocalChanged(); | ||
} | ||
if (!this.translating || v3_equals(this._translation, this.actor.translation, .0001)) { | ||
this.translating = false; | ||
} else { | ||
this._translation = v3_lerp(this._translation, this.actor.translation, tug); | ||
if (this.isRotating) { | ||
if (q_equals(this._rotation, this.actor.rotation, 0.000001)) { | ||
this._rotation = this.actor.rotation; | ||
this.isRotating = false; | ||
} else { | ||
this._rotation = q_slerp(this._rotation, this.actor.rotation, tug); | ||
} | ||
this.onLocalChanged(); | ||
} | ||
if (this.isTranslating) { | ||
if (v3_equals(this._translation, this.actor.translation, .0001)) { | ||
this._translation = this.actor.translation; | ||
this.isTranslating = false; | ||
} else { | ||
this._translation = v3_lerp(this._translation, this.actor.translation, tug); | ||
} | ||
this.onLocalChanged(); | ||
} | ||
if (!this._global) { | ||
this.global; | ||
if (this.children) this.children.forEach(child => child._global = null); // If our global changes, so do the globals of our children | ||
this.say("viewGlobalChanged"); | ||
if (this.children) this.children.forEach(child => child.onGlobalChanged()); // If our global changes, so do the globals of our children | ||
} | ||
} | ||
postUpdate(time, delta) { | ||
super.postUpdate(time, delta); | ||
if (this.children) this.children.forEach(child => child.fullUpdate(time, delta)); | ||
} | ||
@@ -456,2 +416,106 @@ | ||
//------------------------------------------------------------------------------------------ | ||
//-- PM_SmoothedDriver --------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// This version of PM_Smoothed sets the transform values instantly on the local pawn and only implements smoothing on other clients. | ||
// export const PM_SmoothedDriver = superclass => class extends PM_Smoothed(superclass) { | ||
// constructor(...args) { | ||
// super(...args); | ||
// this.throttle = 100; //ms | ||
// this.ignore("scaleSet"); | ||
// this.ignore("rotationSet"); | ||
// this.ignore("translationSet"); | ||
// this.ignore("positionSet"); | ||
// } | ||
// positionTo(v, q, throttle) { | ||
// throttle = throttle || this.throttle; | ||
// this._translation = v; | ||
// this._rotation = q; | ||
// this.isTranslating = false; | ||
// this.isRotating = false; | ||
// this.onLocalChanged(); | ||
// super.positionTo(v, q, throttle); | ||
// } | ||
// scaleTo(v, throttle) { | ||
// throttle = throttle || this.throttle; | ||
// this._scale = v; | ||
// this.isScaling = false; | ||
// this.onLocalChanged(); | ||
// super.scaleTo(v, throttle); | ||
// } | ||
// rotateTo(q, throttle) { | ||
// throttle = throttle || this.throttle; | ||
// this._rotation = q; | ||
// this.isRotating = false; | ||
// this.onLocalChanged(); | ||
// super.rotateTo(q, throttle); | ||
// } | ||
// translateTo(v, throttle) { | ||
// throttle = throttle || this.throttle; | ||
// this._translation = v; | ||
// this.isTranslating = false; | ||
// this.onLocalChanged(); | ||
// super.translateTo(v, throttle); | ||
// } | ||
// } | ||
//------------------------------------------------------------------------------------------ | ||
//-- PM_Driver ----------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// You can add this to a PM_Smoothed pawn to make it more responsive to direct user input. | ||
// Transform values are set instantly on the local pawn and smoothing only happens on remote instances. | ||
export const PM_Driver = superclass => class extends superclass { | ||
constructor(...args) { | ||
super(...args); | ||
this.throttle = 100; //ms | ||
} | ||
positionTo(v, q, throttle) { | ||
throttle = throttle || this.throttle; | ||
this._translation = v; | ||
this._rotation = q; | ||
this.isTranslating = false; | ||
this.isRotating = false; | ||
this.onLocalChanged(); | ||
super.positionTo(v, q, throttle); | ||
} | ||
scaleTo(v, throttle) { | ||
throttle = throttle || this.throttle; | ||
this._scale = v; | ||
this.isScaling = false; | ||
this.onLocalChanged(); | ||
super.scaleTo(v, throttle); | ||
} | ||
rotateTo(q, throttle) { | ||
throttle = throttle || this.throttle; | ||
this._rotation = q; | ||
this.isRotating = false; | ||
this.onLocalChanged(); | ||
super.rotateTo(q, throttle); | ||
} | ||
translateTo(v, throttle) { | ||
throttle = throttle || this.throttle; | ||
this._translation = v; | ||
this.isTranslating = false; | ||
this.onLocalChanged(); | ||
super.translateTo(v, throttle); | ||
} | ||
} | ||
//------------------------------------------------------------------------------------------ | ||
//-- Predictive ---------------------------------------------------------------------------- | ||
@@ -480,9 +544,2 @@ //------------------------------------------------------------------------------------------ | ||
super.init(...args); | ||
this.listen("scaleTo", this.scaleTo); | ||
this.listen("rotateTo", this.rotateTo); | ||
this.listen("translateTo", this.translateTo); | ||
this.listen("setVelocity", this.setVelocity); | ||
this.listen("setSpin", this.setSpin); | ||
this.listen("setVelocitySpin", this.setVelocitySpin); | ||
this.future(0).tick(0); | ||
@@ -503,11 +560,2 @@ } | ||
setVelocity(v) { this._velocity = v; } | ||
setSpin(q) { this._spin = q; } | ||
setVelocitySpin(vq){ | ||
this._velocity = vq[0]; | ||
this._spin = vq[1]; | ||
} | ||
}; | ||
@@ -526,35 +574,12 @@ RegisterMixin(AM_Predictive); | ||
scaleTo(v, throttle) { | ||
this.say("scaleTo", v, throttle) | ||
this.onLocalChanged(); | ||
} | ||
rotateTo(q, throttle) { | ||
this.say("rotateTo", q, throttle) | ||
this.onLocalChanged(); | ||
} | ||
translateTo(v, throttle) { | ||
this.say("translateTo", v, throttle) | ||
this.onLocalChanged(); | ||
} | ||
moveTo(v, throttle) {this.translateTo(v,throttle); } | ||
setVelocity(v, throttle) { | ||
this.say("setVelocity", v, throttle) | ||
this.onLocalChanged(); | ||
this.set({velocity: v}, throttle) | ||
} | ||
setSpin(q, throttle) { | ||
this.say("setSpin", q, throttle) | ||
this.onLocalChanged(); | ||
this.set({spin: q}, throttle) | ||
} | ||
setVelocitySpin(vq, throttle){ | ||
this.say("setVelocitySpin", vq, throttle); | ||
this.onLocalChanged(); | ||
} | ||
update(time, delta) { | ||
@@ -564,4 +589,3 @@ | ||
this._rotation = q_normalize(q_slerp(this._rotation, q_multiply(this._rotation, this.spin), delta)); | ||
this._local = null; | ||
this._global = null; | ||
this.onLocalChanged(); | ||
} | ||
@@ -573,4 +597,3 @@ | ||
this._translation = v3_add(this._translation, move); | ||
this._local = null; | ||
this._global = null; | ||
this.onLocalChanged(); | ||
} | ||
@@ -597,93 +620,93 @@ super.update(time, delta); | ||
//-- Actor --------------------------------------------------------------------------------- | ||
// //-- Actor --------------------------------------------------------------------------------- | ||
export const AM_MouselookAvatar = superclass => class extends AM_Avatar(superclass) { | ||
// export const AM_MouselookAvatar = superclass => class extends AM_Avatar(superclass) { | ||
init(...args) { | ||
this.listen("avatarLookTo", this.onLookTo); | ||
super.init(...args); | ||
this.set({rotation: q_euler(0, this.lookYaw, 0)}); | ||
} | ||
// init(...args) { | ||
// this.listen("avatarLookTo", this.onLookTo); | ||
// super.init(...args); | ||
// this.set({rotation: q_euler(0, this.lookYaw, 0)}); | ||
// } | ||
get lookPitch() { return this._lookPitch || 0 }; | ||
get lookYaw() { return this._lookYaw || 0 }; | ||
// get lookPitch() { return this._lookPitch || 0 }; | ||
// get lookYaw() { return this._lookYaw || 0 }; | ||
onLookTo(e) { | ||
this.set({lookPitch: e[0], lookYaw: e[1]}); | ||
this.rotateTo(q_euler(0, this.lookYaw, 0)); | ||
} | ||
// onLookTo(e) { | ||
// this.set({lookPitch: e[0], lookYaw: e[1]}); | ||
// this.rotateTo(q_euler(0, this.lookYaw, 0)); | ||
// } | ||
} | ||
RegisterMixin(AM_MouselookAvatar); | ||
// } | ||
// RegisterMixin(AM_MouselookAvatar); | ||
//-- Pawn --------------------------------------------------------------------------------- | ||
// //-- Pawn --------------------------------------------------------------------------------- | ||
export const PM_MouselookAvatar = superclass => class extends PM_Avatar(superclass) { | ||
// export const PM_MouselookAvatar = superclass => class extends PM_Avatar(superclass) { | ||
constructor(...args) { | ||
super(...args); | ||
// constructor(...args) { | ||
// super(...args); | ||
this._lookPitch = this.actor.lookPitch; | ||
this._lookYaw = this.actor.lookYaw; | ||
// this._lookPitch = this.actor.lookPitch; | ||
// this._lookYaw = this.actor.lookYaw; | ||
this.lookThrottle = 50; // MS between throttled lookTo events | ||
this.lastlookTime = this.time; | ||
// this.lookThrottle = 50; // MS between throttled lookTo events | ||
// this.lastlookTime = this.time; | ||
this.lookOffset = [0,0,0]; // Vector displacing the camera from the avatar origin. | ||
} | ||
// this.lookOffset = [0,0,0]; // Vector displacing the camera from the avatar origin. | ||
// } | ||
get lookPitch() { return this._lookPitch} | ||
get lookYaw() { return this._lookYaw} | ||
// get lookPitch() { return this._lookPitch} | ||
// get lookYaw() { return this._lookYaw} | ||
lookTo(pitch, yaw) { | ||
this.setLookAngles(pitch, yaw); | ||
this.lastLookTime = this.time; | ||
this.lastLookCache = null; | ||
this.say("avatarLookTo", [pitch, yaw]); | ||
this.say("lookGlobalChanged"); | ||
} | ||
// lookTo(pitch, yaw) { | ||
// this.setLookAngles(pitch, yaw); | ||
// this.lastLookTime = this.time; | ||
// this.lastLookCache = null; | ||
// this.say("avatarLookTo", [pitch, yaw]); | ||
// this.say("lookGlobalChanged"); | ||
// } | ||
throttledLookTo(pitch, yaw) { | ||
pitch = Math.min(Math.PI/2, Math.max(-Math.PI/2, pitch)); | ||
yaw = clampRad(yaw); | ||
if (this.time < this.lastLookTime + this.lookThrottle) { | ||
this.setLookAngles(pitch, yaw); | ||
this.lastLookCache = {pitch, yaw}; | ||
} else { | ||
this.lookTo(pitch,yaw); | ||
} | ||
} | ||
// throttledLookTo(pitch, yaw) { | ||
// pitch = Math.min(Math.PI/2, Math.max(-Math.PI/2, pitch)); | ||
// yaw = clampRad(yaw); | ||
// if (this.time < this.lastLookTime + this.lookThrottle) { | ||
// this.setLookAngles(pitch, yaw); | ||
// this.lastLookCache = {pitch, yaw}; | ||
// } else { | ||
// this.lookTo(pitch,yaw); | ||
// } | ||
// } | ||
setLookAngles(pitch, yaw) { | ||
this._lookPitch = pitch; | ||
this._lookYaw = yaw; | ||
this._rotation = q_euler(0, yaw, 0); | ||
} | ||
// setLookAngles(pitch, yaw) { | ||
// this._lookPitch = pitch; | ||
// this._lookYaw = yaw; | ||
// this._rotation = q_euler(0, yaw, 0); | ||
// } | ||
get lookGlobal() { | ||
const pitchRotation = q_axisAngle([1,0,0], this.lookPitch); | ||
const yawRotation = q_axisAngle([0,1,0], this.lookYaw); | ||
// get lookGlobal() { | ||
// const pitchRotation = q_axisAngle([1,0,0], this.lookPitch); | ||
// const yawRotation = q_axisAngle([0,1,0], this.lookYaw); | ||
const modelLocal = m4_scaleRotationTranslation(this.scale, yawRotation, this.translation) | ||
let modelGlobal = modelLocal; | ||
if (this.parent) modelGlobal = m4_multiply(modelLocal, this.parent.global); | ||
// const modelLocal = m4_scaleRotationTranslation(this.scale, yawRotation, this.translation) | ||
// let modelGlobal = modelLocal; | ||
// if (this.parent) modelGlobal = m4_multiply(modelLocal, this.parent.global); | ||
const m0 = m4_translation(this.lookOffset); | ||
const m1 = m4_rotationQ(pitchRotation); | ||
const m2 = m4_multiply(m1, m0); | ||
return m4_multiply(m2, modelGlobal); | ||
} | ||
// const m0 = m4_translation(this.lookOffset); | ||
// const m1 = m4_rotationQ(pitchRotation); | ||
// const m2 = m4_multiply(m1, m0); | ||
// return m4_multiply(m2, modelGlobal); | ||
// } | ||
update(time, delta) { | ||
super.update(time, delta); | ||
// update(time, delta) { | ||
// super.update(time, delta); | ||
if (this.lastLookCache && this.time > this.lastLookTime + this.lookThrottle) { | ||
this.lookTo(this.lastLookCache.pitch, this.lastLookCache.yaw); | ||
} | ||
// if (this.lastLookCache && this.time > this.lastLookTime + this.lookThrottle) { | ||
// this.lookTo(this.lastLookCache.pitch, this.lastLookCache.yaw); | ||
// } | ||
} | ||
// } | ||
} | ||
// } | ||
155
src/Pawn.js
@@ -15,3 +15,3 @@ /* eslint-disable new-cap */ | ||
this.pawns = new Map(); | ||
this.dynamic = new Set(); | ||
// this.dynamic = new Set(); | ||
@@ -31,41 +31,15 @@ const actorManager = this.modelService("ActorManager"); | ||
spawnPawn(actor) { | ||
new actor.pawn(actor); | ||
} | ||
spawnPawn(actor) { if (actor.pawn) new actor.pawn(actor); } | ||
add(pawn) { this.pawns.set(pawn.actor.id, pawn); } | ||
has(id) { return this.pawns.has(id); } | ||
get(id) { return this.pawns.get(id); } | ||
delete(pawn) { this.pawns.delete(pawn.actor.id); } | ||
add(pawn) { | ||
this.pawns.set(pawn.actor.id, pawn); | ||
} | ||
has(id) { | ||
return this.pawns.has(id); | ||
} | ||
get(id) { | ||
return this.pawns.get(id); | ||
} | ||
delete(pawn) { | ||
this.pawns.delete(pawn.actor.id); | ||
} | ||
addDynamic(pawn) { | ||
this.dynamic.add(pawn); | ||
} | ||
deleteDynamic(pawn) { | ||
this.dynamic.delete(pawn); | ||
} | ||
update(time, delta) { | ||
this.dynamic.forEach( pawn => { | ||
if (pawn.parent) return; // Child pawns get updated in their parent's postUpdate | ||
pawn.fullUpdate(time, delta); | ||
}); | ||
for(const pawn of this.pawns.values()) { if (!pawn.parent) pawn.fullUpdate(time, delta); }; | ||
} | ||
} | ||
export function GetPawn(actorId) { return pm.get(actorId); } | ||
//------------------------------------------------------------------------------------------ | ||
@@ -79,5 +53,8 @@ //-- Pawn ---------------------------------------------------------------------------------- | ||
super(actor); | ||
this._sayNext = {}; | ||
this._sayCache = {}; | ||
this._actor = actor; | ||
pm.add(this); | ||
this.listen("destroyActor", this.destroy); | ||
this.listen("parentSet", this.onParent); | ||
this.init(); | ||
@@ -96,5 +73,40 @@ } | ||
get parent() { | ||
if (this.actor.parent && !this._parent) this._parent = GetPawn(this.actor.parent.id); | ||
return this._parent; | ||
} | ||
get children() { | ||
if (this.actor.children && !this._children) this.actor.children.forEach(child => { this.addChild(child.id); }) | ||
return this._children; | ||
} | ||
addChild(id) { | ||
const child = GetPawn(id); | ||
if (!child) return; | ||
if (!this._children) this._children = new Set(); | ||
this._children.add(child); | ||
child._parent = this; | ||
} | ||
removeChild(id) { | ||
const child = GetPawn(id); | ||
if (!child) return; | ||
if (this._children) this._children.delete(child); | ||
child._parent = null; | ||
} | ||
onParent(d) { | ||
if (d.o) GetPawn(d.o.id).removeChild(this.actor.id); | ||
if (d.v) GetPawn(d.v.id).addChild(this.actor.id); | ||
} | ||
say(event, data, throttle = 0) { | ||
if (throttle) console.warn("Only dynamic pawns can throttle 'say'!"); | ||
this.publish(this.actor.id, event, data); | ||
if (this.time < this._sayNext[event]) { | ||
this._sayCache[event] = data; | ||
} else { | ||
this._sayNext[event] = this.time + throttle; | ||
this._sayCache[event] = null; | ||
this.publish(this.actor.id, event, data); | ||
} | ||
} | ||
@@ -118,57 +130,15 @@ | ||
// Creates a property in the pawn that will access a matching property in the actor. | ||
// When the property is set in the actor, the pawn with set its matching property and call the onSet method | ||
// definePawnProperty(name, onSet) { | ||
// const ul = '_' + name; | ||
// const v = this.actor[name]; | ||
// this[ul] = v; | ||
// Object.defineProperty(this, name, { get: function() {return this[ul]} }); | ||
// if (onSet) onSet(v,null); | ||
// this.listenOnce(ul, () => { | ||
// const o = this[ul]; | ||
// const v = this.actor[name]; | ||
// this[ul] = this.actor[name]; | ||
// if (onSet) onSet(v,o); | ||
// }); | ||
// } | ||
} | ||
//------------------------------------------------------------------------------------------ | ||
//-- PM_Dynamic ---------------------------------------------------------------------------- | ||
//------------------------------------------------------------------------------------------ | ||
// Dynamic pawns get their update called every frame. Most stuff should go in update, but if there are operations | ||
// that need to occur before or after everything else, you can overload pre- and post- update. | ||
export const PM_Dynamic = superclass => class extends superclass { | ||
constructor(...args) { | ||
super(...args); | ||
pm.addDynamic(this); | ||
this._sayNext = {}; | ||
this._sayCache = {}; | ||
set(options, throttle = 0) { | ||
this.say("_set", options, throttle); | ||
} | ||
destroy() { | ||
pm.deleteDynamic(this); | ||
super.destroy(); | ||
} | ||
say(event, data, throttle = 0) { | ||
if (this.time < this._sayNext[event]) { | ||
this._sayCache[event] = data; | ||
} else { | ||
this._sayNext[event] = this.time + throttle; | ||
this._sayCache[event] = null; | ||
this.publish(this.actor.id, event, data); | ||
} | ||
} | ||
preUpdate(time, delta) {} // Called immediately before the main update | ||
update(time, delta) {} | ||
postUpdate(time, delta){ // Called immediately after the main update. | ||
postUpdate(time, delta){} // Called immediately after the main update. | ||
fullUpdate(time, delta) { | ||
this.preUpdate(time, delta); | ||
this.update(time, delta); | ||
this.postUpdate(time, delta); | ||
for (const event in this._sayCache) { // Flushes expired cached events from throttled says | ||
@@ -181,14 +151,9 @@ const data = this._sayCache[event]; | ||
} | ||
} | ||
fullUpdate(time, delta) { | ||
this.preUpdate(time, delta); | ||
this.update(time, delta); | ||
this.postUpdate(time, delta); | ||
if (this.children) this.children.forEach(child => child.fullUpdate(time, delta)); | ||
} | ||
} | ||
export function GetPawn(actorId) { | ||
return pm.get(actorId); | ||
} | ||
@@ -36,7 +36,11 @@ import { Actor } from "./Actor"; | ||
destroyPlayer(player) { | ||
this.publish("playerManager", "destroy", player); | ||
player.destroy(); | ||
} | ||
onExit(viewId) { | ||
const player = this.player(viewId); | ||
if (!player) return; | ||
this.publish("playerManager", "destroy", player); | ||
player.destroy(); | ||
this.destroyPlayer(player); | ||
this.players.delete(viewId); | ||
@@ -43,0 +47,0 @@ } |
@@ -172,3 +172,3 @@ // Vector and matrix math | ||
export function v2_isZero(v) { | ||
return !(v[0] || v[1] ); | ||
return v[0] === 0 && v[1] === 0; | ||
} | ||
@@ -348,3 +348,3 @@ | ||
export function v3_isZero(v) { | ||
return !(v[0] || v[1] || v[2]); | ||
return v[0] === 0 && v[1] === 0 && v[2] === 0; | ||
} | ||
@@ -1166,8 +1166,9 @@ | ||
export function q_equals(a,b,e = 0.0001) { // e is an epsilon | ||
return Math.abs(q_dot(a,b)) + e >= 1; | ||
export function q_equals(a,b,e = 0) { // e is an epsilon | ||
if (e) return Math.abs(q_dot(a,b)) + e >= 1; | ||
return (a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]); | ||
} | ||
export function q_isZero(q) { | ||
return !(q[0] || q[1] || q[2]); | ||
return q[0] === 0 && q[1] === 0 && q[2] === 0; | ||
} | ||
@@ -1174,0 +1175,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
110092
2688
Updated@croquet/croquet@^1.1.0-12