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 m working on a script that let clients draw the way they want to bend iron with detailed angles. everything is OK.
Now the client want's a 3D view of it. but i have no idea of how to do it (with three.js I guess)
I need a simple 3D view.
Here is what i did till now for 2D view, it's based on FabricJS:
$(function(){
var canvas = this.__canvas = new fabric.Canvas('c', { selection: false });
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
var Lines = Array();
var Points = Array();
var circleAngles = Array();
var anglesValues = Array();
function radianToDegrees(r){
r = r * 180/Math.PI;
if(r < 0) r = -r;
if(360 - r < r) r = 360 - r;
return 180 - parseFloat(r).toFixed(0);
}
function makeCircle(left, top, line1, line2) {
var c = new fabric.Circle({
left: left,
top: top,
strokeWidth: 0,
radius: 8,
fill: '#000',
stroke: '#000'
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 2,
selectable: false,
evented: false,
});
}
function makeCircleAngle(angle, startAngle, endAngle){
if (angle == 1) color = 'red'; else color = '#003366';
circleAngle = new fabric.Circle({
radius: 20,
left: Lines[i].get('x1'),
top: Lines[i].get('y1'),
angle: 0,
startAngle: startAngle,
endAngle: endAngle,
strokeDashArray: [3, 2],
stroke: color,
fill: '',
selectable: false,
evented: false,
});
return circleAngle;
}
function makeText(text, x, y){
t = new fabric.Text(text, {
left: x, top: y, fontSize: 13, fill: '#003366', fontFamily: 'Arial',
selectable: false,
evented: false,
});
return t;
}
function drawLinesCanvas(){
$.each(Lines, function(i, e){
canvas.add(e);
})
}
function drawDotsCanvas(){
Points = Array();
p = makeCircle(Lines[0].get('x1'), Lines[0].get('y1'), null, Lines[0]);
Points.push(p)
canvas.add(p);
for(i = 0; i< Lines.length-1; i++){
p = makeCircle(Lines[i].get('x2'), Lines[i].get('y2'), Lines[i], Lines[i+1]);
Points.push(p)
canvas.add( p );
}
if (Lines.length-1 >= 0){
p = makeCircle(Lines[Lines.length-1].get('x2'), Lines[Lines.length-1].get('y2'), Lines[Lines.length-1]);
Points.push(p)
canvas.add( p );
}
}
function calculateAndDrawAngles(){
$.each(circleAngles, function(i, ce){
canvas.remove(ce);
})
$.each(Lines, function(i, l){
canvas.remove(l);
})
$.each(Points, function(i, p){
canvas.remove(p);
})
$.each(anglesValues, function(i, a){
canvas.remove(a);
})
if(Lines.length >= 2){
for(i=1; i<Lines.length; i++){
y11 = Lines[i].get('y1');
y12 = Lines[i].get('y2');
y21 = Lines[i-1].get('y1');
y22 = Lines[i-1].get('y2');
x11 = Lines[i].get('x1');
x12 = Lines[i].get('x2');
x21 = Lines[i-1].get('x1');
x22 = Lines[i-1].get('x2');
angle1 = Math.atan2(y11 - y12, x11 - x12);
angle2 = Math.atan2(y21 - y22, x21 - x22);
angle = angle1 - angle2;
if (angle < 0){
sStartAngle = Math.PI + angle1;
sEndAngle = angle2;
} else {
sStartAngle = angle1 - Math.PI;
sEndAngle = angle2;
}
myAngle = radianToDegrees(angle1 - angle2);
if(sStartAngle > sEndAngle) {
c = makeCircleAngle(1, sStartAngle, sEndAngle);
c1 = makeCircleAngle(2, sEndAngle, sStartAngle);
myAngleText = makeText(myAngle.toString()+'°', Lines[i].get('x1') +20, Lines[i].get('y1') + 20)
} else {
c = makeCircleAngle(2, sStartAngle, sEndAngle);
c1 = makeCircleAngle(1, sEndAngle, sStartAngle);
myAngleText = makeText(myAngle.toString()+'°', Lines[i].get('x1') - 20, Lines[i].get('y1') - 20)
}
circleAngles.push(c, c1);
canvas.add(c, c1);
canvas.add(myAngleText)
anglesValues.push(myAngleText);
}
drawLinesCanvas();
drawDotsCanvas();
}
}
canvas.on('object:moving', function(e) {
var p = e.target;
p.line1 && p.line1.set({ 'x2': p.left, 'y2': p.top });
p.line2 && p.line2.set({ 'x1': p.left, 'y1': p.top });
if (Lines.length > 1){
calculateAndDrawAngles();
}
});
canvas.on('mouse:wheel', function(opt) {
var delta = opt.e.deltaY;
var zoom = canvas.getZoom();
zoom = zoom + delta/200;
if (zoom > 20) zoom = 5;
if (zoom < 0.01) zoom = 0.5;
canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
var vpt = this.viewportTransform;
if (zoom < 400 / 1000) {
this.viewportTransform[4] = 200 - 1000 * zoom / 2;
this.viewportTransform[5] = 200 - 1000 * zoom / 2;
} else {
if (vpt[4] >= 0) {
this.viewportTransform[4] = 0;
} else if (vpt[4] < canvas.getWidth() - 1000 * zoom) {
this.viewportTransform[4] = canvas.getWidth() - 1000 * zoom;
}
if (vpt[5] >= 0) {
this.viewportTransform[5] = 0;
} else if (vpt[5] < canvas.getHeight() - 1000 * zoom) {
this.viewportTransform[5] = canvas.getHeight() - 1000 * zoom;
}
}
});
canvas.on('mouse:down', function(opt) {
var evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
canvas.on('mouse:move', function(opt) {
if (this.isDragging) {
var e = opt.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
canvas.on('mouse:up', function(opt) {
this.isDragging = false;
this.selection = true;
});
$('#addRight').on('click', function(){
fromPoint = Lines[Lines.length - 1];
Lines.push(makeLine([ fromPoint.get('x2'), fromPoint.get('y2'), fromPoint.get('x2') - 50, fromPoint.get('y2') + 50 ]))
calculateAndDrawAngles()
});
$('#addLeft').on('click', function(){
fromPoint = Lines[0];
Lines.unshift(makeLine([ fromPoint.get('x1') + 50, fromPoint.get('y1') + 50, fromPoint.get('x1'), fromPoint.get('y1') ]))
calculateAndDrawAngles()
});
function drawGrid(){
options = {
distance: 10,
width: c.width,
height: c.height,
param: {
stroke: '#ebebeb',
strokeWidth: 1,
selectable: false
}
},
gridLen = options.width / options.distance;
for (var i = 0; i < gridLen; i++) {
distance = i * options.distance,
horizontal = new fabric.Line([ distance, 0, distance, options.width], options.param),
vertical = new fabric.Line([ 0, distance, options.width, distance], options.param);
canvas.add(horizontal);
canvas.add(vertical);
if(i%5 === 0){
horizontal.set({stroke: '#cccccc'});
vertical.set({stroke: '#cccccc'});
};
canvas.sendBackwards(horizontal);
canvas.sendBackwards(vertical);
};
}
Lines = [makeLine([ 100, 50, 400, 50 ])];
drawGrid();
drawLinesCanvas();
drawDotsCanvas();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="c" width="500" height="400" style="border:1px solid #ccc;"></canvas>
<div class="text-center">
<button class="btn btn-info" id="addLeft">Ajouter un point à gauche</button>
<button class="btn btn-info" id="addRight">Ajouter un point à droite</button>
</div>
Just an option, when you can obtain the coordinates of points and use them to build a bended iron:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.setScalar(10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var obtained_data = [];
for (let i = 0; i < 10; i++) {
obtained_data.push(
new THREE.Vector2(i, Math.random() * 8 * 0.5 - 0.5) // fill the data with example coordinates
);
}
//console.log(obtained_data);
var dataLength = obtained_data.length;
var geom = new THREE.PlaneBufferGeometry(1, 10, dataLength - 1, 10); // size on Y and its segmentation is up to you
geom.rotateX(Math.PI * 0.5);
var pos = geom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
let idx = i % dataLength;
let x = obtained_data[idx].x;
let y = obtained_data[idx].y;
pos.setXY(i, x, y);
}
pos.needsUpdate = true;
geom.computeVertexNormals();
var mat = new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
});
var iron = new THREE.Mesh(geom, mat);
scene.add(iron);
renderer.setAnimationLoop(() => {
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 trying to synchronize the move,resize and rotate operations of two fabric objects.
Consider there are two polygons - Poly1 and Poly2.
When Poly1 is modified in any manner, I need to apply the same modification on Poly2 and need to get the updated points for Poly2. Below is the approach being followed :
Get the transform matrices of Poly1 before and after modification.
Use them to get the matrix which modified the transform matrix from
its original to the modified state.
Multiply the matrix from the above step to the current transform
matrix of Poly2 to get the new transform matrix.
Use the new transform matrix and get the updated points and redraw
Poly2.
However, this approach is only working for move operation. If you do resize or rotate on Poly1, the same is not being applied correctly on Poly2. Please let me know what I am doing wrong here.
Thanks in advance for any help!!
Sample code - Blue is Poly1 and Red is Poly2
var oldPoly1Matrix;
var canvas = new fabric.Canvas('c');
var srcOptions = {
stroke: 'blue',
fill: '',
type: 'src'
};
var destOptions = {
stroke: 'red',
fill: '',
selectable: false,
hasControls: false,
hasBorders: false,
type: 'dest'
};
var poly1 = new fabric.Polygon([{
x: 60,
y: 40
}, {
x: 160,
y: 40
}, {
x: 160,
y: 140
}, {
x: 60,
y: 140
}], srcOptions);
var poly2 = new fabric.Polygon([{
x: 60,
y: 300
}, {
x: 160,
y: 300
}, {
x: 160,
y: 400
}, {
x: 60,
y: 400
}], destOptions);
canvas.add(poly1).add(poly2);
oldPoly1Matrix = poly1.calcTransformMatrix();
var originalPoly2Matrix = poly2.calcTransformMatrix();
poly2.matrix = originalPoly2Matrix;
function updatePoly2() {
var newPoly1Matrix = poly1.calcTransformMatrix();
var oldPoly1MatrixInverted = fabric.util.invertTransform(oldPoly1Matrix);
//newMatrix = oldMatrix * someMatrix
//therefore,someMatrix = newMatrix / oldMatrix = newMatrix * inverse(oldMatrix);
var diffMatrix = fabric.util.multiplyTransformMatrices(newPoly1Matrix, oldPoly1MatrixInverted);
var oldPoly2Matrix = poly2.matrix;
//Apply the same someMatrix to find out the new transform matrix for poly2.
var newPoly2Matrix = fabric.util.multiplyTransformMatrices(oldPoly2Matrix, diffMatrix);
var updatedPoints = poly2.get('points')
.map(function(p) {
return new fabric.Point(p.x - poly2.minX - poly2.width / 2, p.y - poly2.minY - poly2.height / 2);
})
.map(function(p) {
return fabric.util.transformPoint(p, newPoly2Matrix);
});
oldPoly1Matrix = newPoly1Matrix;
poly2.remove();
poly2 = new fabric.Polygon(updatedPoints, destOptions);
poly2.matrix = newPoly2Matrix;
canvas.add(poly2);
}
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.js"></script>
<canvas id="c" height="500" width="600"></canvas>
<button id="update" onclick="updatePoly2()">
Update red shape
</button>
jsfiddle - https://jsfiddle.net/btxp0ck6/
It is working fine after changing the translateX and translateY calculation from matrix mulplication to simple subtraction.Posting the code here for reference.
var oldPoly1Matrix;
var canvas = new fabric.Canvas('c');
var srcOptions = {
stroke: 'blue',
fill: '',
type: 'src'
};
var destOptions = {
stroke: 'red',
fill: '',
selectable: false,
hasControls: false,
hasBorders: false,
type: 'dest'
};
var poly1 = new fabric.Polygon([{
x: 60,
y: 40
}, {
x: 160,
y: 40
}, {
x: 160,
y: 140
}, {
x: 60,
y: 140
}], srcOptions);
var poly2 = new fabric.Polygon([{
x: 60,
y: 300
}, {
x: 160,
y: 300
}, {
x: 160,
y: 400
}, {
x: 60,
y: 400
}], destOptions);
canvas.add(poly1).add(poly2);
oldPoly1Matrix = poly1.calcTransformMatrix();
var originalPoly2Matrix = poly2.calcTransformMatrix();
poly2.matrix = originalPoly2Matrix;
function updatePoly2() {
var newPoly1Matrix = poly1.calcTransformMatrix();
var oldPoly1MatrixInverted = fabric.util.invertTransform(oldPoly1Matrix);
//newMatrix = oldMatrix * someMatrix
//therefore,someMatrix = newMatrix / oldMatrix = newMatrix * inverse(oldMatrix);
var diffMatrix = fabric.util.multiplyTransformMatrices(newPoly1Matrix, oldPoly1MatrixInverted,true);
diffMatrix[4] = newPoly1Matrix[4] - oldPoly1Matrix[4];
diffMatrix[5] = newPoly1Matrix[5] - oldPoly1Matrix[5];
var oldPoly2Matrix = poly2.calcTransformMatrix();
//Apply the same someMatrix to find out the new transform matrix for poly2.
var newPoly2Matrix = fabric.util.multiplyTransformMatrices(oldPoly2Matrix, diffMatrix,true);
newPoly2Matrix[4] = oldPoly2Matrix[4] + diffMatrix[4];
newPoly2Matrix[5] = oldPoly2Matrix[5] + diffMatrix[5];
var updatedPoints = poly2.get('points')
.map(function(p) {
return new fabric.Point(p.x - poly2.minX - poly2.width / 2, p.y - poly2.minY - poly2.height / 2);
})
.map(function(p) {
return fabric.util.transformPoint(p, newPoly2Matrix);
});
oldPoly1Matrix = newPoly1Matrix;
poly2.remove();
poly2 = new fabric.Polygon(updatedPoints, destOptions);
poly2.matrix = newPoly2Matrix;
canvas.add(poly2);
}
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.js"></script>
<canvas id="c" height="500" width="600"></canvas>
<button id="update" onclick="updatePoly2()">
Update red shape
</button>
Jsfiddle - https://jsfiddle.net/btxp0ck6/1/
I am creating an enyo Control based on a canvas. It should capture mouse or finger events, and draw them onto it. However when I draw onto that canvas it draws only into a smaller part of it.
Look at that jsfiddle as it contains all relevant code.
enyo.kind({
name: "SignatureControl",
kind: "enyo.Canvas",
recording: false,
points: [],
handlers: {
ondown: "startRecord",
onmove: "record",
onup: "stopRecord"
},
startRecord: function(inSender, inEvent) {
this.recording = true;
if(node = this.hasNode()) {
this.points.push({
x: inEvent.clientX - node.offsetLeft,
y: inEvent.clientY - node.offsetTop,
d: false,
p: 1
});
}
this.update();
},
stopRecord: function() {
this.recording = false;
},
record: function(inSender, inEvent) {
if( this.recording ) {
if(node = this.hasNode()) {
this.points.push({
x: inEvent.clientX - node.offsetLeft,
y: inEvent.clientY - node.offsetTop,
d: true,
p: 1
});
}
this.update();
}
},
update: function() {
var canvas = this.hasNode();
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
this.log(ctx.canvas.width);
ctx.lineJoin = "round";
ctx.lineWidth = 1;
var i = this.points.length - 1;
ctx.strokeStyle = "rgba(0,0,0," + this.points[i].p + ")";
ctx.beginPath();
if(this.points[i].d && i){
ctx.moveTo(this.points[i-1].x, this.points[i-1].y);
}else{
ctx.moveTo(this.points[i].x-1, this.points[i].y);
}
ctx.lineTo(this.points[i].x, this.points[i].y);
ctx.closePath();
ctx.stroke();
}
}
});
You can only use the height/width attributes on the canvas, not size it via CSS. Check out this updated fiddle http://jsfiddle.net/AFqvD/4/
The relevant portion is:
{kind: "SignatureControl", attributes: {height: 150, width: 300}}