Related
I use the oimo.js library (physic engine) with three.js and I would like my objects (not static) to levitate.
https://github.com/lo-th/Oimo.js/
I try to position the objects at a y value defined in my loop function but they keep falling.
How can I have these objects levitate while keeping non-static objects?
Here below my codepen to explain that :
https://codepen.io/ogames/pen/oNEWKLE?editors=0011
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.117.1/build/three.module.js'
import {
OrbitControls
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/controls/OrbitControls.js"
//import * as oimo from 'https://cdnjs.cloudflare.com/ajax/libs/oimo/1.0.9/oimo.min.js'
import {
TWEEN
} from 'https://unpkg.com/three#0.125.2/examples//jsm/libs/tween.module.min'
import {
CSM
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/csm/CSM.js"
///////////////////////////////////////////////////////////////////////////////
// utils
///////////////////////////////////////////////////////////////////////////////
export const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// variables
///////////////////////////////////////////////////////////////////////////////
var scene, container, controls, camera, renderer, light, ambient_light, additionalDirectionalLight, csm, stats, physics, position, body, world, ground_body, camera_body;
var game_start = false
window.setTimeout(() => {
game_start = true
}, 1000)
//material
var m = {};
//geometry
var g = {};
//objects
var o = {};
//
o.enemy = [];
//class
class Enemy_instance {
constructor(config) {
var g = {}
var m = {}
this.config = config;
g.body = new THREE.BoxGeometry(1, 1, 1);
m.body = new THREE.MeshToonMaterial({
color: this.config.color_body,
});
// a lot of body
this.body = this.create_instance(g.body, m.body, this.config.count, true);
this.config.scene.add(this.body)
// create several group and add dummy into it
this.mesh = [];
this.body.dummy = []
this.physic_body = []
for (let i = 0; i < this.config.count; i++) {
this.body.dummy[i] = new THREE.Object3D()
this.mesh[i] = new THREE.Group()
this.mesh[i].is_alive = true;
this.mesh[i].is_collide = false;
// rename
let mesh = this.mesh[i]
mesh.position.x = this.config.posx[i]
mesh.position.y = this.config.posy
mesh.position.z = this.config.posz[i]
this.config.scene.add(mesh)
this.physic_body[i] = this.config.world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [1, 1, 1], // size of shape
pos: [mesh.position.x, mesh.position.y, mesh.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: 10,
friction: .5,
restitution: 0.1,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
this.physic_body[i].position.copy(mesh.position)
this.physic_body[i].quaternion.copy(mesh.position)
console.log(this.physic_body[0])
};
}
//without having "this[obj]mesh"
create_instance(geo, mat, count, fl) {
let obj = new THREE.InstancedMesh(geo, mat, count);
obj.castShadow = fl
obj.receiveShadow = fl
obj.dummy = []
return obj
}
// for all instances
animate_instance(obj) {
obj.instanceMatrix.needsUpdate = true;
for (let i = 0; i < this.config.count; i++) {
obj.dummy[i].updateMatrix()
obj.setMatrixAt(i, obj.dummy[i].matrix);
this.add_element(obj.dummy, i)
}
}
add_element(obj, num) {
let master = this.mesh[num]
let slave = obj[num]
slave.position.set(master.position.x, master.position.y, master.position.z)
slave.rotation.set(master.rotation.x, master.rotation.y, master.rotation.z)
}
die(num) {
this.mesh[num].is_alive = false
this.hide(this.body.dummy[num])
}
hide(obj) {
new TWEEN.Tween(obj.scale)
.to({
x: 1.2,
y: 1.2,
z: 1.2,
}, 100)
.yoyo(true)
.delay(2000)
.repeat(1)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {
new TWEEN.Tween(obj.scale)
.to({
x: 0,
y: 0,
z: 0,
}, 80)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {});
});
}
animate() {
this.animate_instance(this.body)
for (let i = 0; i < this.config.count; i++) {
this.mesh[i].position.copy(this.physic_body[i].getPosition());
this.mesh[i].quaternion.copy(this.physic_body[i].getQuaternion());
// contacts
if (this.physic_body[i].numContacts > 0) {
this.mesh[i].is_collide = true
this.mesh[i].is_alive && this.die(i)
}
// no contacts
if (this.mesh[i].is_collide == false) {
this.physic_body[i].position.y = 2
}
}
}
};
var data = {
fade: true,
far: 200,
mode: 'practical',
lightX: -.31,
lightY: -1,
}
init_app();
function init_app() {
start_physic()
init();
animate();
}
// init physic with oimo
function start_physic() {
world = new OIMO.World({
timestep: 1 / 30,
// timestep: 1 / 60,
iterations: 1,
// iterations: 8,
broadphase: 2, // 1 brute force, 2 sweep and prune, 3 volume tree
worldscale: 1, // scale full world
random: true, // randomize sample
info: true, // calculate statistic or not
gravity: [0, -10, 0]
});
ground_body = world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [100000, .1, 100000], // size of shape
pos: [0, -.7, 0], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: false, // dynamic or statique
density: 1,
friction: 0.2,
restitution: 0.2,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
}
function init() {
Minimal()
create_object()
init_camera()
}
function create_object() {
create_ground();
create_enemy();
}
function init_camera() {
camera_body = world.add({
type: 'sphere', // type of shape : sphere, box, cylinder
size: [10, 10, 10], // size of shape
pos: [camera.position.x, camera.position.y, camera.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: .0001,
friction: 0,
restitution: .5,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffff1 // The bits of the collision groups with which the shape collides.
});
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
}
function create_ground() {
g.ground = new THREE.BoxGeometry(100000, .1, 100000);
m.ground = new THREE.MeshStandardMaterial({
color: 0xfce098,
});
csm.setupMaterial(m.ground)
o.ground = new THREE.Mesh(g.ground, m.ground);
o.ground.position.y = -.7
o.ground.castShadow = true;
o.ground.receiveShadow = true;
scene.add(o.ground);
}
function create_enemy() {
let c = {
scene: scene,
world: world,
speed: random(8, 16) * .01,
color_body: 0xbb1a1a,
count: 50,
}
c.posx = []
c.posz = []
for (let i = 0; i < c.count; i++) {
c.posx[i] = random(-20, -29)
c.posy = 20
c.posz[i] = i * 7
};
o.enemy = new Enemy_instance(c)
}
function Minimal() {
container = document.getElementById('world');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 2, 20000);
camera.position.y = 0
camera.position.z = -4
// camera.position.x = -4
camera.lookAt(0, 0, 0)
camera.updateMatrixWorld()
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
//Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
// ORBIT
controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0
controls.maxPolarAngle = Math.PI
controls.maxDistance = 10000;
controls.minDistance = .01;
// LIGHTS
ambient_light = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient_light);
//PLUG IN SHADOW FOR HUGE SCENE
csm = new CSM({
maxFar: data.far,
cascades: 4,
mode: data.mode,
parent: scene,
shadowMapSize: 4048,
lightDirection: new THREE.Vector3(data.lightX, data.lightY, data.lightZ).normalize(),
camera: camera
});
csm.fade = true;
container.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
world.step();
o.enemy.animate()
// copy position and rotation to three mesh
o.ground.position.copy(ground_body.getPosition());
o.ground.quaternion.copy(ground_body.getQuaternion());
// physical camera
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
TWEEN.update();
camera.updateMatrixWorld();
csm.update();
renderer.render(scene, camera);
};
Thank you for your precious help (I'm stuck on this question for 10 days without finding a real solution).
Thanks #Marquizzo,
It works with create an "new THREE.Object3D();" and set position with another object :
https://codepen.io/ogames/pen/mdXwbvr
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.117.1/build/three.module.js'
import {
OrbitControls
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/controls/OrbitControls.js"
//import * as oimo from 'https://cdnjs.cloudflare.com/ajax/libs/oimo/1.0.9/oimo.min.js'
import {
TWEEN
} from 'https://unpkg.com/three#0.125.2/examples//jsm/libs/tween.module.min'
import {
CSM
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/csm/CSM.js"
///////////////////////////////////////////////////////////////////////////////
// utils
///////////////////////////////////////////////////////////////////////////////
export const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// variables
///////////////////////////////////////////////////////////////////////////////
var scene, container, controls, camera, renderer, light, ambient_light, additionalDirectionalLight, csm, stats, physics, position, body, world, ground_body, camera_body;
var game_start = false
window.setTimeout(() => {
game_start = true
}, 2000)
//material
var m = {};
//geometry
var g = {};
//objects
var o = {};
//
o.enemy = [];
//class
class Enemy_instance {
constructor(config) {
var g = {}
var m = {}
this.config = config;
g.body = new THREE.BoxGeometry(1, 1, 1);
m.body = new THREE.MeshToonMaterial({
color: this.config.color_body,
});
// a lot of body
this.body = this.create_instance(g.body, m.body, this.config.count, true);
this.config.scene.add(this.body)
// create several group and add dummy into it
this.mesh = [];
this.body.dummy = []
this.physic_body = []
for (let i = 0; i < this.config.count; i++) {
this.body.dummy[i] = new THREE.Object3D()
this.mesh[i] = new THREE.Group()
this.mesh[i].is_alive = true;
this.mesh[i].is_collide = false;
// rename
let mesh = this.mesh[i]
mesh.position.x = this.config.posx[i]
mesh.position.y = this.config.posy
mesh.position.z = this.config.posz[i]
this.config.scene.add(mesh)
this.physic_body[i] = this.config.world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [1, 1, 1], // size of shape
pos: [mesh.position.x, mesh.position.y, mesh.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: 10,
friction: .5,
restitution: 0.1,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
this.physic_body[i].position.copy(mesh.position)
this.physic_body[i].quaternion.copy(mesh.position)
console.log(this.physic_body[0])
};
}
//without having "this[obj]mesh"
create_instance(geo, mat, count, fl) {
let obj = new THREE.InstancedMesh(geo, mat, count);
obj.castShadow = fl
obj.receiveShadow = fl
obj.dummy = []
return obj
}
// for all instances
animate_instance(obj) {
obj.instanceMatrix.needsUpdate = true;
for (let i = 0; i < this.config.count; i++) {
obj.dummy[i].updateMatrix()
obj.setMatrixAt(i, obj.dummy[i].matrix);
this.add_element(obj.dummy, i)
}
}
add_element(obj, num) {
let master = this.mesh[num]
let slave = obj[num]
slave.position.set(master.position.x, master.position.y, master.position.z)
slave.rotation.set(master.rotation.x, master.rotation.y, master.rotation.z)
}
die(num) {
this.mesh[num].is_alive = false
//console.log(num, "collide and die")
this.hide(this.body.dummy[num])
}
hide(obj) {
new TWEEN.Tween(obj.scale)
.to({
x: 1.2,
y: 1.2,
z: 1.2,
}, 100)
.yoyo(true)
.delay(2000)
.repeat(1)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {
new TWEEN.Tween(obj.scale)
.to({
x: 0,
y: 0,
z: 0,
}, 80)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {});
});
}
move(num) {
if (this.config.posx[num] < 0) {
this.physic_body[num].position.x += this.config.speed
}
if (this.config.posx[num] > 0) {
this.physic_body[num].position.x -= this.config.speed
}
if (this.physic_body[num].position.x > 30) {
this.physic_body[num].position.x = this.config.posx[num]
}
if (this.physic_body[num].position.x < -30) {
this.physic_body[num].position.x = this.config.posx[num]
}
}
animate() {
this.animate_instance(this.body)
for (let i = 0; i < this.config.count; i++) {
this.mesh[i].position.copy(this.physic_body[i].getPosition());
this.mesh[i].quaternion.copy(this.physic_body[i].getQuaternion());
// contacts
if (this.physic_body[i].numContacts > 0) {
this.mesh[i].is_collide = true
this.mesh[i].is_alive && this.die(i)
}
// no contacts
if (this.mesh[i].is_collide == false) {
this.physic_body[i].position.y = 2
this.move(i)
}
}
}
};
var data = {
fade: true,
far: 200,
mode: 'practical',
lightX: -.31,
lightY: -1,
}
init_app();
function init_app() {
start_physic()
init();
animate();
}
// init physic with oimo
function start_physic() {
world = new OIMO.World({
timestep: 1 / 30,
// timestep: 1 / 60,
iterations: 1,
// iterations: 8,
broadphase: 2, // 1 brute force, 2 sweep and prune, 3 volume tree
worldscale: 1, // scale full world
random: true, // randomize sample
info: true, // calculate statistic or not
gravity: [0, -20, 0]
});
ground_body = world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [100000, .1, 100000], // size of shape
pos: [0, -.7, 0], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: false, // dynamic or statique
density: 1,
friction: 0.2,
restitution: 0.2,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
}
function init() {
Minimal()
create_object()
init_camera()
}
function create_object() {
create_ground();
create_enemy();
o.paddel = new THREE.Object3D();
o.paddel.position.set(10, 10, 10)
scene.add(o.paddel);
}
function init_camera() {
camera_body = world.add({
type: 'sphere', // type of shape : sphere, box, cylinder
size: [10, 10, 10], // size of shape
pos: [camera.position.x, camera.position.y, camera.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: .0001,
friction: 0,
restitution: .5,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffff1 // The bits of the collision groups with which the shape collides.
});
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
}
function create_ground() {
g.ground = new THREE.BoxGeometry(100000, .1, 100000);
m.ground = new THREE.MeshStandardMaterial({
color: 0xfce098,
});
csm.setupMaterial(m.ground)
o.ground = new THREE.Mesh(g.ground, m.ground);
o.ground.position.y = -.7
o.ground.castShadow = true;
o.ground.receiveShadow = true;
scene.add(o.ground);
}
function create_enemy() {
let c = {
scene: scene,
world: world,
speed: random(8, 16) * .01,
color_body: 0xbb1a1a,
count: 5000,
}
c.posx = []
c.posy = 20
c.posz = []
for (let i = 0; i < c.count; i++) {
// pour générer du choix
let rnd = Math.round(Math.random())
if (rnd == 0) {
c.posx[i] = random(20, 29)
} else {
c.posx[i] = random(-20, -29)
}
c.posz[i] = i * 7
};
o.enemy = new Enemy_instance(c)
}
function Minimal() {
container = document.getElementById('world');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 2, 20000);
camera.position.y = 0
camera.position.z = -4
// camera.position.x = -4
camera.lookAt(0, 0, 0)
camera.updateMatrixWorld()
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
//Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
// ORBIT
controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0
controls.maxPolarAngle = Math.PI
controls.maxDistance = 10000;
controls.minDistance = .01;
// LIGHTS
ambient_light = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient_light);
//PLUG IN SHADOW FOR HUGE SCENE
csm = new CSM({
maxFar: data.far,
cascades: 4,
mode: data.mode,
parent: scene,
shadowMapSize: 4048,
lightDirection: new THREE.Vector3(data.lightX, data.lightY, data.lightZ).normalize(),
camera: camera
});
csm.fade = true;
container.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
world.step();
o.enemy.animate()
o.enemy.physic_body[0].setPosition(o.paddel.position);
// copy position and rotation to three mesh
o.ground.position.copy(ground_body.getPosition());
o.ground.quaternion.copy(ground_body.getQuaternion());
// physical camera
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
TWEEN.update();
camera.updateMatrixWorld();
csm.update();
renderer.render(scene, camera);
};
I have a P5.js art, but when I remove the background, the effect changes. I'd like to remove the background but keep the effect the same. The reason for this is because I plan to overlay this on top of another canvas.
The line I'd like to remove is this: background(30); However, when I remove it, the effect becomes different.
The full code can be found here: https://editor.p5js.org/timexer/sketches/Rbbo72xVi
class Particle {
constructor(){
this.pos = createVector(width/2,height/2)
this.vel = createVector(0,0)
this.acc = p5.Vector.random2D().normalize()
this.r = map(this.pos.x, 0, width, 255, 0)
this.g = map(this.pos.y, 0, height, 0, 255)
this.b = map(dist(width / 2, height / 2, this.pos.x, this.pos.y), 0, width/2, 0, 255)
this.alpha = 255
}
update(){
var m = map(sin(frameCount), -1, 1, 0.3, 0.6)
this.acc.mult(m)
this.vel.add(this.acc)
this.pos.add(this.vel)
this.r = map(this.pos.x, 0, width, 255, 0)
this.g = map(this.pos.y, 0, height, 0, 255)
this.b = map(dist(width / 2, height / 2, this.pos.x, this.pos.y), 0, width, 0, 255)
if ( dist(width / 2, height / 2, this.pos.x, this.pos.y) > 80){
this.alpha -= 5
}
}
show(){
noStroke()
fill(this.r, this.g, this.b, this.alpha)
ellipse(this.pos.x, this.pos.y, 2)
}
}
var particles = []
function setup() {
createCanvas(800, 800);
angleMode(DEGREES)
}
function draw() {
background(30);
for(var i = 0; i <10; i++){
p = new Particle()
particles.push(p)
}
for(var i = 0; i < particles.length ; i++){
if(particles[i].alpha > 0){
particles[i].update()
particles[i].show()
}else{
particles.splice(i,1)
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Instead of calling background(30) call clear()
var particles = []
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(DEGREES)
}
function draw() {
clear();
for (var i = 0; i < 10; i++) {
const p = new Particle()
particles.push(p)
}
for (var i = 0; i < particles.length; i++) {
if (particles[i].alpha > 0) {
particles[i].update()
particles[i].show()
} else {
particles.splice(i, 1)
}
}
}
class Particle {
constructor() {
this.pos = createVector(width / 2, height / 2)
this.vel = createVector(0, 0)
this.acc = p5.Vector.random2D().normalize()
this.r = map(this.pos.x, 0, width, 255, 0)
this.g = map(this.pos.y, 0, height, 0, 255)
this.b = map(dist(width / 2, height / 2, this.pos.x, this.pos.y), 0, width / 2, 0, 255)
this.alpha = 255
}
update() {
var m = map(sin(frameCount), -1, 1, 0.3, 0.6)
this.acc.mult(m)
this.vel.add(this.acc)
this.pos.add(this.vel)
this.r = map(this.pos.x, 0, width, 255, 0)
this.g = map(this.pos.y, 0, height, 0, 255)
this.b = map(dist(width / 2, height / 2, this.pos.x, this.pos.y), 0, width, 0, 255)
if (dist(width / 2, height / 2, this.pos.x, this.pos.y) > 80) {
this.alpha -= 5
}
}
show() {
noStroke()
fill(this.r, this.g, this.b, this.alpha)
ellipse(this.pos.x, this.pos.y, 2)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I want to create multiple line graphs with their own datasets into one p5.js sketch.
I created one line graph and do not know how to make another line graph to work with a different dataset.
https://editor.p5js.org/ariel.koh/sketches/VsRk3KxYp
I tried to create another preload table for my second graph but it did not work so I am still confused.
This was what I tried but I think it is not right, I never tried working with multiple datasets before so I am confused :(
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canonattacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv",
"header");
}
You're on the right track loading the data.
The p5.js code you posted is a bit convoluted, here only a few examples:
the year labels a manually drawn, for each chart (even though the data is contained in the csv files)
if the text is simply drawn for the years but separate style, coordinate transformations, etc. are used then the push()/pop() call don't add anything.(e.g push();let sbm = 'Saul Bass Movies';...)
the drawLineGraph() function is defined twice (which might get you into variable shadowing bugs) and might be accidentally called within itself
No wonder you're confused.
I'd simplify as much as possible:
get back to loading and displaying one graph
load the second dataset (as you already correctly do above)
display the second graph
For example:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Burns", 30, 20, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: burnsdataset,
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text("Canon Attacks", 30, 250, 330, 150); // Text wraps within text box
pop();
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// we will calcualte the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
With that working you can focus on making the code nicer.
Taking the idea of "don't repeat yourself(DRY)" you can notice a few patterns that could be improved:
you try to not just draw the graph, but also also display the graph's
label: perhaps the drawLineGraph() could include that
you seem to manually be drawing every single year label, even though the data is already in the dataset and the x position is already calculated to display the points of the graph
Tackling point 1 is fairly straight foward: simply move the label drawing instrunctions inside the function, handle the extra argument to get the label text and adjust coordinates so they're relative to the graph.
Here's a modified version of the above integrating label as part of the drawLineGraph() function:
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
}
function draw() {
background(184, 37, 0);
//linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
column: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
//linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
column: 1,
x: 30,
y: 280,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.column);
let minValue = min(values);
let maxValue = max(values);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y + h + marginTopBottom * 0.5, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
And here's a modified version of the above that renders the labels on the x axis (year):
// file -> duplicate and file -> save
// this sketch first before you
// start working on it.
//
//
//
// Graph from Data, Line graph example
//
// Slides:
// http://slides.com/sojamo/cid-3-2122/fullscreen#/12/5
let burnsdataset;
let canonattacksdataset;
function preload() {
// burns dataset
burnsdataset = loadTable("burns.csv", "csv", "header");
// canon attacks dataset
canonaattacksdataset = loadTable("canonattacks.csv", "csv", "header");
}
function setup() {
// if you add SVG as 3rd parameter and then
// press s, the canvas will be saved as SVG file
createCanvas(displayWidth, displayHeight, SVG);
drawPlots();
}
function drawPlots() {
background(184, 37, 0);
// burnsdataset linegraph
drawLineGraph({
dataset: burnsdataset,
label: "Burns",
xColumn: 0,
yColumn: 1,
x: 30,
y: 50,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
// canonaattacksdataset linegraph
drawLineGraph({
dataset: canonaattacksdataset,
label: "Canon Attacks",
xColumn: 0,
yColumn: 1,
x: 30,
y: 300,
w: width - 1500,
h: height - 900,
lc: color(255, 255, 255),
dc: color(169, 247, 111),
thickness: 1.5,
isCurve: true,
isDots: true,
isBackground: true,
isLabel: true,
});
}
function drawLineGraph(theProps = {}) {
let values = theProps.dataset.getColumn(theProps.yColumn);
let minValue = min(values);
let maxValue = max(values);
let labels = theProps.dataset.getColumn(theProps.xColumn);
let x = theProps.x || 0;
let y = theProps.y || 0;
let w = theProps.w || 400;
let h = theProps.h || 300;
let bg = theProps.bg || color(255, 40);
let fg = theProps.fg || color(255, 120);
let dc = theProps.dc || color(0, 120);
let bc = theProps.bc || color(0, 120);
let lc = theProps.lc || color(255);
let thickness = theProps.thickness || 1;
let isLabel = theProps.isLabel || false;
let isCurve = theProps.isCurve || false;
let isDots = theProps.isDots || false;
let isBackground = theProps.isBackground || false;
let label = theProps.label || "";
let spacing = w / (values.length - 1);
let marginTopBottom = 20;
// y position for labels (e.g. years)
let labelsY = marginTopBottom * 0.5;
//bottom label title
push();
textSize(14);
textFont("inconsolata");
textStyle(BOLDITALIC);
fill(243, 230, 214);
text(label, x, y - marginTopBottom, w, h); // Text wraps within text box
pop();
// we will calculate the x and y values for each
// point first, because we need it later a couple
// of times.
let xx = [];
let yy = [];
let n = values.length;
for (let i = 0; i < n; i++) {
xx[i] = i * spacing;
yy[i] = map(
values[i],
minValue,
maxValue,
-marginTopBottom,
marginTopBottom - h
);
}
push();
translate(x, y);
translate(0, h);
// 1. draw background
if (isBackground == true) {
fill(bg);
rect(0, 0, w, -h);
// 2. draw grid
noStroke();
for (let i = 0; i < n - 1; i++) {
// fill(255,i%2 == 0 ? 80:40);
rect(xx[i], 0, 1, -h);
}
}
// 3. draw curved line graph
noFill();
stroke(lc);
strokeWeight(thickness);
beginShape();
if (isCurve == true) {
vertex(xx[0], yy[0]);
}
for (let i = 0; i < n; i++) {
let fn = isCurve == true ? curveVertex : vertex;
fn(xx[i], yy[i]);
// label per data point
push();
noStroke();
fill(255);
textSize(9);
textAlign(CENTER);
text(labels[i], xx[i], labelsY);
pop();
}
if (isCurve == true) {
vertex(xx[n - 1], yy[n - 1]);
}
endShape();
// 4. draw dot / label for each value
noStroke();
// finally lets draw the dots and
// labels if enabled
let d = thickness + 8;
for (let i = 0; i < n; i++) {
push();
translate(xx[i], yy[i]);
if(isDots == true) {
fill(bc);
ellipse(0, 0, d + 4, d + 4);
fill(dc);
ellipse(0, 0, d, d);
}
if (isLabel == true) {
fill(lc)
rotate(-0.5);
textSize(7);
text(values[i], d, -d);
}
pop();
}
pop();
}
// if you want to use the SVG export
// option, go to setup and enable SVG mode
function keyPressed() {
if (key === "s") {
saveSVG("line-graph.svg");
}
}
There's one slight detail snuck in there: I've renamed draw() to drawPlots() and called it once from setup(). That is to render the plots a single time since they don't change and save CPU resources. If you do plan to have some sort interation changing these graphs then you should use draw().
I would like to change the fill colour of an object over time. Is there a way to change the fill colour of an object after X frames?
I am learning about constructors, and I am thinking that in the code's updateParticle function, after this.age counts to 'X', the fill colour of the ellipse could change.
'''
function Particle(x, y, xSpeed, ySpeed, size, colour) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.size = size;
this.colour = colour;
this.age = 0;
this.drawParticle = function() {
fill(this.colour);
noStroke();
ellipse(this.x, this.y, this.size);
}
this.updateParticle = function() {
this.x += this.xSpeed;
this.y += this.ySpeed;
this.age++;
}
}
function Emitter(x, y, xSpeed, ySpeed, size, colour) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.size = size;
this.colour = colour;
this.particles = [];
this.startParticles = [];
this.lifetime = 0;
this.addParticle = function() {
var p = new Particle(random(this.x - 10, this.x + 10),
random(this.y - 10, this.y + 10),
random(this.xSpeed - 0.4, this.xSpeed + 0.4),
random(this.ySpeed - 5, this.ySpeed - 1),
random(this.size - 4, this.size + 40),
this.colour);
return p;
}
this.startEmitter = function(startParticles, lifetime) {
this.startParticles = startParticles;
this.lifetime = lifetime;
for (var i = 0; i < startParticles; i++) {
this.particles.push(this.addParticle());
}
}
this.updateParticles = function() {
var deadParticles = 0
for (var i = this.particles.length - 1; i >= 0; i--) {
this.particles[i].drawParticle();
this.particles[i].updateParticle();
if (this.particles[i].age > random(0, this.lifetime)) {
this.particles.splice(i, 1);
deadParticles++;
}
}
if (deadParticles > 0) {
for (var i = 0; i < deadParticles; i++) {
this.particles.push(this.addParticle());
}
}
}
}
var emit;
function setup() {
createCanvas(800, 600);
emit = new Emitter(width / 2, height - 100, 0, -1, 10, color(200, 0, 200, 50));
emit.startEmitter(600, 4000);
}
function draw() {
background(200);
emit.updateParticles();
}
'''
Well you could just:
if(frameCount % 30 == 0){ // % 30 is the remainder of num / 30, so 4 % 3 = 1, since 3 / 3 = 0 And 4 / 3 = 3.33
fill("lime") // these are just preset colors in p5.js AND css lime == rgb(0,255,0)
} else {
fill('darkred')
}
or you could also do it with for example a switch statement: using background for no reason
switch(MY_frameCount){
case 1:
background('blue')
break
case 2:
background("darkgreen")
break
case 376:
background(crimson)
// break
}
MY_frameCount ++
or:
if(Math.random() < 0.1){
fill(Math.random() * 255, Math.random() * 255, Math.random() * 255)
} // this should on average fill with random color every 10 frames
I have problem with resume animation after stop it. Any suggestion ? I using kineticjs to make element run along the path, after i reach the end, the animation stop for 2 second then start again.
here my code :
var stage = new Kinetic.Stage({
container: 'canvas',
height: 484,
width: 478
});
var layer = new Kinetic.Layer();
stage.add(layer);
var img = new Image();
img.src = 'images/ani/bullet_blue.png';
var circle = new Image();
circle.src = 'images/ani/1.png';
var shapeCircle = new Kinetic.Image({
x: 10,
y: 10,
image: circle,
width: circle.width,
height: circle.height,
offset: {
x: 0,
y: 0
}
});
layer.add(shapeCircle);
layer.draw();
function animation(points, shape, duration, loop, callback) {
layer.add(shape);
window.anim = new Kinetic.Animation(function (frame) {
var time = frame.time, timeDiff = frame.timeDiff, frameRate = frame.frameRate;
var percent = time / duration, scale = 0.5, opacity = 0;
if (percent < 0.05 || percent > 0.95) opacity = 0;
else opacity = 1;
// scale calculate
if (percent < 0.5) {
scale += percent
}
else if (percent > 0.5) {
scale = 1 - (percent - 0.5)
}
if (percent > 1) {
anim.stop();
percent = 0;
setTimeout(function () { anim.start(); }, 500);
} else {
var pos = Math.ceil(percent * points.length);
pos = pos > (points.length - 1) ? (points.length - 1) : pos;
if (pos == points.length - 1) anim.stop();
shape.setScale(scale, scale);
shape.setOpacity(opacity);
shape.setPosition(points[pos].x, points[pos].y);
}
}, layer);
anim.start();
}
animation(points1, new Kinetic.Image({
x: points1[0].x,
y: points1[0].y,
image: img,
width: img.width,
height: img.height,
opacity: 0,
scaleX: 0.5,
scaleY: 0.5,
offset: {
x: 15,
y: 30
}
}), 2000);
If you want a repeating animation, you might consider a tween instead.
When a tween finishes, it fires a finished event. In that finished handler, you can:
Reset the tween to the starting position using tween.reset,
Start a timer to wait 2 seconds using setTimer,
When the timer fires, restart the tween using tween.start.