I'm trying to get a good animation with constant fps, but nothing works. I'm using threejs, webgl to render the scene and for the animation loop I found two ways (is there a third?), which is either requestAnimationFrame(...) or by setTimeOut(). Both don't guarantee that the fps is constant, but I'm fixing it by updating the object position by the timedelta of window.performance.now(). But I still have lagspikes which one can clearly see. So how can I fix this? It's obviously possibly because there are games like doom which don't lag.
My example with full src-code can be found here:
http://sc2tube.com:8080/test/three.html
the relevant code:
function animate() {
requestAnimationFrame( animate );
// calculate how long the last frame was
var timefix = (window.performance.now() - last)/(1000/30);
last = window.performance.now();
var oldX = object.position.x;
// calculate updateX including the timefix
var updateX = oldX + (10 / 30 * 100) * dx * timefix;
// update the position of the object
object.position.x = updateX;
// render the scene
renderer.render(scene, camera);
}
worker.js:
self.addEventListener('message', function(e) {
setInterval(function(){
now = self.performance.now()
timefix = (now - last)/(1000/100);
last = now;
x += 5*timefix*dx;
self.postMessage(x);
}, 1000/100);
}, false);
var test;
var dx = 1, dy = 0;
var speed = 0.5;
var activeKey = 0;
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
init();
animate();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth - 50,
HEIGHT = 500;
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
camera.position.set(0,0,100);
scene.add(camera);
console.log(WIDTH);
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth - 50,
HEIGHT = window.innerHeight - 50;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
renderer.setClearColor();
var loader = new THREE.ObjectLoader();
loader.parse({
"metadata" : {
"type" : "Object",
"version" : 4.3,
"generator" : "Blender Script"
},
"object" : {
"name" : "red_cube.Material",
"type" : "Mesh",
"uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
"matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
"material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
},
"geometries" : [{
"name" : "red_cube.Material",
"type" : "BufferGeometry",
"uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
"data" : {
"attributes" : {
"position" : {
"type" : "Float32Array",
"itemSize" : 3,
"array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
},
"normal" : {
"type" : "Float32Array",
"itemSize" : 3,
"array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
},
"index" : {
"type" : "Uint32Array",
"itemSize" : 1,
"array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
}
}
}
}],
"materials" : [{
"name" : "Material",
"type" : "MeshBasicMaterial",
"uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
"transparent" : false,
"opacity" : 1,
"color" : 10682379
}]
}, function(object){
test = object;
object.scale.set(50,50,50);
scene.add(object)
});
document.addEventListener('keydown', function(e) {
if (activeKey == e.keyCode) return;
activeKey = e.keyCode;
//left
if (e.keyCode == 37) {
dx = -1;
}
//top
else if (e.keyCode == 38) {
dy = 1;
}
//right
else if (e.keyCode == 39) {
dx = 1;
}
//bottom
else if (e.keyCode == 40) {
dy = -1;
}
});
document.addEventListener('keyup', function(e) {
switch (e.keyCode) {
case 37: // left
case 39: // right
dx = 0;
break;
case 38: // up
case 40: // down
dy = 0;
break;
}
activeKey = 0;
});
}
var start;
var last;
function animate() {
requestAnimationFrame( animate );
if(start == null) {
start = window.performance.now();
last = start;
}
var timefix = (window.performance.now() - last)/(1000/30);
last = window.performance.now();
if(test != null) {
var oldX = test.position.x;
var oldY = test.position.y;
var updateX = oldX + (10 / 30 * 100) * dx * speed * timefix;
var updateY = oldY + (10 / 30 * 100) * dy * speed * timefix;
if(updateX > 1800 ) {
dx = -1;
} else if(updateX < 100) {
dx = 1;
}
test.position.x = updateX;
test.position.y = oldY + (10 / 30 * 100) * dy * speed * timefix;
var text = document.getElementById('panel');
text.innerHTML = timefix;
renderer.render(scene, camera);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">
<div id="panel">TEST
</div>
<br>
</body>
The problem is something called game ticks.
What you need is threads.
one rendering thread.
one game thread.
For the game thread I advice a webworker:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
You let the game thread run every 50ms to update game logic. it should 'sleep" inbetween.
You post stuff back to rendering thread, which updates everything and interprolates the trajectories for currentpos to next pos in 50 ms.
tick 1. 0MS
game thread:
spawn block at 0.0
push gamestate to rendering thread
rendering thread
gather entities.
update entities for location if moving
draw entities at pos
tick 2. 50ms
game thread:
get entities
trigger update functions
red block moves left 20
push gamestate to rendering thread
rendering thread
gather entities.
update entities for location if moving
red block moves from 0 to 20
avg frames per tick 4
interprolated movement per tick, 5 px.
update red to 5
draw entities at pos
edit
Added example code of how to utilise render threads.
Basically you have the same objects in the game thread(webworker) as in the render thread.
Only difference is, render thread has has render instructions(onRender) and game loop has Update instructions(on update)
So they are the same, but also different.
Take a look.
function getInlineJS() {
var js = document.querySelector('[type="javascript/worker"]').textContent;
var blob = new Blob([js], {"type": "text\/plain"});
return URL.createObjectURL(blob);
}
var RedCube = function(id) {
this.cube = null;
this.type = 'redcube';
if(typeof id === undefined) {
this.entityId = generateId();
}
else {
this.entityId = id;
}
this.lastX = 0;
this.x = 0;
}
RedCube.prototype.getType = function() {
return this.type;
}
RedCube.prototype.onUpdate = function() {
this.x += 20;
}
RedCube.prototype.loadCube = function(scene, renderer) {
var that = this;
var loader = new THREE.ObjectLoader();
loader.parse({
"metadata" : {
"type" : "Object",
"version" : 4.3,
"generator" : "Blender Script"
},
"object" : {
"name" : "red_cube.Material",
"type" : "Mesh",
"uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
"matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
"geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
"material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
},
"geometries" : [{
"name" : "red_cube.Material",
"type" : "BufferGeometry",
"uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
"data" : {
"attributes" : {
"position" : {
"type" : "Float32Array",
"itemSize" : 3,
"array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
},
"normal" : {
"type" : "Float32Array",
"itemSize" : 3,
"array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
},
"index" : {
"type" : "Uint32Array",
"itemSize" : 1,
"array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
}
}
}
}],
"materials" : [{
"name" : "Material",
"type" : "MeshBasicMaterial",
"uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
"transparent" : false,
"opacity" : 1,
"color" : 10682379
}]
}, function(object){
that.cube = object;
object.scale.set(50,50,50);
scene.add(object)
});
}
RedCube.prototype.onRender = function(scene, renderer) {
if(this.cube === null) {
this.loadCube(scene, renderer);
}
// Some interprolation logic here to move from lastpos to next pos in average frames
// per tick.
this.cube.position.x = this.x;
}
RedCube.prototype.getType = function() {
return type;
}
RedCube.prototype.generateSyncPacket = function() {
return {
type: this.getType(),
x : this.x
};
}
RedCube.prototype.parseSyncPacket = function(syncpacket) {
this.setPosition(syncpacket.x);
}
RedCube.prototype.generateId = function() {
var d = new Date().getTime();
if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
d += performance.now(); //use high-precision timer if available
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
RedCube.prototype.getEntityId = function() {
return this.entityId;
}
RedCube.prototype.beforeDeath = function() {
}
RedCube.prototype.die = function() {
}
RedCube.prototype.setPosition = function(newpos) {
this.lastX = this.x;
this.x = newpos;
}
RedCube.prototype.getPosition = function() {
return this.x;
}
RedCube.prototype.getLastX = function() {
return this.lastX;
}
var EntityRegistry = function() {
this.entities = {};
this.types = {};
}
EntityRegistry.prototype.register = function(entity) {
this.entities[entity.getEntityId()] = entity;
}
EntityRegistry.prototype.callUpdate = function() {
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].onUpdate();
}
}
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].onRender(scene, renderer);
}
}
}
EntityRegistry.prototype.remove = function(entity) {
entity.beforeDeath();
delete this.entities[entity.getEntityId()]
entity.die();
}
EntityRegistry.prototype.registerType = function(name, entityClass) {
this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(syncpacket, entityId) {
var entity = new this.types[syncpacket.type](entityId);
entity.parseSyncPacket(syncpacket);
this.register(entity);
}
EntityRegistry.prototype.getSyncData = function() {
var syncpacket = {};
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
}
}
return syncpacket;
}
EntityRegistry.prototype.parseSyncData = function(syncpacket) {
for(entityId in syncpacket) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].parseSyncPacket(syncpacket[entityId]);
}
else {
this.startEntity(syncpacket[entityId], entityId);
}
}
return syncpacket;
}
var REGISTRY = new EntityRegistry();
REGISTRY.registerType('redcube', RedCube);
var test = "d";
var dx = 1, dy = 0;
var speed = 0.5;
var activeKey = 0;
// Set up the scene, camera, and renderer as global variables.
var scene, camera, renderer;
var worker = new Worker(getInlineJS());
worker.postMessage("dasd");
worker.addEventListener('message', function(e) {
REGISTRY.parseSyncData(e.data);
}, false);
console.log("asd " + test);
init();
animate();
// Sets up the scene.
function init() {
// Create the scene and set the scene size.
scene = new THREE.Scene();
var WIDTH = window.innerWidth - 50,
HEIGHT = 500;
// Create a renderer and add it to the DOM.
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(WIDTH, HEIGHT);
document.body.appendChild(renderer.domElement);
camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
camera.position.set(0,0,100);
scene.add(camera);
console.log(WIDTH);
window.addEventListener('resize', function() {
var WIDTH = window.innerWidth - 50,
HEIGHT = window.innerHeight - 50;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
renderer.setClearColor();
document.addEventListener('keydown', function(e) {
if (activeKey == e.keyCode) return;
activeKey = e.keyCode;
//left
if (e.keyCode == 37) {
dx = -1;
}
//top
else if (e.keyCode == 38) {
dy = 1;
}
//right
else if (e.keyCode == 39) {
dx = 1;
}
//bottom
else if (e.keyCode == 40) {
dy = -1;
}
});
document.addEventListener('keyup', function(e) {
switch (e.keyCode) {
case 37: // left
case 39: // right
dx = 0;
break;
case 38: // up
case 40: // down
dy = 0;
break;
}
activeKey = 0;
});
}
var start;
var last;
var timefix, oldX,oldY, updateX,updateY,text;
function animate() {
REGISTRY.callOnRender(scene, renderer);
renderer.render(scene, camera);
requestAnimationFrame( animate );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">
<div id="panel">TEST
</div>
<br>
<script type="javascript/worker">
var RedCube = function(id) {
this.direction = false;
this.type = 'redcube';
if(typeof id === 'undefined') {
this.entityId = this.generateId();
}
else {
this.entityId = id;
}
this.lastX = 0;
this.x = 0;
}
RedCube.prototype.getType = function() {
return this.type;
}
RedCube.prototype.generateSyncPacket = function() {
return {
type: this.getType(),
x : this.x
};
}
RedCube.prototype.parseSyncPacket = function(syncpacket) {
this.setPosition(syncpacket.x);
}
RedCube.prototype.generateId = function() {
var d = new Date().getTime();
if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
d += performance.now(); //use high-precision timer if available
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
RedCube.prototype.getEntityId = function() {
return this.entityId;
}
RedCube.prototype.beforeDeath = function() {
}
RedCube.prototype.die = function() {
}
RedCube.prototype.setPosition = function(newpos) {
this.lastX = this.x;
this.x = newpos;
}
RedCube.prototype.getPosition = function() {
return this.x;
}
RedCube.prototype.getLastX = function() {
return this.lastX;
}
var EntityRegistry = function() {
this.entities = {};
this.types = {};
}
RedCube.prototype.onUpdate = function() {
if(this.x > 500) {
this.direction = true;
}
if(this.x <= 0) {
this.direction = false;
}
this.x += !this.direction ? 20 : -20;
}
RedCube.prototype.onRender = function(scene, renderer) {
/// this is not a rendering thread. leave it empty
}
EntityRegistry.prototype.register = function(entity) {
this.entities[entity.getEntityId()] = entity;
}
EntityRegistry.prototype.remove = function(entity) {
entity.beforeDeath();
delete this.entities[entity.getEntityId()]
entity.die();
}
EntityRegistry.prototype.registerType = function(name, entityClass) {
this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(entityId, syncpacket) {
var entity = this.types[syncpacket.type](entityId);
entity.parseSyncPacket(syncpacket);
this.register(entity);
}
EntityRegistry.prototype.getSyncData = function() {
var syncpacket = {};
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
}
}
return syncpacket;
}
EntityRegistry.prototype.callUpdate = function() {
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].onUpdate();
}
}
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
for(entityId in this.entities) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].onRender(scene, renderer);
}
}
}
EntityRegistry.prototype.parseSyncData = function(syncpacket) {
for(entityId in syncpacket) {
if(this.entities.hasOwnProperty(entityId)) {
this.entities[entityId].parseSyncPacket(syncpacket[entityid]);
}
else {
this.startEntity(syncpacket, entityId);
}
}
return syncpacket;
}
var REGISTRY = new EntityRegistry();
var little_red = new RedCube();
REGISTRY.register(little_red);
var x = 0;
var timefix = 0;
var last = 0;
var dx = 1;
var loopInterval = 0;
loopInterval = setInterval(function(){
REGISTRY.callUpdate()
var msg = REGISTRY.getSyncData();
self.postMessage(msg);
}, 1000/60);
self.addEventListener('message', function(e) {
}, false);
</script>
</body>
Related
I am making a slight Tetris remake and I wanted to add a leveling system for when my score reaches, for example, 100, the speed that the blocks go down will also increase. How would I go about doing this? I have the entirety of the javascript code right here so please let me know what I can do to fix it:
//-------------------------------------------------------------------------
// base helper methods
//-------------------------------------------------------------------------
function get(id) { return document.getElementById(id); }
function hide(id) { get(id).style.visibility = 'hidden'; }
function show(id) { get(id).style.visibility = null; }
function html(id, html) { get(id).innerHTML = html; }
function timestamp() { return new Date().getTime(); }
function random(min, max) { return (min + (Math.random() * (max - min))); }
function randomChoice(choices) { return choices[Math.round(random(0, choices.length-1))]; }
if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
window.setTimeout(callback, 1000 / 60);
}
}
//-------------------------------------------------------------------------
// game constants
//-------------------------------------------------------------------------
var KEY = { ESC: 27, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 },
DIR = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3, MIN: 0, MAX: 3 },
stats = new Stats(),
canvas = get('canvas'),
ctx = canvas.getContext('2d'),
ucanvas = get('upcoming'),
uctx = ucanvas.getContext('2d'),
speed = { start: 0.6, decrement: 0.005, min: 0.1 }, // how long before piece drops by 1 row (seconds)
nx = 10, // width of tetris court (in blocks)
ny = 20, // height of tetris court (in blocks)
nu = 5; // width/height of upcoming preview (in blocks)
//-------------------------------------------------------------------------
// game variables (initialized during reset)
//-------------------------------------------------------------------------
var dx, dy, // pixel size of a single tetris block
blocks, // 2 dimensional array (nx*ny) representing tetris court - either empty block or occupied by a 'piece'
actions, // queue of user actions (inputs)
playing, // true|false - game is in progress
dt, // time since starting this game
current, // the current piece
next, // the next piece
score, // the current score
vscore, // the currently displayed score (it catches up to score in small chunks - like a spinning slot machine)
rows, // number of completed rows in the current game
step; // how long before current piece drops by 1 row
//-------------------------------------------------------------------------
// tetris pieces
//
// blocks: each element represents a rotation of the piece (0, 90, 180, 270)
// each element is a 16 bit integer where the 16 bits represent
// a 4x4 set of blocks, e.g. j.blocks[0] = 0x44C0
//
// 0100 = 0x4 << 3 = 0x4000
// 0100 = 0x4 << 2 = 0x0400
// 1100 = 0xC << 1 = 0x00C0
// 0000 = 0x0 << 0 = 0x0000
// ------
// 0x44C0
//
//-------------------------------------------------------------------------
var i = { size: 4, blocks: [0x0F00, 0x2222, 0x00F0, 0x4444], color: 'cyan' };
var j = { size: 3, blocks: [0x44C0, 0x8E00, 0x6440, 0x0E20], color: 'blue' };
var l = { size: 3, blocks: [0x4460, 0x0E80, 0xC440, 0x2E00], color: 'orange' };
var o = { size: 2, blocks: [0xCC00, 0xCC00, 0xCC00, 0xCC00], color: 'yellow' };
var s = { size: 3, blocks: [0x06C0, 0x8C40, 0x6C00, 0x4620], color: 'lime' };
var t = { size: 3, blocks: [0x0E40, 0x4C40, 0x4E00, 0x4640], color: 'purple' };
var z = { size: 3, blocks: [0x0C60, 0x4C80, 0xC600, 0x2640], color: 'red' };
var p = { size: 3, blocks: [0x0F00, 0x2222, 0x00F0,], color: 'maroon' };
//------------------------------------------------
// do the bit manipulation and iterate through each
// occupied block (x,y) for a given piece
//------------------------------------------------
function eachblock(type, x, y, dir, fn) {
var bit, result, row = 0, col = 0, blocks = type.blocks[dir];
for(bit = 0x8000 ; bit > 0 ; bit = bit >> 1) {
if (blocks & bit) {
fn(x + col, y + row);
}
if (++col === 4) {
col = 0;
++row;
}
}
}
//-----------------------------------------------------
// check if a piece can fit into a position in the grid
//-----------------------------------------------------
function occupied(type, x, y, dir) {
var result = false
eachblock(type, x, y, dir, function(x, y) {
if ((x < 0) || (x >= nx) || (y < 0) || (y >= ny) || getBlock(x,y))
result = true;
});
return result;
}
function unoccupied(type, x, y, dir) {
return !occupied(type, x, y, dir);
}
//-----------------------------------------
// start with 4 instances of each piece and
// pick randomly until the 'bag is empty'
//-----------------------------------------
var pieces = [];
function randomPiece() {
if (pieces.length == 0)
pieces = [i,i,i,i,j,j,j,j,l,l,l,l,o,o,o,o,s,s,s,s,t,t,t,t,z,z,z,z,p,p,p,p];
var type = pieces.splice(random(0, pieces.length-1), 1)[0];
return { type: type, dir: DIR.UP, x: Math.round(random(0, nx - type.size)), y: 0 };
}
//-------------------------------------------------------------------------
// GAME LOOP
//-------------------------------------------------------------------------
function run() {
showStats(); // initialize FPS counter
addEvents(); // attach keydown and resize events
var last = now = timestamp();
function frame() {
now = timestamp();
update(Math.min(1, (now - last) / 1000.0)); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
draw();
stats.update();
last = now;
requestAnimationFrame(frame, canvas);
}
resize(); // setup all our sizing information
reset(); // reset the per-game variables
frame(); // start the first frame
}
function showStats() {
stats.domElement.id = 'stats';
get('menu').appendChild(stats.domElement);
}
function addEvents() {
document.addEventListener('keydown', keydown, false);
window.addEventListener('resize', resize, false);
}
function resize(event) {
canvas.width = canvas.clientWidth; // set canvas logical size equal to its physical size
canvas.height = canvas.clientHeight; // (ditto)
ucanvas.width = ucanvas.clientWidth;
ucanvas.height = ucanvas.clientHeight;
dx = canvas.width / nx; // pixel size of a single tetris block
dy = canvas.height / ny; // (ditto)
invalidate();
invalidateNext();
}
function keydown(ev) {
var handled = false;
if (playing) {
switch(ev.keyCode) {
case KEY.LEFT: actions.push(DIR.LEFT); handled = true; break;
case KEY.RIGHT: actions.push(DIR.RIGHT); handled = true; break;
case KEY.UP: actions.push(DIR.UP); handled = true; break;
case KEY.DOWN: actions.push(DIR.DOWN); handled = true; break;
case KEY.ESC: lose(); handled = true; break;
}
}
else if (ev.keyCode == KEY.SPACE) {
play();
handled = true;
}
if (handled)
ev.preventDefault(); // prevent arrow keys from scrolling the page (supported in IE9+ and all other browsers)
}
//-------------------------------------------------------------------------
// GAME LOGIC
//-------------------------------------------------------------------------
function play() { hide('start'); reset(); playing = true; }
function lose() { show('start'); setVisualScore(); playing = false; }
function setVisualScore(n) { vscore = n || score; invalidateScore(); }
function setScore(n) { score = n; setVisualScore(n); }
function addScore(n) { score = score + n; }
function clearScore() { setScore(0); }
function clearRows() { setRows(0); }
function setRows(n) { rows = n; step = Math.max(speed.min, speed.start - (speed.decrement*rows)); invalidateRows(); }
function addRows(n) { setRows(rows + n); }
function getBlock(x,y) { return (blocks && blocks[x] ? blocks[x][y] : null); }
function setBlock(x,y,type) { blocks[x] = blocks[x] || []; blocks[x][y] = type; invalidate(); }
function clearBlocks() { blocks = []; invalidate(); }
function clearActions() { actions = []; }
function setCurrentPiece(piece) { current = piece || randomPiece(); invalidate(); }
function setNextPiece(piece) { next = piece || randomPiece(); invalidateNext(); }
function reset() {
dt = 0;
clearActions();
clearBlocks();
clearRows();
clearScore();
setCurrentPiece(next);
setNextPiece();
}
function update(idt) {
if (playing) {
if (vscore < score)
setVisualScore(vscore + 1);
handle(actions.shift());
dt = dt + idt;
if (dt > step) {
dt = dt - step;
drop();
}
}
}
function handle(action) {
switch(action) {
case DIR.LEFT: move(DIR.LEFT); break;
case DIR.RIGHT: move(DIR.RIGHT); break;
case DIR.UP: rotate(); break;
case DIR.DOWN: drop(); break;
}
}
function move(dir) {
var x = current.x, y = current.y;
switch(dir) {
case DIR.RIGHT: x = x + 1; break;
case DIR.LEFT: x = x - 1; break;
case DIR.DOWN: y = y + 1; break;
}
if (unoccupied(current.type, x, y, current.dir)) {
current.x = x;
current.y = y;
invalidate();
return true;
}
else {
return false;
}
}
function rotate() {
var newdir = (current.dir == DIR.MAX ? DIR.MIN : current.dir + 1);
if (unoccupied(current.type, current.x, current.y, newdir)) {
current.dir = newdir;
invalidate();
}
}
//This is how we make the piece drop down and place:
function drop() {
if (!move(DIR.DOWN)) {
addScore(10);
dropPiece();
removeLines();
setCurrentPiece(next);
setNextPiece(randomPiece());
clearActions();
if (occupied(current.type, current.x, current.y, current.dir)) {
lose();
}
}
}
function dropPiece() {
eachblock(current.type, current.x, current.y, current.dir, function(x, y) {
setBlock(x, y, current.type);
});
}
function removeLines() {
var x, y, complete, n = 0;
for(y = ny ; y > 0 ; --y) {
complete = true;
for(x = 0 ; x < nx ; ++x) {
if (!getBlock(x, y))
complete = false;
}
if (complete) {
removeLine(y);
y = y + 1; // recheck same line
n++;
}
}
if (n > 0) {
addRows(n);
addScore(100*Math.pow(2,n-1)); // 1: 100, 2: 200, 3: 400, 4: 800
}
}
function removeLine(n) {
var x, y;
for(y = n ; y >= 0 ; --y) {
for(x = 0 ; x < nx ; ++x)
setBlock(x, y, (y == 0) ? null : getBlock(x, y-1));
}
}
//-------------------------------------------------------------------------
// RENDERING
//-------------------------------------------------------------------------
var invalid = {};
function invalidate() { invalid.court = true; }
function invalidateNext() { invalid.next = true; }
function invalidateScore() { invalid.score = true; }
function invalidateRows() { invalid.rows = true; }
function draw() {
ctx.save();
ctx.lineWidth = 1;
ctx.translate(0.5, 0.5); // for crisp 1px black lines
drawCourt();
drawNext();
drawScore();
drawRows();
ctx.restore();
}
function drawCourt() {
if (invalid.court) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (playing)
drawPiece(ctx, current.type, current.x, current.y, current.dir);
var x, y, block;
for(y = 0 ; y < ny ; y++) {
for (x = 0 ; x < nx ; x++) {
if (block = getBlock(x,y))
drawBlock(ctx, x, y, block.color);
}
}
ctx.strokeRect(0, 0, nx*dx - 1, ny*dy - 1); // court boundary
invalid.court = false;
}
}
function drawNext() {
if (invalid.next) {
var padding = (nu - next.type.size) / 2; // half-complete attempt at centering next piece display
uctx.save();
uctx.translate(0.5, 0.5);
uctx.clearRect(0, 0, nu*dx, nu*dy);
drawPiece(uctx, next.type, padding, padding, next.dir);
uctx.strokeStyle = 'black';
uctx.strokeRect(0, 0, nu*dx - 1, nu*dy - 1);
uctx.restore();
invalid.next = false;
}
}
function drawScore() {
if (invalid.score) {
html('score', ("00000" + Math.floor(vscore)).slice(-5));
invalid.score = false;
}
}
function drawRows() {
if (invalid.rows) {
html('rows', rows);
invalid.rows = false;
}
}
function drawPiece(ctx, type, x, y, dir) {
eachblock(type, x, y, dir, function(x, y) {
drawBlock(ctx, x, y, type.color);
});
}
function drawBlock(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x*dx, y*dy, dx, dy);
ctx.strokeRect(x*dx, y*dy, dx, dy)
}
//-------------------------------------------------------------------------
// FINALLY, lets run the game
//-------------------------------------------------------------------------
run();
You have a function called addScore that seems to be used to update the user's score. You could add a conditional block inside this function that will update the game speed when the new score is above the threshold you choose. Alternatively, if you would prefer to keep addScore purely focused on updating the value of the score, you could add this conditional block wherever you're calling addScore.
I am using slick carousel slider which has 5 slides, each slide should trigger an animation after it gets displayed on the page.
An animator has created an animation using Adobe Animate CC and exported the animation in createjs format so it is compatible with html5.
If I try to call the animation on afterchange I get an error:
TypeError: lib.FlowerGrowTemplate is not a constructor
See the comment below for where the error is happening:
/*The error is pointing to here*/
Adobe Animate CC generated javascript code:
(function (cjs, an) {
var p; // shortcut to reference prototypes
var lib={};var ss={};var img={};
lib.ssMetadata = [];
// symbols:
// helper functions:
function mc_symbol_clone() {
var clone = this._cloneProps(new this.constructor(this.mode, this.startPosition, this.loop));
clone.gotoAndStop(this.currentFrame);
clone.paused = this.paused;
clone.framerate = this.framerate;
return clone;
}
function getMCSymbolPrototype(symbol, nominalBounds, frameBounds) {
var prototype = cjs.extend(symbol, cjs.MovieClip);
prototype.clone = mc_symbol_clone;
prototype.nominalBounds = nominalBounds;
prototype.frameBounds = frameBounds;
return prototype;
}
.....
})(createjs = createjs||{}, AdobeAn = AdobeAn||{});
var createjs, AdobeAn;
var graphic_canvas_1, graphic_stage_1, graphic_exportRoot_1, graphic_container_1, graphic_dom_overlay_container_1, graphic_fnStartAnimation_1;
function graphic_init_1() {
graphic_canvas_1 = document.getElementById("graphic_canvas");
graphic_container_1 = document.getElementById("graphic_container");
graphic_dom_overlay_container_1 = document.getElementById("graphic_dom_overlay_container");
var comp=AdobeAn.getComposition("AD6FD640782B2A4DAD43D647860AC61B");
var lib=comp.getLibrary();
graphic_handleComplete_1({},comp);
}
function graphic_handleComplete_1(evt,comp) {
//This function is always called, irrespective of the content. You can use the variable "stage" after it is created in token create_stage.
var lib=comp.getLibrary();
var ss=comp.getSpriteSheet();
graphic_exportRoot_1 = new lib.FlowerGrowTemplate(); /*The error is pointing to here*/
graphic_stage_1 = new lib.Stage(graphic_canvas_1);
//Registers the "tick" event listener.
graphic_fnStartAnimation_1 = function() {
graphic_stage_1.addChild(graphic_exportRoot_1);
createjs.Ticker.setFPS(lib.properties.fps);
createjs.Ticker.addEventListener("tick", graphic_stage_1)
graphic_stage_1.addEventListener("tick", graphic_handleTick_1)
function graphic_getProjectionMatrix_1(container, totalDepth) {
var focalLength = 528.25;
var projectionCenter = { x : lib.properties.width/2, y : lib.properties.height/2 };
var scale = (totalDepth + focalLength)/focalLength;
var scaleMat = new createjs.Matrix2D;
scaleMat.a = 1/scale;
scaleMat.d = 1/scale;
var projMat = new createjs.Matrix2D;
projMat.tx = -projectionCenter.x;
projMat.ty = -projectionCenter.y;
projMat = projMat.prependMatrix(scaleMat);
projMat.tx += projectionCenter.x;
projMat.ty += projectionCenter.y;
return projMat;
}
function graphic_handleTick_1(event) {
var cameraInstance = graphic_exportRoot_1.___camera___instance;
if(cameraInstance !== undefined && cameraInstance.pinToObject !== undefined)
{
cameraInstance.x = cameraInstance.pinToObject.x + cameraInstance.pinToObject.pinOffsetX;
cameraInstance.y = cameraInstance.pinToObject.y + cameraInstance.pinToObject.pinOffsetY;
if(cameraInstance.pinToObject.parent !== undefined && cameraInstance.pinToObject.parent.depth !== undefined)
cameraInstance.depth = cameraInstance.pinToObject.parent.depth + cameraInstance.pinToObject.pinOffsetZ;
}
graphic_applyLayerZDepth_1(graphic_exportRoot_1);
}
function graphic_applyLayerZDepth_1(parent)
{
var cameraInstance = parent.___camera___instance;
var focalLength = 528.25;
var projectionCenter = { 'x' : 0, 'y' : 0};
if(parent === graphic_exportRoot_1)
{
var stageCenter = { 'x' : lib.properties.width/2, 'y' : lib.properties.height/2 };
projectionCenter.x = stageCenter.x;
projectionCenter.y = stageCenter.y;
}
for(child in parent.children)
{
var layerObj = parent.children[child];
if(layerObj == cameraInstance)
continue;
graphic_applyLayerZDepth_1(layerObj, cameraInstance);
if(layerObj.layerDepth === undefined)
continue;
if(layerObj.currentFrame != layerObj.parent.currentFrame)
{
layerObj.gotoAndPlay(layerObj.parent.currentFrame);
}
var matToApply = new createjs.Matrix2D;
var cameraMat = new createjs.Matrix2D;
var totalDepth = layerObj.layerDepth ? layerObj.layerDepth : 0;
var cameraDepth = 0;
if(cameraInstance && !layerObj.isAttachedToCamera)
{
var mat = cameraInstance.getMatrix();
mat.tx -= projectionCenter.x;
mat.ty -= projectionCenter.y;
cameraMat = mat.invert();
cameraMat.prependTransform(projectionCenter.x, projectionCenter.y, 1, 1, 0, 0, 0, 0, 0);
cameraMat.appendTransform(-projectionCenter.x, -projectionCenter.y, 1, 1, 0, 0, 0, 0, 0);
if(cameraInstance.depth)
cameraDepth = cameraInstance.depth;
}
if(layerObj.depth)
{
totalDepth = layerObj.depth;
}
//Offset by camera depth
totalDepth -= cameraDepth;
if(totalDepth < -focalLength)
{
matToApply.a = 0;
matToApply.d = 0;
}
else
{
if(layerObj.layerDepth)
{
var sizeLockedMat = graphic_getProjectionMatrix_1(parent, layerObj.layerDepth);
if(sizeLockedMat)
{
sizeLockedMat.invert();
matToApply.prependMatrix(sizeLockedMat);
}
}
matToApply.prependMatrix(cameraMat);
var projMat = graphic_getProjectionMatrix_1(parent, totalDepth);
if(projMat)
{
matToApply.prependMatrix(projMat);
}
}
layerObj.transformMatrix = matToApply;
}
}
}
//Code to support hidpi screens and responsive scaling.
function graphic_makeResponsive_1(isResp, respDim, isScale, scaleType) {
var lastW, lastH, lastS=1;
window.addEventListener('resize', graphic_resizeCanvas_1);
graphic_resizeCanvas_1();
function graphic_resizeCanvas_1() {
var w = lib.properties.width, h = lib.properties.height;
var iw = window.innerWidth, ih=window.innerHeight;
var pRatio = window.devicePixelRatio || 1, xRatio=iw/w, yRatio=ih/h, sRatio=1;
if(isResp) {
if((respDim=='width'&&lastW==iw) || (respDim=='height'&&lastH==ih)) {
sRatio = lastS;
}
else if(!isScale) {
if(iw<w || ih<h)
sRatio = Math.min(xRatio, yRatio);
}
else if(scaleType==1) {
sRatio = Math.min(xRatio, yRatio);
}
else if(scaleType==2) {
sRatio = Math.max(xRatio, yRatio);
}
}
graphic_canvas_1.width = w*pRatio*sRatio;
graphic_canvas_1.height = h*pRatio*sRatio;
graphic_canvas_1.style.width = graphic_dom_overlay_container_1.style.width = graphic_container_1.style.width = w*sRatio+'px';
graphic_canvas_1.style.height = graphic_container_1.style.height = graphic_dom_overlay_container_1.style.height = h*sRatio+'px';
graphic_stage_1.scaleX = pRatio*sRatio;
graphic_stage_1.scaleY = pRatio*sRatio;
lastW = iw; lastH = ih; lastS = sRatio;
graphic_stage_1.tickOnUpdate = false;
graphic_stage_1.update();
graphic_stage_1.tickOnUpdate = true;
}
}
graphic_makeResponsive_1(false,'both',false,1);
AdobeAn.compositionLoaded(lib.properties.id);
graphic_fnStartAnimation_1();
}
My javascript code:
(function($){
$('.custom-graphic-slider .slider').slick({
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
dots: true,
fade: true,
});
$('.custom-graphic-slider .slider').on('afterChange', function(event, slick, currentSlide){
graphic_init_1();
});
})(jQuery);
I hope everything makes sense
I'm trying to display a texture onto a cylinder object in THREE.js, and I want to texture to show up on the cylinder in a well... not so distorted way, here's how it looks now:
and that is just based off this simple texture:
I don't want it to be distorted / stretched along the edges of the cylinder, I would like it to look something like the "generated" texture coordinate in blender cycles:
(notice the "box" setting in the image texture)
SOOO I have no idea how to set the texture coordinates in THREE.js ... ?
EDIT::: There's been a request for the actual source code, so here it is (even though it's complicated):
var COBY = new (function() {
this.p = Processing;
this.t=THREE;
this.mouseX = 0;
this.mouseY = 0;
this.Keyboard = new (function() {
this.keysDown = [];
for(var i = 0; i < 200; i++) {
this.keysDown[i] = false;
}
this.RIGHT = 39;
this.LEFT = 37;
this.UP = 38;
this.DOWN = 40;
this.setKeyDown = function(key) {
COBY.Keyboard.keysDown[key] = true;
};
this.setKeyUp = function(key) {
COBY.Keyboard.keysDown[key] = false;
};
this.isKeyDown = function(key) {
return COBY.Keyboard.keysDown[key];
};
});
document.body.onkeydown = function(e) {
COBY.Keyboard.setKeyDown(e.keyCode);
};
document.body.onkeyup = function(e) {
COBY.Keyboard.setKeyUp(e.keyCode);
};
document.body.onmousemove = function(e) {
COBY.mouseX = e.x;
COBY.mouseY = e.y;
};
this.shapes = {
cube:new THREE.BoxGeometry(1,1,1),
cylinder: new THREE.CylinderGeometry(0.5, 0.5, 1),
plane: new THREE.PlaneBufferGeometry(1,1),
};
this.textures = {
white:new THREE.MeshLambertMaterial({color:"white"}),
red:new THREE.MeshLambertMaterial({color:"red"}),
purple:new THREE.MeshLambertMaterial({color:"purple"}),
blue:new THREE.MeshLambertMaterial({color:"blue"}),
pink:new THREE.MeshLambertMaterial({color:"pink"}),
gray:new THREE.MeshLambertMaterial({color:"gray"}),
yellow:new THREE.MeshLambertMaterial({color:"yellow"}),
green:new THREE.MeshLambertMaterial({color:"green"})
};
this.loadTexture = function(name, path) {
COBY.textures[name] = new THREE.MeshBasicMaterial({
map: THREE.ImageUtils.loadTexture(path)
});
console.log(COBY.textures);
};
this.GUIContainer = function(obj) {
this.div = document.createElement("div");
this.div.style.position="absolute";
this.div.style.left=(obj.x || 0) + "px";
this.div.style.top=(obj.y || 0) + "px";
this.div.style.background=obj.color || "white";
this.div.style.width = (obj.width || 100) + "px";
this.div.style.height = (obj.height || 100) + "px";
this.div.style.display = obj.invisible ? "none" : "block";
var that = this;
this.add = function(g) {
that.div.appendChild(g.div);
};
if(document.body) {
document.body.appendChild(this.div);
}
};
this.GUIObject = function(obj) {
this.div = document.createElement("div");
this.div.style.position="absolute";
this.div.style.left=(obj.x || 0) + "px";
this.div.style.top=(obj.y || 0) + "px";
this.div.style.background=obj.color || "white";
this.div.style.width = (obj.width || 100) + "px";
this.div.style.height = (obj.height || 100) + "px";
if(obj.text) {
this.div.innerHTML = "<p class='gTxt'>" + obj.text + "</p>";
}
if(obj.type === "button") {
this.div.className = "btn";
}
};
this.Cobject = function(obj,world) {
this.width=obj.width||1;
this.height=obj.height||1;
this.depth=obj.depth||1;
this.position = new THREE.Vector3();
this.rotation = {x:0,y:0,z:0};
this.forReference = obj.forReference;
if(obj.position) {
this.position.x = obj.position.x || 0;
this.position.y = obj.position.y || 0;
this.position.z = obj.position.z || 0;
}
if(obj.rotation) {
this.rotation.x = obj.rotation.x || 0;
this.rotation.y = obj.rotation.y || 0;
this.rotation.z = obj.rotation.z || 0;
}
this.worldParent = world || false;
this.followMouse = obj.followMouse || false;
this.update=obj.update||function(c){};
this.start = obj.start || function(){};
this.texture = COBY.textures[obj.texture];
//this.texture.wrapS = this.texture.wrapT = THREE.MirroredRepeatWrapping;
this.mesh=new THREE.Mesh(COBY.shapes[obj.shape],this.texture);
var self = this;
this.updateTexture = function(path) {
self.mesh.material.map.image.src = path;
self.mesh.material.needsUpdate = true;
}
this.superUpdate = function(cob) {
self.mesh.scale.set(self.width,self.height,self.depth);
self.mesh.position.copy(self.position);
self.mesh.rotation.set(self.rotation.x,self.rotation.y,self.rotation.z);
self.update(cob);
};
};
function webglAvailable() {
try {
var canvas = document.createElement( 'canvas' );
return !!( window.WebGLRenderingContext && (
canvas.getContext( 'webgl' ) ||
canvas.getContext( 'experimental-webgl' ) )
);
} catch ( e ) {
return false;
}
}
this.World=function (obj) {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.loadTextures = function(texts) {
for(var i = 0; i < texts.length; i++) {
COBY.loadTexture(texts[i].name, texts[i].path);
}
};
if(obj) {
this.width=obj.width || window.innerWidth;
this.height=obj.height || window.innerHeight;
if(obj.textures) {
this.loadTextures(obj.textures);
}
}
this.scene=new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( 75, this.width / this.height, 0.1, 10000 );
this.camera.name = "camera";
//if ( webglAvailable() ) {
this.renderer = new THREE.WebGLRenderer({alpha:true});
// } else {
// this.renderer = new THREE.CanvasRenderer();
// }
this.renderer.setSize(this.width,this.height);
this.renderer.setClearColor(0x000000, 0);
this.renderer.domElement.style.position="absolute";
this.renderer.domElement.style.left="0";
this.renderer.domElement.style.top="0";
this.camera.position.z=5;
this.cobs = [];
this.meshes=[];
var that=this;
this.light = function(x,y,z,inten) {
var directionalLight1=new THREE.DirectionalLight(0xffffff);
directionalLight1.position.set(x,y,z,inten || 1);
directionalLight1.name == "light";
that.scene.add(directionalLight1);
};
this.lights = function() {
var ambientLight = new THREE.AmbientLight( 0x606060 );
that.scene.add( ambientLight );
var directionalLight = new THREE.DirectionalLight( 0xffffff );
directionalLight.position.set( 1, 0.75, -0.5 ).normalize();
that.scene.add( directionalLight );
};
this.lights();
this.add=function(obj){
if(!obj.forReference) {
if(obj.mesh) {
that.scene.add(obj.mesh);
that.meshes.push(obj.mesh);
that.cobs.push(obj);
} else {
that.scene.add(obj);
}
}
obj.start(obj);
};
this.cob = function(c) {
that.add(new COBY.Cobject(c,that));
};
this.addCobjects = function(cobs) {
for(var i = 0; i < cobs.length; i++) {
that.add(new COBY.Cobject(cobs[i],that));
}
};
this.empty = function() {
that.meshes = [];
that.cobs = [];
for(var i = that.scene.children.length - 1; i >= 0; i--) {
that.scene.remove(that.scene.children[i]);
}
that.scene.add(that.camera);
that.lights();
console.log("just actually emptied..feels good");
};
this.update=function (){
// noprotect
for(var i=0;i<that.cobs.length;i++) {
that.cobs[i].superUpdate(that.cobs[i]);
}
that.loop();
that.renderer.render(that.scene, that.camera);
};
this.loop=function(){
};
this.canvas=this.renderer.domElement;
this.start = function(div) {
if(!div)
document.body.appendChild(this.canvas);
else
div.appendChild(this.canvas);
function sketchProc(proc) {
// noprotect
proc.draw=function(){
that.update();
};
}
var procInst=new Processing(document.createElement("canvas"),sketchProc);
};
};
})();
Both THREE.CylinderGeometry() and THREE.CylinderBufferGeometry() work as expected:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.setScalar(10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
//var cylGeom = new THREE.CylinderGeometry(5, 5, 1, 8);
var cylGeom = new THREE.CylinderBufferGeometry(5, 5, 1, 8);
var texLoader = new THREE.TextureLoader();
var texEnd = texLoader.load("https://threejs.org/examples/textures/UV_Grid_Sm.jpg");
texEnd.wrapS = THREE.RepeatWrapping;
texEnd.wrapT = THREE.RepeatWrapping;
texEnd.repeat.set(1, 1);
var texSide = texLoader.load("https://threejs.org/examples/textures/UV_Grid_Sm.jpg");
texSide.wrapS = THREE.RepeatWrapping;
texSide.wrapT = THREE.RepeatWrapping;
texSide.repeat.set(1, 1 / (cylGeom.parameters.radiusTop * cylGeom.parameters.radiusBottom));
var cylMatEnd = new THREE.MeshBasicMaterial({
map: texEnd
});
var cylMatSide = new THREE.MeshBasicMaterial({
map: texSide
});
var cyl = new THREE.Mesh(cylGeom, [cylMatSide, cylMatEnd, cylMatEnd]);
scene.add(cyl);
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
I am making a game where it's about clicking on the nearest yellow dot from the green dot.
I got a list named dots.
You can check out my codepen to see the code I'm using.
My problem is that when you're playing the game, sometimes some of the yellow dots are too close to each other. So I am thinking if it's possible to make a collision-detection or something else, to check if the yellow dots collides?
Here is a picture of my game...
I made a red circle around the problem:
The link to my codepen project: /lolkie02/pen/PJVOdy?editors=0010
If you wanna try the game, it only works through iPhone or Android browser since I made the buttons etc. 'touchstart' in the javascript.
function getDistance(obj1, obj2) {
return Math.floor(
Math.sqrt(Math.pow(obj1.cx - obj2.cx, 2) + Math.pow(obj1.cy - obj2.cy, 2))
);
}
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function comparator(a, b) {
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
return 0;
}
function difference(source, toRemove) {
return source.filter(function(value) {
return toRemove.indexOf(value) == -1;
});
}
////////////////
// global vars
////////////////
var svg = document.getElementById("svg");
var dotMatrix = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle"
);
var lineMatrix = document.createElementNS("http://www.w3.org/2000/svg", "line");
var screenW = window.innerWidth;
var screenH = window.innerHeight;
var totalDist = document.getElementById("distance");
////////////////
// line constructor
////////////////
function Line(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.el = document.createElementNS("http://www.w3.org/2000/svg", "line");
this.class = "line";
this.update = function(x1, y1, x2, y2) {
this.el.setAttribute("x1", x1 || this.x1);
this.el.setAttribute("y1", y1 || this.y1);
this.el.setAttribute("x2", x2 || this.x2);
this.el.setAttribute("y2", y2 || this.y2);
this.setAttr("class", this.class);
};
this.setAttr = function(attr, value) {
this.el.setAttribute(attr, value);
};
this.append = function() {
svg.insertBefore(this.el, svg.firstChild);
};
}
////////////////
// dot constructor
////////////////
function Dot(r, cx, cy) {
this.r = r;
this.cx = cx;
this.cy = cy;
this.el = document.createElementNS("http://www.w3.org/2000/svg", "circle");
this.class = "dot";
this.update = function() {
this.el.setAttribute("r", this.r);
this.el.setAttribute("cx", this.cx);
this.el.setAttribute("cy", this.cy);
this.setAttr("class", this.class);
};
// activates a dot
this.activate = function() {
for (i = 0; i < dots.num; i++) {
dots.list[i].setAttr("data-selected", "false");
}
this.setAttr("data-selected", "true");
};
this.visited = function() {
this.setAttr("data-visited", "true");
};
// sets attribute to element
this.setAttr = function(attr, value) {
this.el.setAttribute(attr, value);
};
// gets attribute to element
this.getAttr = function(attr) {
return this.el.getAttribute(attr);
};
// appends element to svg and attaches event listeners
this.append = function() {
svg.appendChild(this.el);
this.el.addEventListener("touchstart", this.onClick);
};
// on click on element
this.onClick = function(event) {
//gets the id and the coords of the dot
var thisId = Number(event.target.getAttribute("data-id").substr(3, 2));
var thisCx = dots.list[thisId].cx;
var thisCy = dots.list[thisId].cy;
// calculates the distance between dots
var distances = [];
for (i = 0; i < dots.num; i++) {
distances[i] = [i, getDistance(dots.selected, dots.list[i])];
}
distances.sort(comparator);
distances.splice(0, 1);
var distancesLeft = [];
for (i = 0; i < distances.length; i++) {
if (dots.left.includes(distances[i][0])) {
distancesLeft.push(distances[i][0]);
}
}
//if the element is the nearest
if (thisId == distancesLeft[0] && dots.left.includes(thisId)) {
// calculates distances
var newDistance = getDistance(dots.list[thisId], dots.selected);
app.score.update(1); // punteggio x numero di poi
// app.score.update(newDistance); punteggio x distanza
//sets the active class to the selected dot
dots.list[thisId].activate();
dots.list[thisId].visited();
// creates the line
lines.list.push(
new Line(
dots.selected.cx,
dots.selected.cy,
dots.list[thisId].cx,
dots.list[thisId].cy
)
);
lines.list[lines.list.length - 1].update();
lines.list[lines.list.length - 1].append();
// creates the preview line
//TODO: eliminare le vecchie preline che rimangono vive
svg.addEventListener("mousemove", function prelineMove(e) {
mouseX = e.pageX;
mouseY = e.pageY;
app.preline.update(thisCx, thisCy, mouseX, mouseY);
});
//saves the selected dots coordinates
dots.selected.id = thisId;
dots.selected.cx = thisCx;
dots.selected.cy = thisCy;
//removes the dot from the list of remaining dots
for (i = 0; i < dots.left.length; i++) {
if (dots.left[i] === thisId) {
dots.left.splice(i, 1);
}
}
if (dots.left.length == 0) {
app.end(true);
}
} else {
app.end(false);
}
};
}
////////////////
// lines group
////////////////
var lines = {
list: []
};
////////////////
// dots group
////////////////
var dots = {};
dots.num = 20;
dots.list = [];
dots.start = 0;
dots.selected = {};
dots.selected.id = dots.start;
dots.left = [];
dots.preline;
////////////////
// app
////////////////
var app = {};
app.level = 2;
app.score = {};
app.score.number = 0;
app.score.el = document.getElementById("score");
app.score.update = function(score) {
app.score.number += score;
app.score.el.textContent = app.score.number;
};
app.score.reset = function() {
app.score.number = 0;
app.score.update(0);
};
app.results = function(points) {
if (points == "reset") {
sessionStorage.setItem("results", 0);
} else {
if (!sessionStorage.getItem("results")) {
sessionStorage.setItem("results", points);
} else {
var newscore = points;
sessionStorage.setItem("results", newscore);
}
}
};
app.launchScreen = function(lastScore, title, description, btnText) {
app.launchScreen.el = document.getElementById("launch-screen");
app.launchScreen.el.setAttribute("class", "is-visible");
var launchScreenTitle = document.getElementById("launch-screen__title");
launchScreenTitle.textContent = title;
var launchScreenDescription = document.getElementById(
"launch-screen__description"
);
launchScreenDescription.textContent = description;
app.launchScreen.btn = document.getElementById("start-btn");
app.launchScreen.btn.textContent = btnText;
app.launchScreen.btn.addEventListener("touchstart", function lauch() {
app.launchScreen.el.setAttribute("class", "");
app.start(app.level);
document.getElementById("score2").style.display = "block";
app.launchScreen.btn.removeEventListener("touchstart", lauch);
});
};
app.preline = new Line(0, 0, 200, 200);
app.preline.setAttr("id", "preline");
app.start = function(dotsNum) {
dots.num = dotsNum;
for (i = 0; i < dots.num; i++) {
var cx = getRandomArbitrary(45, screenW - 45);
var cy = getRandomArbitrary(45, screenH - 45);
dots.list[i] = new Dot(14, cx, cy);
dots.list[i].setAttr("data-id", "id-" + i);
dots.list[i].setAttr(
"style",
"animation-delay:" + i / 10 + "s; transform-origin: " + cx + 'px ' + cy + 'px;');
dots.list[i].update();
dots.list[i].append();
dots.left.push(i);
if (i == dots.start) {
dots.selected.cx = dots.list[dots.start].cx;
dots.selected.cy = dots.list[dots.start].cy;
dots.list[dots.start].setAttr("class", "dot dot--starting");
dots.left.splice(i, 1);
}
// adds the preline
app.preline.update(
dots.selected.cx,
dots.selected.cy,
dots.selected.cx,
dots.selected.cy
);
app.preline.append();
svg.addEventListener("mousemove", function prelineMove(e) {
mouseX = e.pageX;
mouseY = e.pageY;
app.preline.update(dots.selected.cx, dots.selected.cy, mouseX, mouseY);
});
}
// sets starting point
dots.list[dots.start].setAttr("data-selected", "true");
};
app.end = function(win) {
if (win) {
app.level += 2;
app.results(app.score.number);
} else {
app.level = 2;
}
dots.list = [];
dots.selected = {};
dots.left.length = 0;
svg.innerHTML = "";
if (win) {
app.launchScreen(
app.score.number,
"", //"Sådan!",
"", //"Din score er nu: " + sessionStorage.getItem("results") + ' Det næste level vil blive endnu hårdere.',
"NÆSTE LEVEL"
);
} else {
app.launchScreen(
0,
"", //"ARGH!",
"", //"Din endelige score blev: " + sessionStorage.getItem("results"),
"PRØV IGEN"
);
app.results("reset");
app.score.reset();
var score2 = document.getElementById('score2');
var number = score2.innerHTML;
number = 0;
score2.innerHTML = number;
document.getElementById("score2").style.display = "none";
}
};
app.launchScreen(
0,
"STIFINDER",
"Find den tætteste gule prik",
"SPIL"
);
$('.btn').on('touchstart',function(e,data) {
var score2 = document.getElementById('score2');
var number = score2.innerHTML;
number++;
score2.innerHTML = number;
});
Use Pythagorean theorem to determine whether the distance of the centers of two dots are closer (or equal) to the sum of their radii - in that case you have collision.
My answer to the similar question :https://stackoverflow.com/a/46973584/4154250
I have a circle that has a menu with objects positioned in a circle inside the circle. When the user drags their finger in any direction the circle spins along with the finger, however, when the user is spinning the wheel and happens to touch one of the objects inside (serves as a link) the circle "brakes" and stops spinning. I need to cancel the event bubble. I know I need to use event.stopPropagation and deal with the capture inside the event handler but I am having issues implementing this with a jQuery plugin (Touchy) that I found.
*MAKE SURE YOU ARE VIEWING IN CHROME WITH THE OVERRIDES SET TO EMULATE TOUCH EVENTS AND THAT THE DEVELOPER CONSOLE IS OPEN Command+Option+i on Mac!!! *
http://jsfiddle.net/ymtV3/
*MAKE SURE YOU ARE VIEWING IN CHROME WITH THE OVERRIDES SET TO EMULATE TOUCH EVENTS AND THAT THE DEVELOPER CONSOLE IS OPEN Command+Option+i on Mac!!! *
<div id="wheelMenu">
<div id="wheel">
<ul class="items">
<li class="flashOff"><span>Flash</span></li>
<li class="sceneOff"><span>Scene</span></li>
<li class="hdrOff"><span>Hdr</span></li>
<li class="panoramaOff"><span>Pana</span></li>
<li class="resolutionOff"><span>Resolu</span></li>
<li class="reviewOff"><span>Review</span></li>
<li class="continuousShootingOff"><span>Contin</span></li>
<li class="dropBoxOff"><span>DropBox</span></li>
<li class="timerOff"><span>Timer</span></li>
</ul>
</div>
</div>
(function ($) {
$.touchyOptions = {
useDelegation: false,
rotate: {
preventDefault: {
start: true,
move: true,
end: true
},
stopPropagation: {
start: true,
move: true,
end: true
},
requiredTouches: 1,
data: {},
proxyEvents: ["TouchStart", "TouchMove", "GestureChange", "TouchEnd"]
}
};
var proxyHandlers = {
handleTouchStart: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
touches = event.targetTouches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.start) {
event.preventDefault();
}
if (settings.stopPropagation.start) {
event.stopPropagation();
}
if (touches.length === settings.requiredTouches) {
switch (eventType) {
case 'rotate':
if (touches.length === 1) {
ensureSingularStartData(data, touches, e.timeStamp);
console.log(eventType);
console.log(touches);
console.log(data);
} else {
var points = getTwoTouchPointData(e);
data.startPoint = {
"x": points.centerX,
"y": points.centerY
};
data.startDate = e.timeStamp;
}
var startPoint = data.startPoint;
$target.trigger('touchy-rotate', ['start', $target, {
"startPoint": startPoint,
"movePoint": startPoint,
"lastMovePoint": startPoint,
"velocity": 0,
"degrees": 0
}]);
break;
}
}
}
},
handleTouchMove: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
touches = event.targetTouches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.move) {
event.preventDefault();
}
if (settings.stopPropagation.move) {
event.stopPropagation();
}
if (touches.length === settings.requiredTouches) {
switch (eventType) {
case 'rotate':
var lastMovePoint,
lastMoveDate,
movePoint,
moveDate,
lastMoveDate,
distance,
ms,
velocity,
targetPageCoords,
centerCoords,
radians,
degrees,
lastDegrees,
degreeDelta;
lastMovePoint = data.lastMovePoint = data.movePoint || data.startPoint;
lastMoveDate = data.lastMoveDate = data.moveDate || data.startDate;
movePoint = data.movePoint = {
"x": touches[0].pageX,
"y": touches[0].pageY
};
moveDate = data.moveDate = e.timeStamp;
if (touches.length === 1) {
targetPageCoords = data.targetPageCoords = data.targetPageCoords || getViewOffset(e.target);
centerCoords = data.centerCoords = data.centerCoords || {
"x": targetPageCoords.x + ($target.width() * 0.5),
"y": targetPageCoords.y + ($target.height() * 0.5)
};
} else {
var points = getTwoTouchPointData(e);
centerCoords = data.centerCoords = {
"x": points.centerX,
"y": points.centerY
};
if (hasGestureChange()) {
break;
}
}
radians = Math.atan2(movePoint.y - centerCoords.y, movePoint.x - centerCoords.x);
lastDegrees = data.lastDegrees = data.degrees;
degrees = data.degrees = radians * (180 / Math.PI);
degreeDelta = lastDegrees ? degrees - lastDegrees : 0;
ms = moveDate - lastMoveDate;
velocity = data.velocity = ms === 0 ? 0 : degreeDelta / ms;
$target.trigger('touchy-rotate', ['move', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": movePoint,
"lastMovePoint": lastMovePoint,
"centerCoords": centerCoords,
"degrees": degrees,
"degreeDelta": degreeDelta,
"velocity": velocity
}]);
break;
}
}
}
},
handleGestureChange: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var $target = $(e.target),
event = e.originalEvent,
touches = event.touches,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName);
if (data.preventDefault.move) {
event.preventDefault();
}
if (settings.stopPropagation.move) {
event.stopPropagation();
}
switch (eventType) {
case 'rotate':
var lastDegrees = data.lastDegrees = data.degrees,
degrees = data.degrees = event.rotation,
degreeDelta = lastDegrees ? degrees - lastDegrees : 0,
ms = data.moveDate - data.lastMoveDate,
velocity = data.velocity = ms === 0 ? 0 : degreeDelta / ms;
$target.trigger('touchy-rotate', ['move', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": data.movePoint,
"lastMovePoint": data.lastMovePoint,
"centerCoords": data.centerCoords,
"degrees": degrees,
"degreeDelta": degreeDelta,
"velocity": velocity
}]);
break;
}
}
},
handleTouchEnd: function (e) {
var eventType = this.context,
$target = getTarget(e, eventType);
if ($target) {
var event = e.originalEvent,
camelDataName = 'touchy' + eventType.charAt(0).toUpperCase() + eventType.slice(1),
data = $target.data(camelDataName),
settings = data.settings;
if (settings.preventDefault.end) {
event.preventDefault();
}
if (settings.stopPropagation.end) {
event.stopPropagation();
}
switch (eventType) {
case 'rotate':
var degreeDelta = data.lastDegrees ? data.degrees - data.lastDegrees : 0;
$target.trigger('touchy-rotate', ['end', $target, {
"startPoint": data.startPoint,
"startDate": data.startDate,
"movePoint": data.movePoint,
"lastMovePoint": data.lastMovePoint,
"degrees": data.degrees,
"degreeDelta": degreeDelta,
"velocity": data.velocity
}]);
$.extend(data, {
"startPoint": null,
"startDate": null,
"movePoint": null,
"moveDate": null,
"lastMovePoint": null,
"lastMoveDate": null,
"targetPageCoords": null,
"centerCoords": null,
"degrees": null,
"lastDegrees": null,
"velocity": null
});
break;
}
}
}
},
ensureSingularStartData = function (data, touches, timeStamp) {
if (!data.startPoint) {
data.startPoint = {
"x": touches[0].pageX,
"y": touches[0].pageY
}
}
if (!data.startDate) {
data.startDate = timeStamp;
}
},
hasGestureChange = function () {
return (typeof window.ongesturechange == "object");
},
getTarget = function (e, eventType) {
var $delegate,
$target = false,
i = 0,
len = boundElems[eventType].length
if ($.touchyOptions.useDelegation) {
for (; i < len; i += 1) {
$delegate = $(boundElems[eventType][i]).has(e.target);
if ($delegate.length > 0) {
$target = $delegate;
break;
}
}
} else if (boundElems[eventType] && boundElems[eventType].index(e.target) != -1) {
$target = $(e.target)
}
return $target;
},
getViewOffset = function (node, singleFrame) {
function addOffset(node, coords, view) {
var p = node.offsetParent;
coords.x += node.offsetLeft - (p ? p.scrollLeft : 0);
coords.y += node.offsetTop - (p ? p.scrollTop : 0);
if (p) {
if (p.nodeType == 1) {
var parentStyle = view.getComputedStyle(p, '');
if (parentStyle.position != 'static') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
if (p.localName == 'TABLE') {
coords.x += parseInt(parentStyle.paddingLeft);
coords.y += parseInt(parentStyle.paddingTop);
} else if (p.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.marginLeft);
coords.y += parseInt(style.marginTop);
}
} else if (p.localName == 'BODY') {
coords.x += parseInt(parentStyle.borderLeftWidth);
coords.y += parseInt(parentStyle.borderTopWidth);
}
var parent = node.parentNode;
while (p != parent) {
coords.x -= parent.scrollLeft;
coords.y -= parent.scrollTop;
parent = parent.parentNode;
}
addOffset(p, coords, view);
}
} else {
if (node.localName == 'BODY') {
var style = view.getComputedStyle(node, '');
coords.x += parseInt(style.borderLeftWidth);
coords.y += parseInt(style.borderTopWidth);
var htmlStyle = view.getComputedStyle(node.parentNode, '');
coords.x -= parseInt(htmlStyle.paddingLeft);
coords.y -= parseInt(htmlStyle.paddingTop);
}
if (node.scrollLeft) coords.x += node.scrollLeft;
if (node.scrollTop) coords.y += node.scrollTop;
var win = node.ownerDocument.defaultView;
if (win && (!singleFrame && win.frameElement)) addOffset(win.frameElement, coords, win);
}
}
......seee the rest on the JS Fiddle