990 lines
41 KiB
JavaScript
990 lines
41 KiB
JavaScript
|
|
function newFloatingLayer () {
|
|
/*
|
|
This module represent one layer of many possible layers that could be at the Floating Space. Objects in the same layer are subject to
|
|
the same physics, and by being at the same level, they interact with each other. For example, they can bounce when one hit the other.
|
|
This module posesses its own physics engine. There are different types of floating objects, but this layer takes care of each of them.
|
|
|
|
Floating Objects Types:
|
|
|
|
1. Profile Balls: These are small balls with a profile picture, and two levels, one above and one below the ball. They are usually used
|
|
to specify the location of a bot instance.
|
|
|
|
2. Notes: Notes are a rectangular area where some text is posted. It has a subject and a body.
|
|
*/
|
|
|
|
const MODULE_NAME = 'Floating Layer'
|
|
const INFO_LOG = false
|
|
let INTENSIVE_LOG = false
|
|
const ERROR_LOG = true
|
|
const logger = newWebDebugLog()
|
|
logger.fileName = MODULE_NAME
|
|
|
|
let thisObject = {
|
|
addFloatingObject: addFloatingObject,
|
|
killFloatingObject: killFloatingObject,
|
|
getFloatingObject: getFloatingObject,
|
|
physicsLoop: physicsLoop,
|
|
isInside: isInside,
|
|
isInsideFloatingObject: isInsideFloatingObject,
|
|
changeTargetRepulsion: changeTargetRepulsion,
|
|
initialize: initialize
|
|
}
|
|
|
|
/*
|
|
When objects are added to this layer they are added to the invisibleFloatingObjects array and they stay there because they are invisible.
|
|
Visibility of objects is not decided at this layer, but outside. The ones who decides which object is visible or not are plotters, because
|
|
they know the position of objects and the coordinate system which these positions belong to.
|
|
|
|
To save resources, the physics engine only apply its calculations to visible floating objects.
|
|
*/
|
|
|
|
let invisibleFloatingObjects = []
|
|
|
|
/*
|
|
Floating objects are accesible to plotters because they carry a "payload" object that was creted by them. This object includes the position
|
|
and visible property. When visible turns to true, this layer module will move the reference of the floating object from the invisible array
|
|
to the visibleFloatingObjects array. If the plotter decides that the object is to far from the what the user is seeing, it can eventually
|
|
turn the visible property off again, forcing the layer to remove the object from the visibleFloatingObjects and place it again at the invisible
|
|
array.
|
|
*/
|
|
|
|
let visibleFloatingObjects = []
|
|
|
|
/*
|
|
The creator of the floatingObject at any time can decide to get rid of it and kill it. But we dont want the object just to dissapear because
|
|
that doesnt look good. What this module does is to remove it from the visible or invisible layer where they are and move them to the
|
|
dyingFloatingObjects array. Object in this array have a short animation until they finally graphically dissapear and at thay point they are also
|
|
removed from the dying array for good.
|
|
*/
|
|
|
|
let dyingFloatingObjects = []
|
|
|
|
let maxTargetRepulsionForce = 0.003
|
|
let currentHandle = 0
|
|
|
|
return thisObject
|
|
|
|
function initialize (callBackFunction) {
|
|
try {
|
|
if (INFO_LOG === true) { logger.write('[INFO] initialize -> Entering function.') }
|
|
|
|
/* We dont need to initialize anything right now. */
|
|
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) }
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err.message = ' + err.message) }
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) }
|
|
}
|
|
}
|
|
|
|
function addFloatingObject (pFloatingObject, callBackFunction) {
|
|
/*
|
|
As you can see here, even though the floating objects are created outside this layer, its creator it is not supossed to keep
|
|
the reference to the object, so that the managment of the objects references can live entirely inside this module. What the
|
|
creator keeps, is a handle to the object which it can use to retrieve the floating object when needed.
|
|
*/
|
|
|
|
try {
|
|
if (INFO_LOG === true) { logger.write('[INFO] addFloatingObject -> Entering function.') }
|
|
|
|
invisibleFloatingObjects.push(pFloatingObject)
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] addFloatingObject -> invisibleFloatingObjects.length = ' + invisibleFloatingObjects.length) }
|
|
|
|
currentHandle++
|
|
|
|
pFloatingObject.handle = currentHandle // Math.floor((Math.random() * 10000000) + 1);
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] addFloatingObject -> pFloatingObject.handle = ' + pFloatingObject.handle) }
|
|
if (INFO_LOG === true) { logger.write('[INFO] addFloatingObject -> pFloatingObject.type = ' + pFloatingObject.type) }
|
|
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) }
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] addFloatingObject -> err.message = ' + err.message) }
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) }
|
|
}
|
|
}
|
|
|
|
function killFloatingObject (pFloatingObjectHandle, callBackFunction) {
|
|
try {
|
|
/*
|
|
The floting object to be killed can be either at the visible or the invisible array. What we do here is look for it
|
|
at each of the arrays until we find it. Once found, we remove it and is sent to the dying floating objects array.
|
|
*/
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> Entering function.') }
|
|
|
|
for (let i = 0; i < invisibleFloatingObjects.length; i++) {
|
|
let floatingObject = invisibleFloatingObjects[i]
|
|
|
|
if (floatingObject.handle === pFloatingObjectHandle) {
|
|
invisibleFloatingObjects.splice(i, 1) // Delete item from array.
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> Removing floatingObject from invisibleFloatingObjects.') }
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> invisibleFloatingObjects.length = ' + invisibleFloatingObjects.length) }
|
|
|
|
sendToDie(floatingObject)
|
|
return
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
|
|
if (floatingObject.handle === pFloatingObjectHandle) {
|
|
visibleFloatingObjects.splice(i, 1) // Delete item from array.
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> Removing floatingObject from visibleFloatingObjects.') }
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> visibleFloatingObjects.length = ' + visibleFloatingObjects.length) }
|
|
|
|
sendToDie(floatingObject)
|
|
return
|
|
}
|
|
}
|
|
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] killFloatingObject -> Floating Object Not Found.') }
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] killFloatingObject -> Floating Object cannot be killed.') }
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] killFloatingObject -> pFloatingObjectHandle = ' + pFloatingObjectHandle) }
|
|
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) }
|
|
|
|
function sendToDie (pFloatingObject) {
|
|
try {
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> sendToDie -> Entering function.') }
|
|
|
|
/* Lets mofigy the targerRadius */
|
|
|
|
pFloatingObject.targetRadius = 0
|
|
|
|
/* Lets transfer the payload to a new payload structure. */
|
|
|
|
let payload = {}
|
|
|
|
switch (pFloatingObject.type) {
|
|
|
|
case 'Profile Ball': {
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> sendToDie -> Profile Ball -> Entering function.') }
|
|
|
|
if (pFloatingObject.payload.profile.visible === false) { return }
|
|
|
|
payload.profile = {
|
|
position: {
|
|
x: pFloatingObject.payload.profile.position.x,
|
|
y: pFloatingObject.payload.profile.position.y
|
|
},
|
|
visible: pFloatingObject.payload.profile.visible,
|
|
botAvatar: pFloatingObject.payload.profile.botAvatar
|
|
}
|
|
|
|
pFloatingObject.payload = payload
|
|
break
|
|
}
|
|
case 'Note': {
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> sendToDie -> Note -> Entering function.') }
|
|
|
|
if (pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].visible === false) { return }
|
|
|
|
payload.notes = []
|
|
let note = {
|
|
title: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].title,
|
|
body: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].body,
|
|
date: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].date,
|
|
rate: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].rate,
|
|
position: {
|
|
x: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].position.x,
|
|
y: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].position.y
|
|
},
|
|
visible: pFloatingObject.payload.notes[pFloatingObject.payloadNoteIndex].visible
|
|
}
|
|
payload.notes.push(note)
|
|
pFloatingObject.payloadNoteIndex = 0
|
|
|
|
pFloatingObject.payload = payload
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
dyingFloatingObjects.push(pFloatingObject)
|
|
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> sendToDie -> Adding floatingObject to dyingFloatingObjects.') }
|
|
if (INFO_LOG === true) { logger.write('[INFO] killFloatingObject -> sendToDie -> dyingFloatingObjects.length = ' + dyingFloatingObjects.length) }
|
|
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) }
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] killFloatingObject -> err.message = ' + err.message) }
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) }
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] killFloatingObject -> err.message = ' + err.message) }
|
|
if (callBackFunction !== undefined) { callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) }
|
|
}
|
|
}
|
|
|
|
function getFloatingObject (pFloatingObjectHandle, pFloatingObjectIndex) {
|
|
/*
|
|
There are two ways to look for a floating object: by handle or by index. The search by handle is done on the visible and invisible array.
|
|
The search by index is done only on the visible ones. The search by index is usually needed when mouse events occurs. In those cases
|
|
only objects visible to the end usser matters.
|
|
*/
|
|
|
|
try {
|
|
if (INFO_LOG === true) { logger.write('[INFO] getFloatingObject -> Entering function.') }
|
|
|
|
if (pFloatingObjectHandle !== undefined) {
|
|
for (let i = 0; i < invisibleFloatingObjects.length; i++) {
|
|
let floatingObject = invisibleFloatingObjects[i]
|
|
|
|
if (floatingObject.handle === pFloatingObjectHandle) {
|
|
return floatingObject
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
|
|
if (floatingObject.handle === pFloatingObjectHandle) {
|
|
return floatingObject
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pFloatingObjectIndex !== undefined) {
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
|
|
if (i === pFloatingObjectIndex) {
|
|
return floatingObject
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] getFloatingObject -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
/******************************************/
|
|
/* */
|
|
/* Physics Engine Follows */
|
|
/* */
|
|
/******************************************/
|
|
|
|
function physicsLoop () {
|
|
/*
|
|
The Physics engine is hooked at the animation loop, so it executes very very often. According to this Physics engine, each
|
|
floating object has a position and a speed. In fact, each floating object has two positions at the same time:
|
|
|
|
1. The current position: is the position of the ball, floating around.
|
|
2. The anchor position: is the position of where the ball is achored on the plot. This one is inside the payload object controlled by the
|
|
plotter.
|
|
|
|
The anchor position influences the current position, since the Physics engine will apply a gravity force to the ball's position in order
|
|
to make it gravitate towards the anchor position. At the same time there will be a repulsion force that will prevent the ball reaching
|
|
the anchor position, so that the anchor point keeps visible to the user.
|
|
|
|
*/
|
|
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> Entering function.') }
|
|
|
|
applyPhysics()
|
|
|
|
function applyPhysics () {
|
|
/* This function makes all the calculations to apply phisycs on all visible floatingObjects in this layer. */
|
|
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> applyPhysics -> Entering function.') }
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> Change position based on speed.') }
|
|
|
|
floatingObject.currentPosition.x = floatingObject.currentPosition.x + floatingObject.currentSpeed.x
|
|
floatingObject.currentPosition.y = floatingObject.currentPosition.y + floatingObject.currentSpeed.y
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> Apply some friction to desaccelerate.') }
|
|
|
|
floatingObject.currentSpeed.x = floatingObject.currentSpeed.x * floatingObject.friction // Desaceleration factor.
|
|
floatingObject.currentSpeed.y = floatingObject.currentSpeed.y * floatingObject.friction // Desaceleration factor.
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> Gives a minimun speed towards their taget.') }
|
|
|
|
let payload = {
|
|
position: undefined,
|
|
visible: false
|
|
}
|
|
|
|
switch (floatingObject.type) {
|
|
|
|
case 'Profile Ball': {
|
|
payload.position = floatingObject.payload.profile.position
|
|
payload.visible = floatingObject.payload.profile.visible
|
|
break
|
|
}
|
|
case 'Note': {
|
|
payload.position = floatingObject.payload.notes[floatingObject.payloadNoteIndex].position
|
|
payload.visible = floatingObject.payload.notes[floatingObject.payloadNoteIndex].visible
|
|
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
if (floatingObject.currentPosition.x < payload.position.x) {
|
|
floatingObject.currentSpeed.x = floatingObject.currentSpeed.x + 0.005
|
|
} else {
|
|
floatingObject.currentSpeed.x = floatingObject.currentSpeed.x - 0.005
|
|
}
|
|
|
|
if (floatingObject.currentPosition.y < payload.position.y) {
|
|
floatingObject.currentSpeed.y = floatingObject.currentSpeed.y + 0.005
|
|
} else {
|
|
floatingObject.currentSpeed.y = floatingObject.currentSpeed.y - 0.005
|
|
}
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> Set a maximun speed.') }
|
|
|
|
const MAX_SPEED = 50
|
|
|
|
if (floatingObject.currentSpeed.x > MAX_SPEED) {
|
|
floatingObject.currentSpeed.x = MAX_SPEED
|
|
}
|
|
|
|
if (floatingObject.currentSpeed.y > MAX_SPEED) {
|
|
floatingObject.currentSpeed.y = MAX_SPEED
|
|
}
|
|
|
|
if (floatingObject.currentSpeed.x < -MAX_SPEED) {
|
|
floatingObject.currentSpeed.x = -MAX_SPEED
|
|
}
|
|
|
|
if (floatingObject.currentSpeed.y < -MAX_SPEED) {
|
|
floatingObject.currentSpeed.y = -MAX_SPEED
|
|
}
|
|
|
|
// The radius also have a target.
|
|
|
|
if (Math.abs(floatingObject.currentRadius - floatingObject.targetRadius) >= 1) {
|
|
if (floatingObject.currentRadius < floatingObject.targetRadius) {
|
|
floatingObject.currentRadius = floatingObject.currentRadius + 0.5
|
|
} else {
|
|
floatingObject.currentRadius = floatingObject.currentRadius - 0.5
|
|
}
|
|
}
|
|
|
|
// The imageSize also have a target.
|
|
|
|
if (Math.abs(floatingObject.currentImageSize - floatingObject.targetImageSize) >= 1) {
|
|
if (floatingObject.currentImageSize < floatingObject.targetImageSize) {
|
|
floatingObject.currentImageSize = floatingObject.currentImageSize + 1
|
|
} else {
|
|
floatingObject.currentImageSize = floatingObject.currentImageSize - 1
|
|
}
|
|
}
|
|
|
|
// The fontSize also have a target.
|
|
|
|
if (Math.abs(floatingObject.currentFontSize - floatingObject.targetFontSize) >= 0.2) {
|
|
if (floatingObject.currentFontSize < floatingObject.targetFontSize) {
|
|
floatingObject.currentFontSize = floatingObject.currentFontSize + 0.2
|
|
} else {
|
|
floatingObject.currentFontSize = floatingObject.currentFontSize - 0.2
|
|
}
|
|
}
|
|
|
|
// We let the Floating Object animate the physics loops by itself.
|
|
|
|
floatingObject.physicsLoop()
|
|
|
|
/* Collision Control */
|
|
|
|
for (let k = i + 1; k < visibleFloatingObjects.length; k++) {
|
|
if (colliding(visibleFloatingObjects[i], visibleFloatingObjects[k])) {
|
|
resolveCollision(visibleFloatingObjects[k], visibleFloatingObjects[i])
|
|
}
|
|
}
|
|
|
|
/* Calculate repulsion force produced by all other floatingObjects */
|
|
|
|
currentRepulsionForce(i)
|
|
|
|
targetRepulsionForce(i)
|
|
|
|
gravityForce(floatingObject, payload)
|
|
}
|
|
|
|
drawVisibleObjects()
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> applyPhysics -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function drawVisibleObjects () {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> drawVisibleObjects -> Entering function.') }
|
|
|
|
/* We draw all the visibleFloatingObjects. */
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
floatingObject.drawBackground()
|
|
}
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[visibleFloatingObjects.length - i - 1]
|
|
floatingObject.drawForeground()
|
|
}
|
|
|
|
makeVisible()
|
|
} catch (err) {
|
|
if (ERROR_LOG === true && INTENSIVE_LOG === true) { logger.write('[ERROR] physicsLoop -> drawVisibleObjects -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function makeVisible () {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> makeVisible -> Entering function.') }
|
|
|
|
/* Now we check if any of the created FloatingObjects where enabled to run under the Physics Engine. */
|
|
|
|
for (let i = 0; i < invisibleFloatingObjects.length; i++) {
|
|
let floatingObject = invisibleFloatingObjects[i]
|
|
|
|
let payload = {
|
|
position: undefined,
|
|
visible: false
|
|
}
|
|
|
|
switch (floatingObject.type) {
|
|
|
|
case 'Profile Ball': {
|
|
payload.position = floatingObject.payload.profile.position
|
|
payload.visible = floatingObject.payload.profile.visible
|
|
break
|
|
}
|
|
case 'Note': {
|
|
if (floatingObject.payload.notes[floatingObject.payloadNoteIndex] !== undefined) {
|
|
payload.position = floatingObject.payload.notes[floatingObject.payloadNoteIndex].position
|
|
payload.visible = floatingObject.payload.notes[floatingObject.payloadNoteIndex].visible
|
|
}
|
|
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
if (payload.visible === true) {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> payload.visible = ' + payload.visible) }
|
|
|
|
/* The first time that the floatingObject becomes visible, we need to do this. */
|
|
|
|
floatingObject.radomizeCurrentPosition(payload.position)
|
|
floatingObject.radomizeCurrentSpeed()
|
|
|
|
visibleFloatingObjects.push(floatingObject)
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject added to visibleFloatingObjects') }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> visibleFloatingObjects.length = ' + visibleFloatingObjects.length) }
|
|
|
|
invisibleFloatingObjects.splice(i, 1) // Delete item from array.
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject removed from invisibleFloatingObjects') }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> invisibleFloatingObjects.length = ' + invisibleFloatingObjects.length) }
|
|
|
|
return // Only one at the time.
|
|
}
|
|
}
|
|
|
|
makeInvisible()
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> makeVisible -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function makeInvisible () {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> makeInvisible -> Entering function.') }
|
|
|
|
/* Finally we check if any of the currently visible floatingObjects has become invisible and must be removed from the Physics Engine. */
|
|
|
|
for (let i = 0; i < visibleFloatingObjects.length; i++) {
|
|
let floatingObject = visibleFloatingObjects[i]
|
|
|
|
let payload = {
|
|
position: undefined,
|
|
visible: true
|
|
}
|
|
|
|
switch (floatingObject.type) {
|
|
|
|
case 'Profile Ball': {
|
|
payload.visible = floatingObject.payload.profile.visible
|
|
break
|
|
}
|
|
case 'Note': {
|
|
payload.visible = floatingObject.payload.notes[floatingObject.payloadNoteIndex].visible
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
if (payload.visible === false) {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> payload.visible = ' + payload.visible) }
|
|
|
|
invisibleFloatingObjects.push(floatingObject)
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject added to invisibleFloatingObjects') }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> invisibleFloatingObjects.length = ' + invisibleFloatingObjects.length) }
|
|
|
|
visibleFloatingObjects.splice(i, 1) // Delete item from array.
|
|
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject.handle = ' + floatingObject.handle) }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> floatingObject removed from visibleFloatingObjects') }
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> visibleFloatingObjects.length = ' + visibleFloatingObjects.length) }
|
|
|
|
return // Only one at the time.
|
|
}
|
|
}
|
|
|
|
animateDyingObjects()
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> makeInvisible -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function animateDyingObjects () {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> animateDyingObjects -> Entering function.') }
|
|
|
|
/* We animate some parts of the dying objects */
|
|
|
|
for (let i = 0; i < dyingFloatingObjects.length; i++) {
|
|
let floatingObject = dyingFloatingObjects[i]
|
|
|
|
if (Math.abs(floatingObject.currentRadius - floatingObject.targetRadius) >= 5) {
|
|
let speed = Math.random()
|
|
|
|
floatingObject.currentRadius = floatingObject.currentRadius - speed * 3
|
|
} else {
|
|
/* Here is when the floatingObjects are definetelly killed. */
|
|
|
|
dyingFloatingObjects.splice(i, 1) // Delete item from array.
|
|
break // only one at the time.
|
|
}
|
|
}
|
|
|
|
drawDyingObjects()
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> animateDyingObjects -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function drawDyingObjects () {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] physicsLoop -> drawDyingObjects -> Entering function.') }
|
|
|
|
/* We also draw all the dyingFloatingObjects */
|
|
|
|
for (let i = 0; i < dyingFloatingObjects.length; i++) {
|
|
let floatingObject = dyingFloatingObjects[i]
|
|
floatingObject.drawBackground()
|
|
}
|
|
|
|
for (let i = 0; i < dyingFloatingObjects.length; i++) {
|
|
let floatingObject = dyingFloatingObjects[dyingFloatingObjects.length - i - 1]
|
|
floatingObject.drawForeground()
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> drawDyingObjects -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] physicsLoop -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function gravityForce (floatingObject, payload) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] gravityForce -> Entering function.') }
|
|
|
|
/* We simulate a kind of gravity towards the target point of each floatingObject. This force will make the floatingObject to keep pushing to reach that point. */
|
|
|
|
const coulomb = 0.00001
|
|
const minForce = 0.01
|
|
|
|
var d = Math.sqrt(Math.pow(payload.position.x - floatingObject.currentPosition.x, 2) + Math.pow(payload.position.y - floatingObject.currentPosition.y, 2)) // ... we calculate the distance ...
|
|
|
|
var force = coulomb * d * d / floatingObject.currentMass // In this case the mass of the floatingObject affects the gravity force that it receives, that gives priority to target position to bigger floatingObjects.
|
|
|
|
if (force < minForce) { // We need this attraction force to overcome the friction we imposed to the system.
|
|
force = minForce
|
|
}
|
|
|
|
var pos1 = {
|
|
x: floatingObject.currentPosition.x,
|
|
y: floatingObject.currentPosition.y
|
|
}
|
|
|
|
var pos2 = {
|
|
x: payload.position.x,
|
|
y: payload.position.y
|
|
}
|
|
|
|
var posDiff = { // Next we need the vector resulting from the 2 positions.
|
|
x: pos2.x - pos1.x,
|
|
y: pos2.y - pos1.y
|
|
}
|
|
|
|
var unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector.
|
|
x: posDiff.x / d,
|
|
y: posDiff.y / d
|
|
}
|
|
|
|
var forceVector = {
|
|
x: unitVector.x * force,
|
|
y: unitVector.y * force
|
|
}
|
|
|
|
/* We add the force vector to the speed vector */
|
|
|
|
floatingObject.currentSpeed.x = floatingObject.currentSpeed.x + forceVector.x
|
|
floatingObject.currentSpeed.y = floatingObject.currentSpeed.y + forceVector.y
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] gravityForce -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function currentRepulsionForce (currentFloatingObject) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] currentRepulsionForce -> Entering function.') }
|
|
|
|
/* We generate a repulsion force between floatingObjects, that prevents them to be collisioning so often. */
|
|
|
|
const coulomb = 2
|
|
|
|
var floatingObject1 = visibleFloatingObjects[currentFloatingObject]
|
|
|
|
for (var i = 0; i < visibleFloatingObjects.length; i++) {
|
|
// The force to be applied is considering all other floatingObjects...
|
|
|
|
if (i !== currentFloatingObject) {
|
|
// ... except for the current one.
|
|
|
|
var floatingObject2 = visibleFloatingObjects[i] // So, for each floatingObject...
|
|
|
|
var d = Math.sqrt(Math.pow(floatingObject2.currentPosition.x - floatingObject1.currentPosition.x, 2) + Math.pow(floatingObject2.currentPosition.y - floatingObject1.currentPosition.y, 2)) // ... we calculate the distance ...
|
|
|
|
var force = coulomb * floatingObject2.currentMass / (d * d) // ... and with it the repulsion force.
|
|
|
|
/* We need to put a hard limit to this force, in order to to eject very little floatingObjects to the infinite and beyond. */
|
|
|
|
if (force > 1) {
|
|
force = 1
|
|
}
|
|
|
|
var pos1 = {
|
|
x: floatingObject1.currentPosition.x,
|
|
y: floatingObject1.currentPosition.y
|
|
}
|
|
|
|
var pos2 = {
|
|
x: floatingObject2.currentPosition.x,
|
|
y: floatingObject2.currentPosition.y
|
|
}
|
|
|
|
var posDiff = { // Next we need the vector resulting from the 2 positions.
|
|
x: pos2.x - pos1.x,
|
|
y: pos2.y - pos1.y
|
|
}
|
|
|
|
var unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector.
|
|
x: posDiff.x / d,
|
|
y: posDiff.y / d
|
|
}
|
|
|
|
var forceVector = {
|
|
x: unitVector.x * force,
|
|
y: unitVector.y * force
|
|
}
|
|
|
|
/* We substract the force vector to the speed vector of the current floatingObject */
|
|
|
|
floatingObject1.currentSpeed.x = floatingObject1.currentSpeed.x - forceVector.x
|
|
floatingObject1.currentSpeed.y = floatingObject1.currentSpeed.y - forceVector.y
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] currentRepulsionForce -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function targetRepulsionForce (currentFloatingObject) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] targetRepulsionForce -> Entering function.') }
|
|
|
|
/* We generate a repulsion force between floatingObjects, that prevents them to be collisioning so often. */
|
|
|
|
const coulomb = 2
|
|
|
|
var floatingObject1 = visibleFloatingObjects[currentFloatingObject]
|
|
|
|
for (var i = 0; i < visibleFloatingObjects.length; i++) {
|
|
// The force to be applied is considering all other floatingObjects...
|
|
|
|
var floatingObject2 = visibleFloatingObjects[i] // So, for each floatingObject...
|
|
|
|
let payload = {
|
|
position: undefined
|
|
}
|
|
|
|
switch (floatingObject2.type) {
|
|
|
|
case 'Profile Ball': {
|
|
payload.position = floatingObject2.payload.profile.position
|
|
break
|
|
}
|
|
case 'Note': {
|
|
payload.position = floatingObject2.payload.notes[floatingObject2.payloadNoteIndex].position
|
|
break
|
|
}
|
|
default: {
|
|
break
|
|
}
|
|
}
|
|
|
|
var d = Math.sqrt(Math.pow(payload.position.x - floatingObject1.currentPosition.x, 2) + Math.pow(payload.position.y - floatingObject1.currentPosition.y, 2)) // ... we calculate the distance ...
|
|
|
|
var force = coulomb * floatingObject2.currentMass / (d * d) // ... and with it the repulsion force.
|
|
|
|
/* We need to put a hard limit to this force, in order to to eject very little floatingObjects to the infinite and beyond. */
|
|
|
|
if (force > maxTargetRepulsionForce) {
|
|
force = maxTargetRepulsionForce
|
|
}
|
|
|
|
var pos1 = {
|
|
x: floatingObject1.currentPosition.x,
|
|
y: floatingObject1.currentPosition.y
|
|
}
|
|
|
|
var pos2 = {
|
|
x: payload.position.x,
|
|
y: payload.position.y
|
|
}
|
|
|
|
var posDiff = { // Next we need the vector resulting from the 2 positions.
|
|
x: pos2.x - pos1.x,
|
|
y: pos2.y - pos1.y
|
|
}
|
|
|
|
var unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector.
|
|
x: posDiff.x / d,
|
|
y: posDiff.y / d
|
|
}
|
|
|
|
var forceVector = {
|
|
x: unitVector.x * force,
|
|
y: unitVector.y * force
|
|
}
|
|
|
|
/* We substract the force vector to the speed vector of the current floatingObject */
|
|
|
|
floatingObject1.currentSpeed.x = floatingObject1.currentSpeed.x - forceVector.x
|
|
floatingObject1.currentSpeed.y = floatingObject1.currentSpeed.y - forceVector.y
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] targetRepulsionForce -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function changeTargetRepulsion (pDelta) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] changeTargetRepulsion -> Entering function.') }
|
|
|
|
if (pDelta > 0) {
|
|
pDelta = 1
|
|
} else {
|
|
pDelta = -1
|
|
}
|
|
|
|
maxTargetRepulsionForce = maxTargetRepulsionForce + pDelta / 10000
|
|
|
|
if (maxTargetRepulsionForce < 0.0001) {
|
|
maxTargetRepulsionForce = 0.0001
|
|
}
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] changeTargetRepulsion -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function colliding (floatingObject1, floatingObject2) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] colliding -> Entering function.') }
|
|
|
|
/* This function detects weather 2 floatingObjects collide with each other. */
|
|
|
|
var r1 = floatingObject1.currentRadius
|
|
var r2 = floatingObject2.currentRadius
|
|
|
|
var distance = Math.sqrt(Math.pow(floatingObject2.currentPosition.x - floatingObject1.currentPosition.x, 2) + Math.pow(floatingObject2.currentPosition.y - floatingObject1.currentPosition.y, 2))
|
|
|
|
if (distance > (r1 + r2)) {
|
|
// No solutions, the circles are too far apart.
|
|
return false
|
|
} else if (distance <= r1 + r2) {
|
|
// One circle contains the other.
|
|
return true
|
|
} else if ((distance === 0) && (r1 === r2)) {
|
|
// the circles coincide.
|
|
return true
|
|
} else return true
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] colliding -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function isInside (x, y) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] isInside -> Entering function.') }
|
|
|
|
/* This function detects weather the point x,y is inside any of the floatingObjects. */
|
|
|
|
for (var i = 0; i < visibleFloatingObjects.length; i++) {
|
|
var floatingObject = visibleFloatingObjects[i]
|
|
var distance = Math.sqrt(Math.pow(floatingObject.currentPosition.x - x, 2) + Math.pow(floatingObject.currentPosition.y - y, 2))
|
|
|
|
if (distance < floatingObject.currentRadius) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] isInside -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function isInsideFloatingObject (floatingObjectIndex, x, y) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] isInsideFloatingObject -> Entering function.') }
|
|
|
|
/* This function detects weather the point x,y is inside one particular floatingObjects. */
|
|
|
|
var floatingObject = visibleFloatingObjects[floatingObjectIndex]
|
|
var distance = Math.sqrt(Math.pow(floatingObject.currentPosition.x - x, 2) + Math.pow(floatingObject.currentPosition.y - y, 2))
|
|
|
|
if (distance < floatingObject.currentRadius) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] isInsideFloatingObject -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function distance (x1, y1, x2, y2) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] distance -> Entering function.') }
|
|
|
|
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] distance -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
|
|
function resolveCollision (floatingObject1, floatingObject2) {
|
|
try {
|
|
if (INTENSIVE_LOG === true) { logger.write('[INFO] resolveCollision -> Entering function.') }
|
|
|
|
/* This function changes speed and position of floatingObjects that are in collision */
|
|
|
|
var collisionision_angle = Math.atan2((floatingObject2.currentPosition.y - floatingObject1.currentPosition.y), (floatingObject2.currentPosition.x - floatingObject1.currentPosition.x))
|
|
|
|
var speed1 = Math.sqrt(floatingObject1.currentSpeed.x * floatingObject1.currentSpeed.x + floatingObject1.currentSpeed.y * floatingObject1.currentSpeed.y) // Magnitude of Speed Vector for floatingObject 1
|
|
var speed2 = Math.sqrt(floatingObject2.currentSpeed.x * floatingObject2.currentSpeed.x + floatingObject2.currentSpeed.y * floatingObject2.currentSpeed.y) // Magnitude of Speed Vector for floatingObject 2
|
|
|
|
var direction_1 = Math.atan2(floatingObject1.currentSpeed.y, floatingObject1.currentSpeed.x)
|
|
var direction_2 = Math.atan2(floatingObject2.currentSpeed.y, floatingObject2.currentSpeed.x)
|
|
|
|
var new_xspeed_1 = speed1 * Math.cos(direction_1 - collisionision_angle)
|
|
var new_yspeed_1 = speed1 * Math.sin(direction_1 - collisionision_angle)
|
|
var new_xspeed_2 = speed2 * Math.cos(direction_2 - collisionision_angle)
|
|
var new_yspeed_2 = speed2 * Math.sin(direction_2 - collisionision_angle)
|
|
|
|
var final_xspeed_1 = ((floatingObject1.currentMass - floatingObject2.currentMass) * new_xspeed_1 + (floatingObject2.currentMass + floatingObject2.currentMass) * new_xspeed_2) / (floatingObject1.currentMass + floatingObject2.currentMass)
|
|
var final_xspeed_2 = ((floatingObject1.currentMass + floatingObject1.currentMass) * new_xspeed_1 + (floatingObject2.currentMass - floatingObject1.currentMass) * new_xspeed_2) / (floatingObject1.currentMass + floatingObject2.currentMass)
|
|
var final_yspeed_1 = new_yspeed_1
|
|
var final_yspeed_2 = new_yspeed_2
|
|
|
|
var cosAngle = Math.cos(collisionision_angle)
|
|
var sinAngle = Math.sin(collisionision_angle)
|
|
|
|
floatingObject1.currentSpeed.x = cosAngle * final_xspeed_1 - sinAngle * final_yspeed_1
|
|
floatingObject1.currentSpeed.y = sinAngle * final_xspeed_1 + cosAngle * final_yspeed_1
|
|
|
|
floatingObject2.currentSpeed.x = cosAngle * final_xspeed_2 - sinAngle * final_yspeed_2
|
|
floatingObject2.currentSpeed.y = sinAngle * final_xspeed_2 + cosAngle * final_yspeed_2
|
|
|
|
var pos1 = {
|
|
x: floatingObject1.currentPosition.x,
|
|
y: floatingObject1.currentPosition.y
|
|
}
|
|
|
|
var pos2 = {
|
|
x: floatingObject2.currentPosition.x,
|
|
y: floatingObject2.currentPosition.y
|
|
}
|
|
|
|
// get the mtd
|
|
var posDiff = {
|
|
x: pos1.x - pos2.x,
|
|
y: pos1.y - pos2.y
|
|
}
|
|
|
|
var d = Math.sqrt(Math.pow(floatingObject2.currentPosition.x - floatingObject1.currentPosition.x, 2) + Math.pow(floatingObject2.currentPosition.y - floatingObject1.currentPosition.y, 2))
|
|
|
|
// minimum translation distance to push floatingObjects apart after intersecting
|
|
var scalar = (((floatingObject1.currentRadius + floatingObject2.currentRadius) - d) / d)
|
|
|
|
var minTD = {
|
|
x: posDiff.x * scalar,
|
|
y: posDiff.y * scalar
|
|
}
|
|
|
|
// resolve intersection --
|
|
// computing inverse mass quantities
|
|
var im1 = 1 / floatingObject1.currentMass
|
|
var im2 = 1 / floatingObject2.currentMass
|
|
|
|
// push-pull them apart based off their mass
|
|
|
|
pos1.x = pos1.x + minTD.x * (im1 / (im1 + im2))
|
|
pos1.y = pos1.y + minTD.y * (im1 / (im1 + im2))
|
|
pos2.x = pos2.x - minTD.x * (im2 / (im1 + im2))
|
|
pos2.y = pos2.y - minTD.y * (im2 / (im1 + im2))
|
|
|
|
floatingObject1.currentPosition = pos1
|
|
floatingObject2.currentPosition = pos2
|
|
} catch (err) {
|
|
if (ERROR_LOG === true) { logger.write('[ERROR] resolveCollision -> err.message = ' + err.message) }
|
|
}
|
|
}
|
|
}
|
|
|