Memory leak, CSG import, THREEJS - three.js

I was able to make this example working https://sbcode.net/threejs/engraving/.
I am now looking to engrave my Mesh previously imported from GLB file in the scene.
Below my code:
const loader = new GLTFLoader();
let sword
loader.load("scene/glb/object.glb", function (gltf) {
sword = gltf.scene; // sword 3D object is loaded
sword.scale.set(1, 1, 1);
sword.position.y = 0;
sword.position.x = 0;
sword.position.z = 0;
engravedMesh = sword.children[0]
engravedCSG = CSG.fromMesh(engravedMesh)
scene.add(sword);
engraving()
});
let font
function engraving() {
const loaderFont = new FontLoader()
loaderFont.load('fonts/helvetiker_regular.typeface.json', function (f) {
font = f
regenerateGeometry()
})
}
function regenerateGeometry() {
let newGeometry
newGeometry = new TextGeometry("AAAAAAAAAAAAAAAAAAAAAAAA", {
font: font,
size: 3,
height: 3,
curveSegments: 2,
})
newGeometry.center()
//bender.bend(newGeometry, 'y', Math.PI / 16)
newGeometry.translate(0, 0, 0)
//scene.add(newGeometry)
const textCSG = CSG.fromGeometry(newGeometry)
var engraved = engravedCSG.subtract(textCSG)
engravedMesh.geometry.dispose()
engravedMesh.geometry = CSG.toMesh(
engraved,
new THREE.Matrix4()
).geometry
}
When I tried to execute to execute it, my scree has frozen.
Is there something I did wrong ?

Finally it works with another glb file.
I guess I built a Sphere on Blender with too high definition.

Related

How Redux-Saga connect with Babylon-React hook

I've been doing a project to have a redux-saga react pattern to store and display the babylon scene logic, what I thoungt was distributing babylon stuff inside a single js file then export to a react fragment.
My qestion is how can we sent the data generate in babylon js by users, outside of the babylon js file (I have tried things like useState but it seemed that only work on react fragment but my babylon js is only handle for game logic.) I think if I can figure out this, I will be able to do futhur step like conncet with redux-saga.
My purpose is first of all bring the params like the position x,y,z outside createScene.js to be utilized by redux-saga, and if the user refresh the page, the scene he created won't dispear.
React newbie here seeking for suggestion, thanks in advance!
React-babylon hook below
import SceneComponent from 'babylonjs-hook'
import styled from 'styled-components'
import 'App.css'
import { onRender, onSceneReady } from '../hooks/babylonjs/createScene'
const ThreeDEditPageMain = styled.div``
const ThreeDEditPage = () => (
<ThreeDEditPageMain>
<SceneComponent antialias onSceneReady={onSceneReady} onRender={onRender} id="my-canvas" />
</ThreeDEditPageMain>
)
export default ThreeDEditPage
createScene.js below
import {
ActionManager,
ArcRotateCamera,
Color3,
ExecuteCodeAction,
HemisphericLight,
Mesh,
MeshBuilder,
StandardMaterial,
Vector3,
VertexBuffer,
} from '#babylonjs/core'
export const onSceneReady = scene => {
// This creates and positions a free camera (non-mesh)
const camera = new ArcRotateCamera('camera1', 0.4, 0.4, 50, new Vector3(0, 5, -10), scene)
// This targets the camera to scene origin
camera.setTarget(Vector3.Zero())
const canvas = scene.getEngine().getRenderingCanvas()
// This attaches the camera to the canvas
camera.attachControl(canvas, true)
camera.wheelPrecision = 50
// This creates a light, aiming 0,1,0 - to the sky (non-mesh)
const light = new HemisphericLight('light', new Vector3(0, 1, 0), scene)
// Default intensity is 1. Let's dim the light a small amount
light.intensity = 0.7
// Our built-in 'ground' shape.
const ground = MeshBuilder.CreateGround(
'ground',
{ width: 100, height: 100, subdivisions: 100 },
scene,
)
ground.updateFacetData()
// console.log(ground.facetNb)
// Our built-in 'box' shape.
const size = 4
const box = MeshBuilder.CreateBox('box', { size }, scene)
// Move the box upward 1/2 its height
// box.position.y = 1
box.position = new Vector3(size / 2, size / 2, size / 2)
box.bakeCurrentTransformIntoVertices()
box.isPickable = false
const positions = ground.getVerticesData(VertexBuffer.PositionKind)
// console.log(positions)
const snappedPosition = new Vector3()
box.position = snappedPosition
scene.onPointerMove = e => {
const pickingInfo = scene.pick(scene.pointerX, scene.pointerY)
if (pickingInfo.hit && pickingInfo.pickedMesh.name === 'ground') {
snappedPosition.x = Math.round(pickingInfo.pickedPoint.x)
snappedPosition.y = Math.round(pickingInfo.pickedPoint.y)
snappedPosition.z = Math.round(pickingInfo.pickedPoint.z)
}
}
// click action for player
ground.actionManager = new ActionManager(scene)
ground.actionManager.registerAction(
new ExecuteCodeAction(ActionManager.OnPickUpTrigger, () => {
// player clicked
console.log(
`gen a new box at x:${snappedPosition.x}, y:${snappedPosition.y}, z:${snappedPosition.z}`,
)
const genBox = Mesh.CreateBox('box', 4, scene)
genBox.position = new Vector3(snappedPosition.x, snappedPosition.y + 2, snappedPosition.z)
const mat = new StandardMaterial('mat', scene)
mat.diffuseColor = new Color3(Math.random(), Math.random(), Math.random()) // color stuff
genBox.material = mat
}),
)
}
export function onRender(sence) {
}

Applying two different fragment shaders to two different materials (of the same type) using onBeforeCompile?

I've imported a GLTF file with two different meshes. My goal is to give each mesh a material with a unique custom fragment shader using onBeforeCompile. Each mesh has the same type of material (MeshNormalMaterial).
When I try to apply one fragment shader to one material and the other fragment shader to the other material, both materials wind up with the same fragment shader. The fragment shader each material has depends on which material I setup first.
Here's a few pictures showing what I'm talking about:
Below is all the relevant code.
Main code: This is the general structure of my code. I've enclosed the important part between "PRIMARY AREA OF INTEREST" comments. For simplicity, I've replaced my shader code with "..." or a comment describing what it does. They do work as shown in the pictures above.
// Three.JS Canvas
const threeDisplay = document.getElementById("threeDisplay");
// Globals
var displayDimensions = getElemDimensions(threeDisplay); // Uniform
var currentTime = 0; // Uniform
var helix = null; // Mesh
var innerHelix = null; // Mesh
var horseshoe = null; // Mesh
// Set the scene and camera up
const scene = new THREE.Scene();
const camera = initCamera();
// Setup a directional light
const light = new THREE.DirectionalLight( 0xffffff, 1.0 );
light.position.set(-0.2, 1, -0.6);
scene.add(light);
// Setup WebGL renderer
const renderer = initRenderer();
threeDisplay.appendChild( renderer.domElement );
// Load the gltf model
new GLTFLoader().load( "./spiral_pillar_hq_horseshoe.glb", function (object) {
const helixFragmentShaderReplacements = [
{
from: ' ... ',
to: ' // rainbow '
}
];
const horseshoeFragmentShaderReplacements = [
{
from: ' ... ',
to: ' // white '
}
];
//////////////////////////////////////
// PRIMARY AREA OF INTEREST - START //
//////////////////////////////////////
// Turn the horseshoe into a shader.
horseshoe = object.scene.children[1];
var horseshoeGeometry = horseshoe.geometry;
var horseshoeMaterial = shaderMeshMaterial(new THREE.MeshNormalMaterial(), horseshoeGeometry, horseshoeFragmentShaderReplacements);
var horseshoeMesh = new THREE.Mesh(horseshoeGeometry, horseshoeMaterial);
horseshoe = horseshoeMesh;
horseshoe.rotation.z = deg2rad(180); // Re-orient the horseshoe to the correct position and rotation.
horseshoe.position.y = 13;
scene.add(horseshoe);
// Turn the inner helix into a colorful, wiggly shader.
helix = object.scene.children[0];
var helixGeometry = helix.geometry;
var helixMaterial = shaderMeshMaterial(new THREE.MeshNormalMaterial(), helixGeometry, helixFragmentShaderReplacements);
var helixMesh = new THREE.Mesh(helixGeometry, helixMaterial);
helix = helixMesh;
scene.add(innerHelix);
animate();
////////////////////////////////////
// PRIMARY AREA OF INTEREST - END //
////////////////////////////////////
}, undefined, function (error) {
console.error(error);
});
Below are functions which are relevant.
shaderMeshMaterial: Constructs a new material based on the supplied materialType that supports editing the default shader. If it's not initProcessing, then the problem may stem from this function.
// Globals used: displayDimensions
function shaderMeshMaterial(materialType, geometry, fragmentShaderReplacements) {
var material = materialType;
material.onBeforeCompile = function ( shader ) {
// Uniforms
shader.uniforms.time = { value: 0 };
shader.uniforms.resolution = { value: new THREE.Vector2(displayDimensions.width, displayDimensions.height) };
shader.uniforms.bboxMin = { value: geometry.boundingBox.min };
shader.uniforms.bboxMax = { value: geometry.boundingBox.max };
fragmentShaderReplacements.forEach((rep) => {
shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
});
console.log(shader);
material.userData.shader = shader;
}
return material;
}
initRenderer: Sets up the renderer. Just showing you guys the renderer setup I have in case that's important.
// Globals used: displayDimensions
function initRenderer() {
var renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
precision: "mediump"
});
renderer.setClearColor( 0x000000, 0);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( displayDimensions.width, displayDimensions.height );
renderer.shadowMap.enabled = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.25;
return renderer;
}
animate: Handles the animation frames.
// Globals used: renderer, currentTime, postprocessing
function animate (timestamp = 0) {
requestAnimationFrame(animate);
resizeRendererToDisplaySize(renderer);
currentTime = timestamp/1000; // Current time in seconds.
scene.traverse( function ( child ) {
if ( child.isMesh ) {
const shader = child.material.userData.shader;
if ( shader ) {
shader.uniforms.time.value = currentTime;
}
}
} );
renderer.render( scene, camera );
postprocessing.composer.render( 0.1 );
};
One last thing to note is that when I inspected the console log of shader from the shaderMeshMaterial function, I can see that the fragment shaders are indeed different as they should be for each material. Also not sure why there are 4 console logs when there should only be 2.
Sorry for all the code, but I did condense it to where all irrelevant code was stripped out. I'm fairly new to Three.JS, so any possible explanations as to why this is happening are much appreciated!
EDIT: Removed vertex shader parameter from shaderMeshMaterial function to keep this question focused on just the fragment shaders. Though this problem does apply to both the vertex and fragment shaders, I figure if you fix one then you'll fix the other.
EDIT 2: Added language identifiers to code snippets. Also I removed the postprocessing function and the problem still persists, so I know the problem isn't caused by that. I've updated the code above to reflect this change. As a happy side effect of removing the postprocessing function, the console.log of the shader variable from shaderMeshMaterial new appears twice in the log (as it should).
EDIT 3: (Implementing WestLangley's suggestion) I tweaked the shaderMeshMaterial function by adding the customProgramCacheKey function. I had to condense the four parameters of shaderMeshMaterial into one for the sake of the customProgramCacheKey function. I believe I implemented the function correctly, but I'm still getting the same result as before where both materials display the same fragment shader.
New "PRIMARY AREA OF INTEREST" code:
horseshoe = object.scene.children[1];
var horseshoeGeometry = horseshoe.geometry;
var meshData = {
materialType: new THREE.MeshNormalMaterial(),
geometry: horseshoeGeometry,
fragmentShaderReplacements: horseshoeFragmentShaderReplacements
}
var horseshoeMaterial = shaderMeshMaterial(meshData);
var horseshoeMesh = new THREE.Mesh(horseshoeGeometry, horseshoeMaterial);
horseshoe = horseshoeMesh;
horseshoe.rotation.z = deg2rad(180); // Re-orient the horseshoe to the correct position and rotation.
horseshoe.position.y = 13;
scene.add(horseshoe);
// Turn the inner helix into a colorful, wiggly shader.
helix = object.scene.children[0];
var helixGeometry = helix.geometry;
var meshData2 = {
materialType: new THREE.MeshNormalMaterial(),
geometry: helixGeometry,
fragmentShaderReplacements: helixFragmentShaderReplacements
}
var helixMaterial = shaderMeshMaterial(meshData2);
var helixMesh = new THREE.Mesh(helixGeometry, helixMaterial);
helix = helixMesh;
scene.add(innerHelix);
animate();
New shaderMeshMaterial code:
// Globals used: displayDimensions
function shaderMeshMaterial(meshData) {
var material = meshData.materialType;
material.onBeforeCompile = function ( shader ) {
// Uniforms
shader.uniforms.time = { value: 0 };
shader.uniforms.resolution = { value: new THREE.Vector2(displayDimensions.width, displayDimensions.height) };
shader.uniforms.bboxMin = { value: meshData.geometry.boundingBox.min };
shader.uniforms.bboxMax = { value: meshData.geometry.boundingBox.max };
meshData.fragmentShaderReplacements.forEach((rep) => {
shader.fragmentShader = shader.fragmentShader.replace(rep.from, rep.to);
});
material.customProgramCacheKey = function () {
return meshData;
};
console.log(shader);
material.userData.shader = shader;
}
return material;
}
WestLangley suggestion worked for me!
material.onBeforeCompile = ...
// Make sure WebGLRenderer doesnt reuse a single program
material.customProgramCacheKey = function () {
return UNIQUE_PER_MATERIAL_ID;
};
I believe your mistake is returning meshData from customProgramCacheKey.
I think customProgramCacheKey need concrete identifier like a number or string.
It would be nice to understand what exactly happening and why do we need to specify customProgramCacheKey.
EDIT: I discover that default value for customProgramCacheKey calculated as follow in Threejs source.
customProgramCacheKey() {
return this.onBeforeCompile.toString();
}
Perhaps this is explains this default caching behavior because calling toString on function returns that function body literally as string.
For example consider function const myFunc = () => { return 1 }. Calling myFunc.toString() returns "() => { return 1 }"
So if your calling onBeforeCompile in a for loop you function body as string never change.

How to dynamically change texture of PIXI.Sprite when PIXI.Sprite reaches certain position - Pixi.js?

I have a class which extends PIXI.Sprite. Here i create the sprite initially. The texture i use is a spritesheet and i create sprites from random sections of this spritesheet.png by creating random frames for the texture. There I add 10000 sprites and move them in random directions. Then I add the PIXI.Sprite class in another class which extends PIXI.ParticleContainer 10,000 times.
createTexture() {
this.textureWidth = 2048;
this.rectX = () => {
let number;
while (number % 32 !== 0) number = Math.floor(Math.random() * this.textureWidth) + 0;
return number;
}
this.rectY = () => {
let number;
while (number % 32 !== 0) number = Math.floor(Math.random() * 128) + 0;
return number;
}
this.initialTexture = PIXI.Texture.from(this.resources[assets.images[0].src].name);
this.rectangle = new PIXI.Rectangle(this.rectX(), this.rectY(), 32, 32);
this.initialTexture.frame = this.rectangle;
this.texture = new PIXI.Texture(this.initialTexture.baseTexture, this.initialTexture.frame);
this.texture.requiresUpdate = true;
this.texture.updateUvs();
this.timesChangedVy = 0;
}
When a Sprite hits window borders, i call the method change texture in the class of PIXI.Sprite:
changeTexture() {
let newTexture = PIXI.Texture.from(this.resources[assets.images[0].src].name);
let rectangle = new PIXI.Rectangle(this.rectX(), this.rectY(), 32, 32);
newTexture.frame = rectangle;
// this.texture.frame = rectangle
this.texture = newTexture;
// this.texture = new PIXI.Texture.from(this.resources[assets.images[0].src].name)
// this.texture._frame = rectangle
// this.texture.orig = rectangle
// this._texture = newTexture
// this.texture = new PIXI.Texture(newTexture.baseTexture, rectangle)
this.texture.update()
this.texture.requiresUpdate = true;
this.texture.updateUvs();
}
I tried different approaches. When i console.log the texture after changing it , i see that the frame and origins have been changed, but the new texture is not being rendered.
Does someone know where the problem lies and how i can fix it?
Finally, I found the reason for my sprites not updating on texture change.
It is because I add them as children of Pixi.ParticleContainer, which has less functionality than Pixi.Container and does not update Uvs of children by default.
THE SOLUTION IS TO SET uvs to true when creating PIXI.ParticleContainer.
It looks like this: new PIXI.ParticleContainer(10000, { uvs: true }).
This will solve the problem of changing textures not being updated and uvs will be uploaded and applied.
https://pixijs.download/dev/docs/PIXI.ParticleContainer.html

THREE.Euler().copy issue with revision 88 and 89

the following code does not seem to work with revision 88 and beyond:
function moveAndLookAt(dstpos, dstlookat, duration = 500) {
TWEEN.removeAll();
var origpos = new THREE.Vector3().copy(camera.position); // original position
var origrot = new THREE.Euler().copy(camera.rotation); // original rotation
camera.position.set(dstpos.x, dstpos.y, dstpos.z);
camera.lookAt(dstlookat);
var dstrot = new THREE.Euler().copy(camera.rotation)
// reset original position and rotation
camera.position.set(origpos.x, origpos.y, origpos.z);
camera.rotation.set(origrot.x, origrot.y, origrot.z);
// position
new TWEEN.Tween(this.camera.position).to({
x: dstpos.x,
y: dstpos.y,
z: dstpos.z
}, duration).start();;
// rotation (using slerp)
let scope = this;
(function () {
var qa = new THREE.Quaternion().copy(camera.quaternion); // src quaternion
var qb = new THREE.Quaternion().setFromEuler(dstrot); // dst quaternion
var qm = new THREE.Quaternion();
var o = {t: 0};
new TWEEN.Tween(o).to({t: 1}, duration).onUpdate(function () {
THREE.Quaternion.slerp(qa, qb, qm, o.t);
scope.camera.quaternion.set(qm.x, qm.y, qm.z, qm.w);
}).start();
}).call(this);
}
The target object seem to disappear and no errors. It looks like the dstrot = new THREE.Euler().copy(camera.rotation) value is returning NaN instead of numeric numbers. The code was from a previous post:
Tween camera position while rotation with slerp -- THREE.js
Nevermind, my dstlookat worked fine when I was passing {x:2,y:2,z:2} with previous versions but the latest version seem to require defined Vector3 values.
Solution:
var dstlookat = {x:2, y: 2, z: 2};
to:
dstlookat = new THREE.Vector3( 2, 2, 2 );

Three.js combining particles

I have a problem with three.js. i have two particle system set ups that seem to be conflicting with each other.
The first scene loads up without problem, but when the second set loads the first set of particles vanish. This wouldn't be too confusing if it weren't for the the fact that the rest of the first scene is still appearing in the entire set up.
Is there an easy way to rename or call in the two sets of particles?
I've looked around but can't find a ref to this.
the one thing that i think might be causing this is the PARTICLE_COUNT call - which features in both scripts...
in one it is
var PARTICLE_COUNT = 15000;
var MAX_DISTANCE = 1500;
var IMAGE_SCALE = 5;
followed by
for(var i = 0; i < PARTICLE_COUNT; i++) {
geometry.vertices.push(new THREE.Vertex());
var star = new Star();
stars.push(star);
}
and the second
AUDIO_FILE = 'songs/zircon_devils_spirit',
PARTICLE_COUNT = 250,
MAX_PARTICLE_SIZE = 12,
MIN_PARTICLE_SIZE = 2,
GROWTH_RATE = 5,
DECAY_RATE = 0.5,
BEAM_RATE = 0.5,
BEAM_COUNT = 20,
GROWTH_VECTOR = new THREE.Vector3( GROWTH_RATE, GROWTH_RATE, GROWTH_RATE ),
DECAY_VECTOR = new THREE.Vector3( DECAY_RATE, DECAY_RATE, DECAY_RATE ),
beamGroup = new THREE.Object3D(),
particles = group.children,
colors = [ 0xaaee22, 0x04dbe5, 0xff0077, 0xffb412, 0xf6c83d ],
t, dancer, kick;
followed by
dancer = new Dancer();
kick = dancer.createKick({
onKick: function () {
var i;
if ( particles[ 0 ].scale.x > MAX_PARTICLE_SIZE ) {
decay();
} else {
for ( i = PARTICLE_COUNT; i--; ) {
particles[ i ].scale.addSelf( GROWTH_VECTOR );
}
}
if ( !beamGroup.children[ 0 ].visible ) {
for ( i = BEAM_COUNT; i--; ) {
beamGroup.children[ i ].visible = true;
}
}
},
offKick: decay
});
dancer.onceAt( 0, function () {
kick.on();
}).onceAt( 8.2, function () {
scene.add( beamGroup );
}).after( 8.2, function () {
beamGroup.rotation.x += BEAM_RATE;
beamGroup.rotation.y += BEAM_RATE;
}).onceAt( 50, function () {
changeParticleMat( 'white' );
}).onceAt( 66.5, function () {
changeParticleMat( 'pink' );
}).onceAt( 75, function () {
changeParticleMat();
}).fft( document.getElementById( 'fft' ) )
.load({ src: AUDIO_FILE, codecs: [ 'ogg', 'mp3' ]})
Dancer.isSupported() || loaded();
!dancer.isLoaded() ? dancer.bind( 'loaded', loaded ) : loaded();
Bit of a "needle lost in a haystack" i know...
But maybe someone can see the error of my ways!
I've tried updating the revision of three.js but r47 was as up to date as i could get it - my knowledge of three.js and dancer.js is very limited...
i also tried to create a jsfiddle - but as the earliest version on there is r54 jsfiddle won't work when i put it together... shows only parts of the whole thing... not the working version...
but maybe just the bare bones might be thing...
this one http://jsfiddle.net/wwfc/3L5z5mx…
is for the file min.'s (which drives the animated/moving particles that go from one shape to another...
and this one http://jsfiddle.net/wwfc/v96L3kq…
is the one that calls and sets up the audio reactive particles...
this is the one that the particles (not the beams just particles) vanish from when min.'s loads up...
i can see where both scripts create the particles that are clashing but no idea of how to remedy it :-(
is there anything glaringly obvious that i need to be addressing?

Resources