Fakultas Ilmu Komputer UI

Commit df69ec4e authored by Agas Yanpratama's avatar Agas Yanpratama 💬
Browse files

Merge branch 'dark-theme' into 'dev'

Improvement

See merge request !3
parents fd27b019 a661249f
Pipeline #43185 passed with stage
in 3 minutes and 43 seconds
/**
* Replace Javascript's native number where mod over
* negative number yields negative number
*/
function mod(number, divisor) {
return ((number % divisor) + divisor) % divisor;
}
/**
* Normalize current frame number so that it stays within keyframe loop time range
* while maintaining congruency.
* Specifically, it returns s + (c - e) % t where s is start range, e is end range,
* c is current range, and t is range span.
*/
function normalizeFrameNumber(current, start, span) {
return start + mod(current - start, span);
}
/**
* Find index of nearest frame number with value closest
* but not greater than current frame.
* @param {Array<Number>} frameNumbers
* @param {Number} currentFrame
*/
function nearestFrameIndex(frameNumbers, currentFrame) {
let nearestId = 0;
for (let i = 0; i < frameNumbers.length; i++) {
if (frameNumbers[i] < currentFrame) {
nearestId = i;
} else {
break;
}
}
return nearestId
}
/** Find value interpolated linearly (linerp) from current frame,
* given keyframes and its corresponding keyframe value.
*
* Examples:
* - `getLinerpValue([9, 13, 17], [0, 0.2, 0.8], 9)` returns 0
* - `getLinerpValue([9, 13, 17], [0, 0.2, 0.8], 11)` returns 0.1
* - `getLinerpValue([9, 13, 17], [0, 0.2, 0.8], 15)` returns 0.5
*
* @param {Array<Number>} frameNumbers
* @param {Array<Number>} values
* @param {Number} currFrame
*/
function getLinerpValue(frameNumbers, values, currFrame) {
let start = frameNumbers[0];
let end = frameNumbers[frameNumbers.length - 1];
let span = end - start;
// normalize frame number
currFrame = normalizeFrameNumber(currFrame, start, span);
let nearestId = nearestFrameIndex(frameNumbers, currFrame)
// compute linear interpolation
let prevFrame = frameNumbers[nearestId]
let nextFrame = frameNumbers[nearestId + 1]
let prevVal = values[nearestId]
let nextVal = values[nearestId + 1]
let factor = (currFrame - prevFrame) / (nextFrame - prevFrame);
return prevVal * (1 - factor) + nextVal * factor;
}
class AnimationManager extends EventDispatcher {
constructor({ sceneGraph, speed, maxFrameNumber }) {
super()
this.sceneGraph = sceneGraph
/** List of animation keyframe values
*/
this.animationValues = {}
this.frameNow = 0
this._speed = speed
this.speed = this._speed
this.maxFrameNumber = maxFrameNumber
this.isAnimating = false
}
startAnimation() {
this.isAnimating = true
this.dispatchEvent('start')
this.animate()
}
stopAnimation() {
this.isAnimating = false
this.dispatchEvent('stop')
}
initFromConfig(animationConfig) {
Object.keys(animationConfig).forEach(key => {
let arr = animationConfig[key].split(" ")
let frames = []
let values = []
for (let i = 0; i < arr.length; i += 2) {
frames.push(parseInt(arr[i].slice(0, -1)))
values.push(parseFloat(arr[[i + 1]]))
}
let interpolatedValues = []
// Generate n + 2 interpolation points at every integer frame number
for (let i = 0; i < this.maxFrameNumber + 2; i++) {
interpolatedValues.push(getLinerpValue(frames, values, i))
}
this.animationValues[key] = interpolatedValues
})
}
animate() {
let nodes = this.sceneGraph.nodes
let frameNow = this.frameNow
let speed = this.speed
let actualFrame = frameNow * speed
let frameId = parseInt(actualFrame)
let factor = actualFrame - frameId
let animationValues = this.animationValues
Object.keys(this.animationValues).forEach(sliderName => {
const data = parsePropertyString(sliderName)
if (!data) { return }
const { modelName, propertyName, axisId } = data
let values = animationValues[sliderName]
// Interpolate linearly from two consecutive frames
let value = values[frameId + 1] * factor + values[frameId] * (1 - factor)
// Update model transformation properties
nodes[modelName].model[propertyName][axisId] = value
})
// Update transformation matrices, starting from root nodes
this.sceneGraph.updateModelsTransformations()
this.dispatchEvent('animationupdate', { currentFrame: frameNow })
// Update frame number
if (this.frameNow > this.maxFrameNumber / speed - 1) {
this.frameNow = 0
}
else {
this.frameNow++
}
let self = this
if (this.isAnimating) {
window.requestAnimationFrame(() => {self.animate()})
}
}
get speed() {
return this._speed
}
set speed (newSpeed) {
if (newSpeed < 0.001) {
return
}
this.frameNow = this.frameNow / newSpeed * this._speed
this._speed = newSpeed
}
}
\ No newline at end of file
// Animation Var
var animationDict = {};
let frameNow = 0
let maxFrameNumber = 120;
let speed = 0.5;
var sliderList = [];
var throttledUpdateAnimation = () => {};
const throttle = (func, limit) => {
let lastFunc
let lastRan
return function() {
const context = this
const args = arguments
if (!lastRan) {
func.apply(context, args)
lastRan = Date.now()
} else {
clearTimeout(lastFunc)
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args)
lastRan = Date.now()
}
}, limit - (Date.now() - lastRan))
}
}
}
function startAnimation() {
isAnimated = true;
animate();
}
function stopAnimation() {
isAnimated = false;
}
function initAnimationDict() {
for (let key in animations_definition) {
let arr = animations_definition[key].split(" ")
let frames = []
let values = []
for (let i = 0; i < arr.length; i += 2) {
frames.push(parseInt(arr[i].slice(0, -1)))
values.push(parseFloat(arr[[i + 1]]))
}
let interpolatedValues = []
for (let i = 0; i < maxFrameNumber + 2; i++) {
interpolatedValues.push(getInterpolatedValue(frames, values, i))
}
animationDict[key] = interpolatedValues;
}
throttledUpdateAnimation = throttle(updateSliderToMatchAnimation, 50);
}
/**
* Replace Javascript's native number where mod over
* negative number yields negative number
*/
function mod(number, divisor) {
return ((number % divisor) + divisor) % divisor;
}
function normalizeFrameNumber(currentFrame, beginFrameNum, loopTime) {
let frameNum = beginFrameNum + mod(currentFrame - beginFrameNum, loopTime);
return frameNum;
}
/**
*
* @param {array} frameNumbers
* @param {int} currentFrame
*/
function getNearestFrameNumber(frameNumbers, currentFrame) {
let nearestId = 0;
for (let i = 0; i < frameNumbers.length; i++) {
if (frameNumbers[i] < currentFrame) {
nearestId = i;
} else {
break;
}
}
return nearestId
}
/**
* contoh output:
* getInterpolatedValue([9, 13, 17], [0, 0.2, 0.8], 9) // hasilnya 0
* getInterpolatedValue([9, 13, 17], [0, 0.2, 0.8], 10) // hasilnya 0.05
*/
function getInterpolatedValue(frameNumbers, values, currFrame) {
let beginFrameNum = frameNumbers[0];
let endFrameNum = frameNumbers[frameNumbers.length - 1];
let loopTime = endFrameNum - beginFrameNum;
// normalize frame number
currFrame = normalizeFrameNumber(currFrame, beginFrameNum, loopTime);
let nearestId = getNearestFrameNumber(frameNumbers, currFrame)
// compute linear interpolation
let prevFrame = frameNumbers[nearestId]
let nextFrame = frameNumbers[nearestId + 1]
let prevVal = values[nearestId]
let nextVal = values[nearestId + 1]
let factor = (currFrame - prevFrame) / (nextFrame - prevFrame);
return prevVal * (1 - factor) + nextVal * factor;
}
function animate() {
for (let sliderName in animationDict) {
const matches = sliderName.match(/^([a-zA-Z_.]+)\.(location|rotation|scale)\.(x|y|z)$/);
if (!matches) {
return
}
const objectName = matches[1];
const propertyName = matches[2];
const axisId = ['x', 'y', 'z'].indexOf(matches[3]);
let actualFrame = frameNow * speed;
let frameId = parseInt(actualFrame);
let values = animationDict[sliderName];
let factor = actualFrame - frameId;
value = values[frameId + 1] * factor + values[frameId] * (1 - factor);
ObjectNode.cache[objectName].model[propertyName][axisId] = value
}
// Update all transformations
rootNodes.forEach(node => node.updateTransformations())
sliderList = listCustomSliders();
throttledUpdateAnimation();
// Update Frame Number
if (frameNow > maxFrameNumber / speed - 1) frameNow = 0;
else frameNow++;
if (isAnimated) {
window.requestAnimationFrame(animate)
}
}
function updateSpeed(newSpeed) {
if (newSpeed < 0.001) {
return;
}
frameNow = frameNow / newSpeed * speed;
speed = newSpeed;
}
function listCustomSliders() {
let listName = [];
document.querySelectorAll('input[type="range"]')
.forEach(elem => {
const sliderName = elem.getAttribute('name')
const matches = sliderName.match(/^([a-zA-Z_.]+)\.(location|rotation|scale)\.(x|y|z)$/);
if (!matches) {
return
}
const objectName = matches[1];
if (!ObjectNode.cache[objectName]) {
return
}
const propertyName = matches[2];
const axisId = ['x', 'y', 'z'].indexOf(matches[3]);
listName.push({
sliderName,
objectName,
propertyName,
axisId
});
})
return listName;
}
function updateSliderToMatchAnimation() {
sliderList.forEach(({sliderName, objectName, propertyName, axisId}) => {
let objectPropertyValue = ObjectNode.cache[objectName].model[propertyName][axisId];
let sliderElement = document.querySelector(`input[name="${sliderName}"]`);
sliderElement.value = objectPropertyValue;
sliderElement.parentElement.querySelector('.slider-value').textContent = Math.round(objectPropertyValue * 100) / 100;
})
}
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.8 12.3" height="46.5" width="48.5"><path d="M6.6 0l6.2 12.3H0z" fill="#fff"/></svg>
\ No newline at end of file
// These coordinates uses Y+ axis as world's up
// which is inconsistent with Blender, so it will be swapped later.
var cameraCoordinates = [
[-1, +1, -1],
[+0, +1, -1],
......@@ -55,7 +58,7 @@ var cameraMovementCoordinates = {
12: [9, 3, 14, 20],
13: [11, 22, 16, 5],
14: [12, 6, 15, 23],
15: [14, 24, 16, 7],
15: [7, 16, 24, 14],
16: [13, 25, 15, 8],
17: [-1, 9, 20, 18],
18: [10, 17, 21, 19],
......
class EventDispatcher {
constructor() {
this.eventsListeners = {};
}
addListener(event, callback) {
// Check if callback is a function
if (typeof callback !== 'function') {
console.error('Callback must be a function')
return false
}
// Check if event is a string
if (typeof event !== 'string') {
console.error('Event must be a string')
return false
}
// Create event key if it doesn't exist
if (this.eventsListeners[event] === undefined) {
this.eventsListeners[event] = [callback]
} else {
this.eventsListeners[event].push(callback)
}
}
removeListener(event, callback) {
// Check if callback is a function
if (typeof callback !== 'function') {
console.error('Callback must be a function')
return false
}
// Check if event is a string
if (typeof event !== 'string') {
console.error('Event must be a string')
return false
}
const listeners = this.eventsListeners[event]
if (listeners === undefined) {
return
}
const callbackIndex = listeners.indexOf(callback)
if (callbackIndex >= 0) {
listeners.splice(callbackIndex, 1)
}
}
dispatchEvent(event, details) {
// Check if event exists
if (this.eventsListeners[event] === undefined) {
// console.error(`Event ${event} does not exist`)
return false
}
this.eventsListeners[event].forEach(listener => listener(details))
}
}
\ No newline at end of file
window.addEventListener('load', function() {
// Query all input sliders
this.document.querySelectorAll('input[type="range"]').forEach(elem => {
// Get attribute slider name
const sliderName = elem.getAttribute('name')
// find match
const matches = sliderName.match(/^([a-zA-Z_.]+)\.(location|rotation|scale)\.(x|y|z)$/);
if (!matches) {
return
}
const objectName = matches[1];
const propertyName = matches[2];
const axisId = ['x', 'y', 'z'].indexOf(matches[3]);
// Attach input event listener to this slider
elem.addEventListener('input', function(event) {
ObjectNode.cache[objectName].model[propertyName][axisId] = parseFloat(event.target.value);
ObjectNode.cache[objectName].updateTransformations()
let textVal = event.target.parentElement.querySelector('.slider-value')
textVal.innerHTML = parseFloat(event.target.value);
})
})
let speedSlider = document.querySelector('input[name="speed"]')
speedSlider.addEventListener('input', event => {
let textVal = event.target.parentElement.querySelector('.slider-value')
let value = parseFloat(event.target.value);
let multiplier = Math.log(4 / 0.05);
value = 0.05 * Math.exp(multiplier * (value - 0.05) / (4 - 0.05));
textVal.textContent = Math.round(value * 100) / 100;
updateSpeed(value);
})
let multiplier = Math.log(4 / 0.05);
let speedSliderValue = Math.log(speed / 0.05) / multiplier * (4 - 0.05) + 0.05;
speedSlider.value = speedSliderValue;
speedSliderValue = Math.round(speedSliderValue * 100) / 100;
speedSlider.parentElement.querySelector('.slider-value').textContent = speed;
})
\ No newline at end of file
/*
Transpose and inverse matrix function are copyright Gregg Tavares
* Copyright 2014, Gregg Tavares.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Gregg Tavares. nor the names of his
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var m4 = {
// Setup 3x3 transformation matrix object
identity: function () {
......@@ -222,4 +256,127 @@ var m4 = {
scale: function (m, sx, sy, sz) {
return m4.multiply(m, m4.scaling(sx, sy, sz));
},
/**
* Computes the inverse of a matrix.
* @param {Matrix4} m matrix to compute inverse of
* @param {Matrix4} [dst] optional matrix to store result
* @return {Matrix4} dst or a new matrix if none provided
* @memberOf module:webgl-3d-math
*/
inverse: function(m, dst) {
dst = dst || new Float32Array(16);
var m00 = m[0 * 4 + 0];
var m01 = m[0 * 4 + 1];
var m02 = m[0 * 4 + 2];
var m03 = m[0 * 4 + 3];
var m10 = m[1 * 4 + 0];
var m11 = m[1 * 4 + 1];
var m12 = m[1 * 4 + 2];
var m13 = m[1 * 4 + 3];
var m20 = m[2 * 4 + 0];
var m21 = m[2 * 4 + 1];
var m22 = m[2 * 4 + 2];
var m23 = m[2 * 4 + 3];
var m30 = m[3 * 4 + 0];
var m31 = m[3 * 4 + 1];
var m32 = m[3 * 4 + 2];
var m33 = m[3 * 4 + 3];
var tmp_0 = m22 * m33;
var tmp_1 = m32 * m23;
var tmp_2 = m12 * m33;
var tmp_3 = m32 * m13;
var tmp_4 = m12 * m23;
var tmp_5 = m22 * m13;
var tmp_6 = m02 * m33;
var tmp_7 = m32 * m03;
var tmp_8 = m02 * m23;
var tmp_9 = m22 * m03;
var tmp_10 = m02 * m13;
var tmp_11 = m12 * m03;
var tmp_12 = m20 * m31;
var tmp_13 = m30 * m21;
var tmp_14 = m10 * m31;
var tmp_15 = m30 * m11;
var tmp_16 = m10 * m21;
var tmp_17 = m20 * m11;
var tmp_18 = m00 * m31;
var tmp_19 = m30 * m01;
var tmp_20 = m00 * m21;
var tmp_21 = m20 * m01;
var tmp_22 = m00 * m11;
var tmp_23 = m10 * m01;
var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
(tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
(tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
(tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
(tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
dst[0] = d * t0;
dst[1] = d * t1;
dst[2] = d * t2;
dst[3] = d * t3;
dst[4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
(tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
dst[5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
(tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
dst[6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
(tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
dst[7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
(tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
dst[8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
(tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
dst[9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -