Text alignment within a geometry - three.js

I am trying to display text within a mesh that gets created using a component. Here is the component that creates a rectangular shape with rounded edges. I got it from github link for aframe-rounded
Here is the code for this component
AFRAME.registerComponent('rounded', {
schema: {
enabled: {default: true},
width: {type: 'number', default: 1},
height: {type: 'number', default: 1},
radius: {type: 'number', default: 0.3},
topLeftRadius: {type: 'number', default: -1},
topRightRadius: {type: 'number', default: -1},
bottomLeftRadius: {type: 'number', default: -1},
bottomRightRadius: {type: 'number', default: -1},
color: {type: 'color', default: "#F0F0F0"},
opacity: {type: 'number', default: 1}
},
init: function () {
this.rounded = new THREE.Mesh( this.draw(), new THREE.MeshPhongMaterial( { color: new THREE.Color(this.data.color), side: THREE.DoubleSide } ) );
this.updateOpacity();
this.el.setObject3D('mesh', this.rounded)
},
update: function () {
if (this.data.enabled) {
if (this.rounded) {
this.rounded.visible = true;
this.rounded.geometry = this.draw();
this.rounded.material.color = new THREE.Color(this.data.color);
this.updateOpacity();
}
} else {
this.rounded.visible = false;
}
},
updateOpacity: function() {
if (this.data.opacity < 0) { this.data.opacity = 0; }
if (this.data.opacity > 1) { this.data.opacity = 1; }
if (this.data.opacity < 1) {
this.rounded.material.transparent = true;
} else {
this.rounded.material.transparent = false;
}
this.rounded.material.opacity = this.data.opacity;
},
tick: function () {},
remove: function () {
if (!this.rounded) { return; }
this.el.object3D.remove( this.rounded );
this.rounded = null;
},
draw: function() {
var roundedRectShape = new THREE.Shape();
function roundedRect( ctx, x, y, width, height, topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius ) {
if (!topLeftRadius) { topLeftRadius = 0.00001; }
if (!topRightRadius) { topRightRadius = 0.00001; }
if (!bottomLeftRadius) { bottomLeftRadius = 0.00001; }
if (!bottomRightRadius) { bottomRightRadius = 0.00001; }
ctx.moveTo( x, y + topLeftRadius );
ctx.lineTo( x, y + height - topLeftRadius );
ctx.quadraticCurveTo( x, y + height, x + topLeftRadius, y + height );
ctx.lineTo( x + width - topRightRadius, y + height );
ctx.quadraticCurveTo( x + width, y + height, x + width, y + height - topRightRadius );
ctx.lineTo( x + width, y + bottomRightRadius );
ctx.quadraticCurveTo( x + width, y, x + width - bottomRightRadius, y );
ctx.lineTo( x + bottomLeftRadius, y );
ctx.quadraticCurveTo( x, y, x, y + bottomLeftRadius );
}
var corners = [this.data.radius, this.data.radius, this.data.radius, this.data.radius];
if (this.data.topLeftRadius != -1) { corners[0] = this.data.topLeftRadius; }
if (this.data.topRightRadius != -1) { corners[1] = this.data.topRightRadius; }
if (this.data.bottomLeftRadius != -1) { corners[2] = this.data.bottomLeftRadius; }
if (this.data.bottomRightRadius != -1) { corners[3] = this.data.bottomRightRadius; }
roundedRect( roundedRectShape, 0, 0, this.data.width, this.data.height, corners[0], corners[1], corners[2], corners[3] );
return new THREE.ShapeBufferGeometry( roundedRectShape );
},
pause: function () {},
play: function () {}
});
In order to display the text, here is how I am using it in my code
<a-entity rounded="width: 3; height: 1; color: #ff00ed; opacity: 0.4" text="align: left; baseline: bottom; anchor: left; wrapCount: 35; value: Animating color; width: 2.5" position="2 1 -3">
However, the text always shows up at the bottom or outside the mesh geometry. I have played around with values for baseline, anchor, wrapCount etc..but it never shows the text at the top of the mesh.
Here is the fiddle that shows the problem jsfiddle
Could someone please help?

Changing the value of x and y to set the position of the rounded mesh solved this issue. Specifically this piece of code
roundedRect( roundedRectShape, 0, -1, this.data.width, this.data.height, corners[0], corners[1], corners[2], corners[3] );

Related

Swap image sprite in HTML5 Canvas game

Trying to add basically different looking zombie. Is there a way to just swap the sprite or would I have to keep adding the code for class, scrolling etc.
class Zombie1 {
constructor({
position,
velocity,
distance = {
limit: 50,
traveled: 0
}
}) {
this.position = {
x: position.x,
y: position.y
}
this.velocity = {
x: velocity.x,
y: velocity.y
}
this.width = 97.49
this.height = 150
this.image = createImage(spriteZombie1)
this.frames = 0
this.distance = distance
}
draw() {
// c.fillStyle = 'red'
// c.fillRect(this.position.x, this.position.y, this.width, this.height)
c.drawImage(
this.image,
130 * this.frames,
0,
130,
150,
this.position.x,
this.position.y,
this.width,
this.height
)
}
update() {
this.frames++
if (this.frames >= 58) this.frames = 0
this.draw()
this.position.x += this.velocity.x
this.position.y += this.velocity.y
this.enemies = [];
if (this.position.y + this.height + this.velocity.y <= canvas.height)
this.velocity.y += gravity
// walk the zombie back and forth
this.distance.traveled += Math.abs(this.velocity.x)
if (this.distance.traveled > this.distance.limit) {
this.distance.traveled = 0
this.velocity.x = -this.velocity.x
}
}
}
zombies1 = [
new Zombie1({
position: {
x: 4000,
y: -950
},
velocity: {
x: -1,
y: 0
},
distance: {
limit: 7,
traveled: 0
}
}),
I have no problem adding more classes, it gets tedious and clutters the code. It won't let me post anymore code but that seems to be the important ones, other than importing the sprites.

How to levitate non-static objects with oimo.js

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);
};

Fabric JS 2D lines into 3Dviews

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>

Updating the transform matrix of one fabric object based on the changes to the transform matrix of another object

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/

Why is my canvas drawing area so smal?

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}}

Resources