Animate threeJS light color with tweenmax - three.js

I would like to change my threeJS lights colors with a tweenmax timeline but it doesn't seems to work.
Here is kind of what I tried. Am I doing something wrong?
var light = new THREE.HemisphereLight( "hsl(0,0%,87%);", 0xFFFFFF, 1);
var light2 = new THREE.PointLight( 0x69be94, 1, 15, 2 );
var light3 = new THREE.SpotLight( 0xFFFFE0, 1, 30, Math.PI/14, 1 );
...
var tl = new TimelineMax();
tl.to(light.color.setHSL, 1, { h : 1, s : 0.4, l : 0.58 });
tl.to(light2.color.setHex, 1, { hex: "#69BE94" });
tl.to(light3.color.setRGB, 1, { r: 105, g:190 ,b:148 }, 1,);

Notice that .setHSL, .setHex, & .setRGB are all methods, AKA functions. You can't point to a method as the first argument of TweenMax, it has to be a property. You'll need to do something like this:
tl.to(light3.color, 1, {r: 0.3, g:0.7, b:0.5}, 1,); So the timeline can access it.
Also, keep in mind that color .rgb values are in the [0, 1] range, not [0, 255]. If you need to tween to a specific HSL value, just use a temporary color variable:
// Create temp variable to extract RGB values
var tempColor = new THREE.Color().setHSL(1, 0.4, 0.58);
tl.to(light.color, 1, { r: tempColor.r, g: tempColor.g, b: tempColor.b });
// Extract RGB values from tempColor again
tempColor.setHex(0x69BE94);
tl.to(light2.color, 1, { r: tempColor.r, g: tempColor.g, b: tempColor.b });
.setHex takes an integer, not a string

#Marquizzo's answer works but if you actually want to tween HSL and not RGB then you need a different solution.
As pointed out tweenmax adjust properties so you can make an object that uses getters and setters so it has properties for tweenmax to adjust but ends up setting the color.
class HSLHelper {
constructor(color) {
this.color = color;
this.temp = {h: 0, s: 0, l: 0};
}
get h() { return this.color.getHSL(this.temp).h; }
set h(h) {
const {s, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get s() { return this.color.getHSL(this.temp).s; }
set s(s) {
const {h, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get l() { return this.color.getHSL(this.temp).l; }
set l(l) {
const {h, s} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
}
And now you can use it, example.
const p = new THREE.PointLight( 0x69be94, 1, 15, 2 );
const helper = new HSLHelper(p.color);
var tl = new TimelineMax();
tl.to(helper, 1, { h : 1, s : 0.4, l : 0.58 });
Compare the results: the cube on the left is using RGB tweening, the cube on the right is using HSL tweening
class HSLHelper {
constructor(color) {
this.color = color;
this.temp = {h: 0, s: 0, l: 0};
}
get h() { return this.color.getHSL(this.temp).h; }
set h(h) {
const {s, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get s() { return this.color.getHSL(this.temp).s; }
set s(s) {
const {h, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get l() { return this.color.getHSL(this.temp).l; }
set l(l) {
const {h, s} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
}
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const material1 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const material2 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const cube1 = new THREE.Mesh(geometry, material1);
scene.add(cube1);
cube1.position.x = -1;
const cube2 = new THREE.Mesh(geometry, material2);
scene.add(cube2);
cube2.position.x = 1;
const tl1 = new TimelineMax();
tl1.fromTo(material1.color, {r: 1, g: 0, b: 0}, {duration: 2, r: 0, g: 0, b: 1, ease:Linear.easeNone});
tl1.repeat(-1);
const helper2 = new HSLHelper(material2.color);
const tl2 = new TimelineMax();
tl2.fromTo(helper2, {h: 0, s: 1, l: 0.5}, {duration: 2, h: 0.66, s: 1, l: 0.5, ease:Linear.easeNone});
tl2.repeat(-1);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap#3.0.1/dist/gsap.min.js"></script>
<canvas id="c"></canvas>
Also note that the H (hue) in HSL is unbounded 0 = red, 0.33 = green, 0.66 = blue, 1 = red, 1.33 = green, 1.66 = blue, etc.. So for example if I set h to go from 0 to 10 over 5 seconds it will go through all the colors 10 times over 5 seconds
class HSLHelper {
constructor(color) {
this.color = color;
this.temp = {h: 0, s: 0, l: 0};
}
get h() { return this.color.getHSL(this.temp).h; }
set h(h) {
const {s, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get s() { return this.color.getHSL(this.temp).s; }
set s(s) {
const {h, l} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
get l() { return this.color.getHSL(this.temp).l; }
set l(l) {
const {h, s} = this.color.getHSL(this.temp);
this.color.setHSL(h, s, l);
}
}
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const material1 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const material2 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const cube1 = new THREE.Mesh(geometry, material1);
scene.add(cube1);
cube1.position.x = -1;
const cube2 = new THREE.Mesh(geometry, material2);
scene.add(cube2);
cube2.position.x = 1;
const helper1 = new HSLHelper(material1.color);
const tl1 = new TimelineMax();
tl1.fromTo(helper1, {h: 0, s: 1, l: 0.5}, {duration: 5, h: 1, s: 1, l: 0.5, ease:Linear.easeNone});
tl1.repeat(-1);
const helper2 = new HSLHelper(material2.color);
const tl2 = new TimelineMax();
tl2.fromTo(helper2, {h: 0, s: 1, l: 0.5}, {duration: 5, h: 10, s: 1, l: 0.5, ease:Linear.easeNone});
tl2.repeat(-1);
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap#3.0.1/dist/gsap.min.js"></script>
<canvas id="c"></canvas>

While both Marquizzo and gman are in the right direction, it's actually even more simple in GSAP! You can just animate the .color to values that you set (it's easiest to use a new THREE.Color object). For example:
// The new colors in whatever format you want
const newColor1 = new THREE.Color("hsl(120, 100%, 50%)");
const newColor2 = new THREE.Color("blue");
const tl1 = gsap.timeline({repeat: -1});
tl1.to(material1.color, {duration: 5, r: newColor1.r, g: newColor1.g, b: newColor1.b, ease:"none"});
const tl2 = gsap.timeline({repeat: -1});
tl2.to(material2.color, {duration: 5, r: newColor2.r, g: newColor2.g, b: newColor2.b, ease:"none"});
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
const material1 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const material2 = new THREE.MeshBasicMaterial({color: 0xFF0000});
const cube1 = new THREE.Mesh(geometry, material1);
scene.add(cube1);
cube1.position.x = -1;
const cube2 = new THREE.Mesh(geometry, material2);
scene.add(cube2);
cube2.position.x = 1;
// The new colors in whatever format you want
const newColor1 = new THREE.Color("hsl(120, 100%, 50%)");
const newColor2 = new THREE.Color("blue");
const tl1 = gsap.timeline({repeat: -1});
tl1.to(material1.color, {duration: 5, r: newColor1.r, g: newColor1.g, b: newColor1.b, ease:"none"});
const tl2 = gsap.timeline({repeat: -1});
tl2.to(material2.color, {duration: 5, r: newColor2.r, g: newColor2.g, b: newColor2.b, ease:"none"});
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap#3.2.1/dist/gsap.min.js"></script>
<canvas id="c"></canvas>

Related

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

How to use line with width of three.js with morphTargets to animate one line moving to another

question reference
I want to implement an animation.
The animation should be a line move to another line. There will be some deformation in the process of the line moving
There is a correspondence between the points of the two lines.
I can use the basic line of three.js to animate one line moving to another.
<script type="module">
import * as THREE from '../../build/three.module.js'
import { OrbitControls } from '../jsm/controls/OrbitControls.js'
function main() {
var scene = new THREE.Scene()
const geometry = new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute([-2, 0, 0, -0.5, 0, -0.5, 0, 0, -2], 3))
const geometry1 = new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute([5, 0, 0, 1, 1, 1, 0, 0, 5], 3))
geometry.morphAttributes.position = [
geometry1.attributes.position,
]
var material = new THREE.LineBasicMaterial({
color: 0x0000ff,
})
var mesh = new THREE.Line(geometry, material)
mesh.morphTargetInfluences[0] = 0
scene.add(mesh)
/**
*
*/
var width = window.innerWidth
var height = window.innerHeight
var k = width / height
var s = 10
var camera = new THREE.OrthographicCamera(
-s * k,
s * k,
s,
-s,
-1500,
1500
)
camera.position.set(10, 10, 10)
camera.lookAt(10, 0, 0)
/**
*
*/
var renderer = new THREE.WebGLRenderer()
// window.renderer = renderer;
renderer.setSize(width, height)
renderer.setClearColor(0xb9d3ff, 1)
document.body.appendChild(renderer.domElement)
// renderer.render(scene, camera);
new OrbitControls(camera, renderer.domElement)
var clock = new THREE.Clock()
const axes = new THREE.AxesHelper(10)
scene.add(axes)
let flag = false
let index = 0
function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
if (index <0) {
flag = true
} else if (index > 1) {
flag = false
}
if (flag) {
index += 0.01
} else {
index -= 0.01
}
mesh.morphTargetInfluences[0] = index
console.log(flag, index)
}
render()
}
main()
</script>
Now I want to use the LineGeometry of three.js because this line has a width property. How to use the morphtargets with LineGeometry?
If the LineGeometry can't do it,any other bufferGeometry to replace LineGeometry?
Three.js 136.
<script type="module">
import * as THREE from '../../build/three.module.js'
import { OrbitControls } from '../jsm/controls/OrbitControls.js'
import { Line2 } from '../jsm/lines/Line2.js'
import { LineMaterial } from '../jsm/lines/LineMaterial.js'
import { LineGeometry } from '../jsm/lines/LineGeometry.js'
function main() {
var scene = new THREE.Scene()
const geometry2 = new LineGeometry()
geometry2.setPositions([-3, 0, 0, -0.5, 0, -0.5, 0, 0, -2])
console.log(geometry2)
const geometry1 = new LineGeometry()
geometry1.setPositions([-3, 0, 0, -1, 0, -1, 0, 0, -3])
geometry2.morphAttributes.position = [geometry1.attributes.position]
let matLine = new LineMaterial({
color: '#ffdb2d',
linewidth: 5, // in world units with size attenuation, pixels otherwise
})
let line = new Line2(geometry2, matLine)
line.morphTargetInfluences[0] = 0
scene.add(line)
/**
*/
var width = window.innerWidth
var height = window.innerHeight
var k = width / height
var s = 5
var camera = new THREE.OrthographicCamera(
-s * k,
s * k,
s,
-s,
-1500,
1500
)
camera.position.set(5, 5, 5)
camera.lookAt(5, 0, 0)
var renderer = new THREE.WebGLRenderer()
// window.renderer = renderer;
renderer.setSize(width, height)
renderer.setClearColor(0xb9d3ff, 1)
document.body.appendChild(renderer.domElement)
// renderer.render(scene, camera);
new OrbitControls(camera, renderer.domElement)
var clock = new THREE.Clock()
const axes = new THREE.AxesHelper(10)
scene.add(axes)
let flag = false
let index = 0
function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
if (index < 0) {
flag = true
} else if (index > 1) {
flag = false
}
if (flag) {
index += 0.01
} else {
index -= 0.01
}
line.morphTargetInfluences[0] = index
matLine.resolution.set(window.innerWidth, window.innerHeight) // resolution of the viewport
}
render()
}
main()
</script>
Use TubeGeometry:
const curve = new THREE.CatmullRomCurve3([new THREE.Vector3(-2, 0, 0), new THREE.Vector3(-0.5, 0, -0.5), new THREE.Vector3(0, 0, -2)])
var tubeGeometry = new THREE.TubeGeometry(curve, 100, 0.05, 50, false);
const curve1 = new THREE.CatmullRomCurve3([new THREE.Vector3(0, 0, 5,), new THREE.Vector3( 1, 1, 1,), new THREE.Vector3(5, 0, 0,)])
var tubeGeometry1 = new THREE.TubeGeometry(curve1, 100, 0.05, 50, false);
tubeGeometry.morphAttributes.position = [
tubeGeometry1.attributes.position,
]
var tubeMaterial = new THREE.MeshBasicMaterial({
side:THREE.DoubleSide,
color:'#ffffff',
});
var tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
tube.morphTargetInfluences[0] = 0
scene.add(tube)
In the render function:
tube.morphTargetInfluences[0] += 0.01

THREE.JS Level of Detail Texture Loading

Here is the small Three.JS sketch with THREE.LOD() objects.
As you can see there are 4 levels with their unique textures.
By now, all these textures are preloaded on start.
Is there any way to load 1, 2, 3 levels textures on the fly while zooming in?
Yes, I could do same thing without THREE.LOD() just by coding my own custom algorithm, which would generate/remove planes on zooming, but I'm very interesting in built-in THREE.LOD().
var folder = "http://vault.vkuchinov.co.uk/test/assets";
var levels = [0xF25E6B, 0x4EA6A6, 0x8FD9D1, 0xF2B29B, 0xF28E85];
var renderer, scene, camera, controls, loader, lod, glsl, uniforms;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-2048, 2048, -2048);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 5120;
controls.maxPolarAngle = Math.PI / 2;
lod = new THREE.LOD();
lod.name = "0,0";
generateTiles(lod, 2048, 2048, 2048, 0, 0x00FFFF);
scene.add(lod);
animate();
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function generateTiles(parent_, width_, height_, zoom_, level_, hex_){
var id = parent_.name.split(",");
var colors = [0xFFFF00, 0xFF000, 0x00FF00, 0x0000FF, 0xFF00FF, 0xF0F0F0];
var group = new THREE.Group(), geometry, plane;
var dx = 0, dy = 0;
dy *= Math.pow(2, level_); dx *= Math.pow(2, level_);
var url = folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg";
if(level_ < 3){
var uniforms = {
satellite: {
type: "t",
value: loader.load(url)
}
};
var glsl = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl.extensions.derivatives = true;
geometry = new THREE.PlaneGeometry(width_, height_, 256, 256);
plane = new THREE.Mesh(geometry, glsl);
plane.rotation.set(-Math.PI / 2, 0, 0);
parent_.addLevel(plane, zoom_);
geometry = new THREE.PlaneGeometry(width_ / 2, height_ / 2, 128, 128);
var ix = (Number(id[0]) * 2);
var iy = (Number(id[1]) * 2);
var lod1 = new THREE.LOD();
var url1 = getURL(ix + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms1 = {
satellite: {
type: "t",
value: loader.load(url1)
}
};
var glsl1 = new THREE.ShaderMaterial({
uniforms: uniforms1,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl1.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl1);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod1.addLevel(plane, zoom_ / 2);
lod1.position.set(-width_ / 4, 0, -height_ / 4);
lod1.name = ix + "," + iy;
group.add(lod1);
var lod2 = new THREE.LOD();
var url2 = getURL(ix + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms2 = {
satellite: {
type: "t",
value: loader.load(url2)
}
};
var glsl2 = new THREE.ShaderMaterial({
uniforms: uniforms2,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl2.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl2);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod2.addLevel(plane, zoom_ / 2);
lod2.position.set(width_ / 4, 0, -height_ / 4);
lod2.name = ix + "," + (iy + 1);
group.add(lod2);
var lod3 = new THREE.LOD();
var url3 = getURL((ix + 1) + "," + iy, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms3 = {
satellite: {
type: "t",
value: loader.load(url3)
}
};
var glsl3 = new THREE.ShaderMaterial({
uniforms: uniforms3,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl3.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl3);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod3.addLevel(plane, zoom_ / 2);
lod3.position.set(-width_ / 4, 0, height_ / 4);
lod3.name = (ix + 1) + "," + iy;
group.add(lod3);
var lod4 = new THREE.LOD();
var url4 = getURL((ix + 1) + "," + (iy + 1), width_ / 2, height_ / 2, zoom_ / 2, level_ + 1);
var uniforms4 = {
satellite: {
type: "t",
value: loader.load(url4)
}
};
var glsl4 = new THREE.ShaderMaterial({
uniforms: uniforms4,
vertexShader: document.getElementById("vertexTerrain").textContent,
fragmentShader: document.getElementById("fragmentTerrain").textContent,
lights: false,
fog: false,
transparent: true
});
glsl4.extensions.derivatives = true;
plane = new THREE.Mesh(geometry, glsl4);
plane.rotation.set(-Math.PI / 2, 0, 0);
lod4.addLevel(plane, zoom_ / 2);
lod4.position.set(width_ / 4, 0, height_ / 4);
lod4.name = (ix + 1) + "," + (iy + 1);
group.add(lod4);
parent_.addLevel(group, zoom_ / 2);
generateTiles(lod1, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod2, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod3, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
generateTiles(lod4, width_ / 2, height_ / 2, zoom_ / 2, level_ + 1, colors[level_]);
}
}
function getURL(name_, width_, height_, zoom_, level_){
var id = name_.split(",");
return folder + "/textures/level" + level_ + "/" + id[0] + "_" + id[1] + ".jpg";
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GLSL Intersection</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<script id="vertexTerrain" type="x-shader/x-vertex">
uniform sampler2D satellite;
varying vec2 vUv;
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fragmentTerrain" type="x-shader/x-fragment">
precision highp float;
precision highp int;
uniform sampler2D satellite;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(satellite, vUv);
}
</script>
</body>
</html>
Looking at the code you could scan your lods, see what their current level is, and check if it's loaded or not?
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/controls/OrbitControls.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 500;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const controls = new OrbitControls(camera, canvas);
controls.update();
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const numLevels = 4;
const lodInfos = [];
function createLod(pos) {
const lod = new THREE.LOD();
lod.position.set(...pos);
scene.add(lod);
for (let level = 0; level < numLevels; ++level) {
const obj = new THREE.Object3D();
lod.addLevel(obj, 3 + Math.pow(2, level));
}
lodInfos.push({
lod,
levels: [],
});
}
createLod([0, 0, 0]);
function scanLods() {
for (const {lod, levels} of lodInfos) {
const level = lod.getCurrentLevel();
if (!levels[level]) {
// this level is not loaded
levels[level] = true; // mark it as loaded
// load it
loadLodLevel(level, lod.levels[level].object);
// optimization: if all levels are loaded
// remove this from the lodInfos
}
}
}
function loadLodLevel(level, obj) {
// obviously I'd use some kind of data structure but just to
// get something working
let geometry;
let material;
switch(level) {
case 0:
geometry = new THREE.BoxBufferGeometry(1, 1, 1);
material = new THREE.MeshPhongMaterial({color: 'red'});
break;
case 1:
geometry = new THREE.SphereBufferGeometry(0.5, 12, 6);
material = new THREE.MeshPhongMaterial({color: 'yellow'});
break;
case 2:
geometry = new THREE.ConeBufferGeometry(0.5, 1, 12);
material = new THREE.MeshPhongMaterial({color: 'green'});
break;
case 3:
geometry = new THREE.CylinderBufferGeometry(0.5, 0.5, 1, 12);
material = new THREE.MeshPhongMaterial({color: 'purple'});
break;
}
const lodMesh = new THREE.Mesh(geometry, material);
obj.add(lodMesh);
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
scanLods();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
The solution above adds a THREE.Object3D for each lod and then childs a mesh to it when it's made visible.
You could also replace the THREE.Object3D so instead of
obj.add(lodMesh);
it would be something like
obj.levels[level].object = lodMesh;
obj.parent.add(lodMesh);
obj.parent.remove(obj);

Unable to cast a shadow with THREE.js and Mapbox GL

I'm trying to add a THREE.js scene into a Mapbox GL visualization following this example. I've added a sphere and a ground plane and a DirectionalLight. Now I'm trying to get the light to cast a shadow on the ground plane. Adding a DirectionalLightHelper and a CameraHelper for the light's shadow camera, everything looks pretty reasonable to me:
I'd expect to see a shadow for the sphere on the plane.
Full code here, but here are the highlights:
class SpriteCustomLayer {
type = 'custom';
renderingMode = '3d';
constructor(id) {
this.id = id;
this.gui = new dat.GUI();
THREE.Object3D.DefaultUp.set(0, 0, 1);
}
async onAdd(map, gl) {
this.camera = new THREE.Camera();
const centerLngLat = map.getCenter();
this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
const {x, y, z} = this.center;
this.cameraTransform = new THREE.Matrix4()
.makeTranslation(x, y, z)
.scale(new THREE.Vector3(1, -1, 1));
this.map = map;
this.scene = this.makeScene();
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
this.renderer.shadowMap.enabled = true;
this.renderer.autoClear = false;
}
makeScene() {
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
const s = this.center.meterInMercatorCoordinateUnits();
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0.000002360847837325531, 0.000004566603480958114, 0.00000725142167844218);
light.target.position.set(0, 0, 0);
light.castShadow = true;
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
light.shadow.camera.left = -0.000002383416166278454 * 2;
light.shadow.camera.right = 0.000002383416166278454 * 2;
light.shadow.camera.bottom = -0.000002383416166278454 * 2;
light.shadow.camera.top = 0.000002383416166278454 * 2;
light.shadow.camera.near = 0.0000012388642793465356;
light.shadow.camera.far *= s;
scene.add(light);
this.light = light;
{
const planeSize = 500;
const loader = new THREE.TextureLoader();
const texture = loader.load('/checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = 10;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.scale.setScalar(s);
plane.receiveShadow = true;
scene.add(plane);
}
{
const sphereRadius = 5e-7;
const sphereGeo = new THREE.SphereBufferGeometry(sphereRadius, 32, 32);
const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(0, 0, 5e-6);
mesh.castShadow = true;
mesh.receiveShadow = false;
sphereMat.side = THREE.DoubleSide;
scene.add(mesh);
}
return scene;
}
render(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
}
Mapbox GL JS uses a coordinate system where the entire world is in [0, 1] so the coordinates are pretty tiny. It also uses x/y for lat/lng and z for up, which is different than usual Three.js coordinates.
How can I get the shadow to appear? I'm using Three.js r109 and Mapbox GL JS 1.4.0. I've tried replacing the PlaneBufferGeometry with a thin BoxGeometry to no avail.
EDIT
Forget everything I said in my old answer.
The example below scales things WAY down and the shadow remains.
The kicker was here:
shadowLight.shadow.camera.near *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.far *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.updateProjectionMatrix(); // <========= !!!!!
I was updating the scale, but wasn't updating the near/far of the shadow camera. Then, once I was, I was forgetting to update that camera's projection matrix. With all the pieces back together, it seems to be working well.
Try adding a call to update the shadow-casting light's camera's projection matrix after you configure the values.
If it still doesn't work, maybe you can use my example to figure out what's going on in your code.
If MY example doesn't work for you, then it might be your hardware doesn't support the level of precision you need.
// just some random colors to show it's actually rendering
const colors = [
0xff0000, // 1e+1
0x00ff00, // 1e+0
0x0000ff, // 1e-1
0xffff00, // 1e-2
0xff00ff, // 1e-3
0x00ffff, // 1e-4
0xabcdef, // 1e-5
0xfedcba, // 1e-6
0x883300, // 1e-7
0x008833, // 1e-8
0x330088, // 1e-9
0x338800 // 1e-10
];
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.shadowMap.enabled = true; // turn on shadow mapping
renderer.setClearColor(0xcccccc);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(28, 1, 1, 1000)
camera.position.set(25, 10, 15);
camera.lookAt(0, 0, 0);
const camLight = new THREE.PointLight(0xffffff, 1);
camera.add(camLight);
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(50, 50),
new THREE.MeshPhongMaterial({
color: "gray"
})
);
floor.receiveShadow = true;
floor.rotation.set(Math.PI / -2, 0, 0);
floor.position.set(0, -1, 0);
const sphere = new THREE.Mesh(
new THREE.SphereBufferGeometry(2, 16, 32),
new THREE.MeshPhongMaterial({
color: colors[0]
})
);
sphere.castShadow = true;
sphere.position.set(0, 1, 0);
const shadowLight = new THREE.PointLight(0xffffff, 1);
shadowLight.castShadow = true;
shadowLight.position.set(-10, 10, 5);
const group = new THREE.Group();
group.add(floor);
group.add(sphere);
group.add(shadowLight);
group.add(camera);
scene.add(group);
function render() {
renderer.render(scene, camera);
}
function resize() {
const W = window.innerWidth;
const H = window.innerHeight;
renderer.setSize(W, H);
camera.aspect = W / H;
camera.updateProjectionMatrix();
}
window.onresize = resize;
resize();
render();
let scaler = 10;
let scaleLevel = 10;
let scaleLevelOutput = document.getElementById("scaleLevel");
let scaleDown = true;
let colorIndex = 0;
setInterval(() => {
colorIndex += (scaleDown) ? 1 : -1;
scaleLevel *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.near *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.far *= (scaleDown) ? 0.1 : 10;
shadowLight.shadow.camera.updateProjectionMatrix();
if (scaleLevel < 1e-9 && scaleDown) {
scaleDown = false;
}
if (scaleLevel >= 10 && !scaleDown) {
scaleDown = true;
}
scaleLevelOutput.innerText = `SCALE LEVEL: ${scaleLevel.toExponential()}`;
group.scale.set(scaleLevel, scaleLevel, scaleLevel);
sphere.material.color.setHex(colors[colorIndex]);
sphere.material.needsUpdate = true;
render();
}, 1000);
body {
margin: 0;
overflow: hidden;
}
#scaleLevel {
font-family: monospace;
font-size: 2em;
position: absolute;
top: 0;
left: 0;
font-weight: bold;
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<div id="scaleLevel">SCALE LEVEL: 1e+1</div>

How to show only edge lines in three.js?

I want to show lines only on the edges. Here I have included my output model which I tried using edgeGeometry and LinebasicMaterial. I want to remove the inner edge lines and show only outline edges
You can use EdgesGeometry
You pass it some other geometry and a threshold angle
// only show edges with 15 degrees or more angle between faces
const thresholdAngle = 15;
const lineGeometry = new THREE.EdgesGeometry(geometry, thresholdAngle));
'use strict';
/* global THREE */
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 40;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 20;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xAAAAAA);
let solidMesh;
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(1, -2, -4);
scene.add(light);
}
const objects = [];
const spread = 15;
function addObject(x, y, obj) {
obj.position.x = x * spread;
obj.position.y = y * spread;
scene.add(obj);
objects.push(obj);
}
function createMaterial() {
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
});
const hue = Math.random();
const saturation = 1;
const luminance = .5;
material.color.setHSL(hue, saturation, luminance);
return material;
}
function addSolidGeometry(x, y, geometry) {
const mesh = new THREE.Mesh(geometry, createMaterial());
addObject(x, y, mesh);
return mesh;
}
function addLineGeometry(x, y, geometry) {
const material = new THREE.LineBasicMaterial({color: 0x000000});
const mesh = new THREE.LineSegments(geometry, material);
addObject(x, y, mesh);
return mesh;
}
{
const shape = new THREE.Shape();
const x = -2.5;
const y = -5;
shape.moveTo(x + 2.5, y + 2.5);
shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
const extrudeSettings = {
steps: 2,
depth: 2,
bevelEnabled: true,
bevelThickness: 1,
bevelSize: 1,
bevelSegments: 2,
};
const geometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSettings);
solidMesh = addSolidGeometry(0, 0, geometry);
const thresholdAngle = 15;
addLineGeometry(0, 0, new THREE.EdgesGeometry(geometry, thresholdAngle));
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
solidMesh.visible = (time | 0) % 2 !== 0;
objects.forEach((obj, ndx) => {
const speed = .1 + ndx * .0;
const rot = time * speed;
obj.rotation.x = rot;
obj.rotation.y = rot;
});
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r108/build/three.min.js"></script>

Resources