iwmlib/lib/pixi/maps/utils.js

514 lines
15 KiB
JavaScript
Raw Permalink Normal View History

import { Points } from '../../utils.js'
export class ObjectUtils {
/**
* Finds the previous key in an object.
*
* @param {string} targetKey - The target key, of whom the predecessor is wanted.
*/
static findPredecessor(targetKey, obj) {
// Initialize pointers ...
let prev = null
let first = null
// ... and a hit flag.
let hit = false
//Iterate over all available items.
for (let key in obj)
if (obj.hasOwnProperty(key)) {
if (!hit) {
//Assign both values,
//while not hit
prev = first
first = key
//When hit, set flag.
if (key == targetKey) {
hit = true
if (prev) return prev //If it was not hit on the first item, we can return prev.
}
} else {
//Otherwise, when first item was hit, we iterate over the entire object.
first = key
}
}
return first
}
/**
* Finds the successing key in an object.
*
* @param {string} targetKey - The key of whom the successor key should be found.
* @param {object} obj - The object, that is scanned for the successor.
*/
static findSuccessor(targetKey, obj) {
let first = null
let next = false
for (let key in obj)
if (obj.hasOwnProperty(key)) {
if (!first) first = key
if (next) {
return key
}
if (targetKey == key) {
next = true
}
}
return first
}
static fromPath(obj, path, separator = '.') {
let arr = path.split(separator)
let result = obj
for (let i = 0; i < arr.length; i++) {
if (result[arr[i]] !== null) {
result = result[arr[i]]
} else return null
}
return result
}
}
export class PathUtils {
static fixTrailingSlash(url) {
return url.replace(/\/?$/, '/')
}
}
export class TimeUtils {
static minutesToMs(minutes) {
return TimeUtils.minutesToSeconds(minutes) * 1000
}
static minutesToSeconds(minutes) {
return minutes * 60
}
}
export class StringUtils {
/* Used from: https://stackoverflow.com/questions/1026069/how-do-i-make-the-first-letter-of-a-string-uppercase-in-javascript */
static capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
}
export class GeometryUtils {
static RandomRange(min, max) {
return GeometryUtils.toRadians(Math.random() * (max - min) + min)
}
static RandomSign() {
return Math.sign(Math.random() - 0.5)
}
static toDegrees(radians) {
return (radians * 180) / Math.PI
}
static toRadians(degrees) {
return (degrees / 180) * Math.PI
}
/*
Recursion is buggy.
*/
static subdivide(polygon, factor = 0.5, n = 0) {
console.log(polygon.length, factor)
let points = []
for (let i = 0; i < polygon.length; i++) {
let prev = i - 1 < 0 ? polygon[polygon.length - 1] : polygon[i - 1]
let next = i + 1 < polygon.length ? polygon[i + 1] : polygon[0]
let current = polygon[i]
points.push(GeometryUtils.linearInterpolation(prev, current, factor))
points.push(GeometryUtils.linearInterpolation(current, next, 1 - factor))
}
if (n > 0) {
n--
points = GeometryUtils.subdivide(points, n)
}
return points
}
static linearInterpolation(first, second, n) {
let point = new PIXI.Point()
point.x = first.x * n + second.x * (1 - n)
point.y = first.y * n + second.y * (1 - n)
return point
}
}
export class Matrix {
constructor(a, b, d, e, c = 0, f = 0) {
Object.assign(this, {
a,
b,
c,
d,
e,
f,
g: 0,
h: 0,
i: 1
})
}
static Transpose(m) {
let first = ['b', 'c', 'f']
let second = ['d', 'g', 'h']
for (let i = 0; i < first.length; i++) {
let tmp = m[first[i]]
first[i] = second[i]
second[i] = tmp
}
}
static Rotation(deg) {
let rad = (2 * Math.PI * deg) / 180
return new Matrix(Math.cos(rad), -Math.sin(rad), Math.sin(rad), Math.cos(rad))
}
static Transform(x, y) {
return new Matrix(1, 0, 0, 1, x, y)
}
static MultiplyPoint(m, p) {
x = p.x * m.a + m.b * p.y + m.c * 1
y = p.x * m.d + p.y * m.e + m.f * 1
return {
x,
y
}
}
}
export class DomUtils {
static getCenter(element) {
let x = (element.clientLeft + element.clientWidth) / 2
let y = (element.clientTop + element.clientHeight) / 2
return { x, y }
}
static getTransformedPosition(element) {
let mat = this.getCSSMatrix(element)
return { x: mat[4], y: mat[5] }
}
static getCSSMatrix(element) {
let matrix = window.getComputedStyle(element).transform
if (matrix == 'none') {
return [1, 0, 0, 1, 0, 0]
} else {
const pre = 'matrix('
let values = matrix.substring(pre.length, matrix.length - 1).split(',')
for (let i in values) {
values[i] = parseFloat(values[i])
}
return values
}
}
static positionOnElement(element, position) {
const matrix = this.getCSSMatrix(element)
const inX = element.offsetWidth * (position.x - 0.5)
const inY = element.offsetHeight * (position.y - 0.5)
let outX = matrix[0] * inX + matrix[2] * inY + matrix[4]
let outY = matrix[1] * inX + matrix[3] * inY + matrix[5]
let out = {
x: outX,
y: outY
}
return out
}
static applyTransform(target, transformedPosition, size = 0) {
return {
x: target.offsetLeft + target.offsetWidth / 2 + transformedPosition.x - size / 2,
y: target.offsetTop + target.offsetHeight / 2 + transformedPosition.y - size / 2
}
}
}
export class Vector {
static length(vector) {
return Math.sqrt(vector.x * vector.x + vector.y * vector.y)
}
static normalize(vector) {
return Points.multiplyScalar(vector, 1 / this.length(vector))
}
static scaleTo(vector, length) {
let normalized = this.normalize(vector)
return Points.multiplyScalar(normalized, length)
}
}
2020-01-09 15:14:27 +01:00
export class PIXIUtils {
/*
* Transform a pixi text to it's actual screensize,
* ignoring it's local transforms
*/
static toScreenFontSize(pixiText, fontSize = null) {
pixiText._recursivePostUpdateTransform();
let normalizedScale = {
x: pixiText.scale.x / pixiText.transform.worldTransform.a,
y: pixiText.scale.x / pixiText.transform.worldTransform.d
};
pixiText.scale = { x: normalizedScale.x, y: normalizedScale.y };
if (fontSize) pixiText.style.fontSize = fontSize;
}
static saveFill(graphics) {
return {
fill: graphics.fill.color,
alpha: graphics.fill.alpha
}
}
}
/**
* The EventHandler class is used to take care of a event based design
* pattern. Callbacks can subscribe to an event and these unknown sources
* get notified whenever the event changes.
*
* @export
* @class EventHandler
*/
export class EventHandler {
/**
* Creates an instance of EventHandler.
* @param {any} name
* @param {any} [{
* listeners = [] - With the listnerers parameter the user can specify a function, array of functions or null (no function - useful when used in constructor with optional parameter).
* }={}]
* @memberof EventHandler
*/
constructor(name, { listeners = [] } = {}) {
this.name = name
this.listeners = []
this.onces = []
/**
* One may initialize the eventListener using a parameter
* that is either passed or null.
*/
if (listeners == null) {
// Null is a valid value as the EventHandler assumes no listener is passed on purpose.
// This is useful, when a default parameter is passed as null.
} else if (Array.isArray(listeners)) this.listeners = listeners
else if (typeof listeners == 'function') {
this.listeners = []
this.add(listeners)
} else {
console.warn(
"The provided 'listeners' is neither an Array of functions, nor a function. No eventcallback was added!",
listeners,
this
)
}
}
addMultiple(...callbacks) {
for (let callback of callbacks) {
this.listeners.push(callback)
}
}
add(callback) {
this.listeners.push(callback)
return callback
}
once(callback) {
this.onces.push(callback)
}
remove(func) {
for (const [idx, listener] of this.listeners.entries()) {
if (listener === func) {
this.listeners.splice(idx, 1)
return true
}
}
return false
}
empty() {
this.listeners = []
}
call(context, ...args) {
if (context == null) {
this.listeners.forEach(listener => listener(...args))
this.onces.forEach(listener => listener(...args))
} else {
this.listeners.forEach(listener => listener.call(context, ...args))
this.onces.forEach(listener => listener.call(context, ...args))
}
this.onces = []
}
get length() {
return this.listeners.length + this.onces.length
}
}
export class Dom {
/**
* Popups should be displayed right over the text.
* Normally we would expect the popup to appear right over
* the center of the text. A problem in HTML is, that it's hard
* to determine the position of a text link, when it has a line-break
* in it.
*
* This function solves this problem in the (so far) only possible way.
*
* 1. It removes the link from the dom tree.
* 2. It adds an empty copy A of the link to the dom tree. (Copy is important, as the same styles have to be applied.)
* 3. The contents of the link are added one by one to A.
* 4. If the resulting boundingRect is bigger than the previous one, a line break is detected.
* 5. The old line is tested, if the point was inside that boundingBox. If so save that bounding box (Goto: 7), else:
* 6. Saves the content to a preceding clone B. And repeats from 3.
* 7. Replace A with the initial content
* 8. Return the found BoundingBox. If none found. Return the last bounding box.
*/
static getTextHitRect(link, point) {
// We cannot use it as it produces axis aligned bounding boxes
/* if (true) {
let rects = link.getClientRects()
let target = null
for (let [idx, rect] of Object.entries(rects)) {
target = rect
if (Rect.contains(rect, point))
break
}
return target
} else {*/
let processedText = link.cloneNode(true)
let content = processedText.innerHTML
let words = content.split(/ /g)
processedText.innerHTML = ''
event.target.innerHTML = ''
// let lineRect = event.target.getBoundingClientRect()
let local = Points.fromPageToNode(event.target.parentNode, point)
console.log(local)
let target = event.target
let height = 0
while (words.length > 0) {
let word = words.pop()
target.innerHTML += word + ' '
if (target.height != height) {
// New line was reached.
console.log('NEW LINE WAS REACHED!')
}
}
return {
top: 0,
left: 0,
right: 100,
bottom: 100,
width: 100,
height: 100
}
// let total = words.length
// while (words.length > 0) {
// let lastRect = lineRect
// let lastContent = event.target.innerHTML
// let added = words.length == total ? "" : " "
// added += words.shift()
// event.target.innerHTML += added
// lineRect = event.target.getBoundingClientRect()
// // When new line or last line:
// if (lineRect.height != lastRect.height) {
// //Reconstructure last line.
// event.target.innerHTML = lastContent
// //Create Rect from last line.
// lineRect = event.target.getBoundingClientRect()
// rects.push(lineRect)
// //Copy last line content to processed text.
// processedText.innerHTML += lastContent
// // Create content of new line.
// event.target.innerHTML = added
// if (!processedText.parentNode) event.target.parentNode.insertBefore(processedText, event.target)
// if (Rect.contains(lineRect, point)) {
// break
// }
// }
// if (words.length == 0) {
// lineRect = event.target.getBoundingClientRect()
// processedText.innerHTML += event.target.innerHTML
// rects.push(lineRect)
// }
// }
// event.target.innerHTML = content
// if (processedText.parentNode) processedText.parentNode.removeChild(processedText)
// return lineRect
// }
}
static printDomRect(rect, { color = 'red', pad = 0, parent = document.body }) {
let element = document.createElement('div')
Object.assign(element.style, {
padding: pad + 'px',
borderColor: color,
borderWidth: '2px',
borderStyle: 'solid',
top: rect.top + 'px',
left: rect.left + 'px',
width: rect.width + 'px',
height: rect.height + 'px',
position: 'absolute',
zIndex: 10000,
opacity: 0.3
})
parent.appendChild(element)
}
static printDomPoint(point, { color = 'red', parent = document.body }) {
let element = document.createElement('div')
Object.assign(element.style, {
position: 'absolute',
top: point.y + 'px',
left: point.x + 'px',
width: '10px',
height: '10px',
backgroundColor: color,
zIndex: 10000
})
parent.appendChild(element)
}
}