I'm working with react-three-fiber and struggling with Line2 and some memory issues. I left my application running for a time and then when I came back my computer was frozen... After I recovered I realized that the the application was gradually consuming more and more memory and sometimes, but not always, showing this error: "webgl warning: drawelementsinstanced: instance fetch requires 30, but attribs only supply 20". I tried changing the getPoints divisions and the number of points in the line itself and I was able to make things a little bit better, but it still didn't solve the issue. The problem seems to be comming from the 'getPoints' method, but I'm not quite so sure. Can someone help me?
export function SingleTrace({
particleSystem,
physics,
steps,
detail,
width,
color,
}) {
const line = useRef();
let positions = useMemo(() => [], [physics]);
useFrame(() => {
let stepsSoFar = positions.length;
if (stepsSoFar === 0) {
let randomX = ((Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x) as number;
let randomY = ((Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y) as number;
let randomZ = ((Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z) as number;
positions = [vec(randomX, randomY, randomZ)]; //otherwise just use a random position
} else if (stepsSoFar > steps) {
positions.shift();
}
let lastPosition = positions[positions.length - 1];
let totalField = vec();
particleSystem.particles.forEach((particle) => {
let field = particle["physics"][physics].field(
lastPosition
);
totalField.add(field);
});
let newPosition = vec()
.copy(lastPosition)
.add(vec().copy(totalField).setLength(detail));
if (particleSystem.boundary.contains(newPosition)) {
positions.push(newPosition);
}
let willRestart = Math.random() < 0.05 ? true : false;
if (willRestart) {
let randomX =
(Math.random() - 1 / 2) * particleSystem.boundary.w +
particleSystem.boundary.x;
let randomY =
(Math.random() - 1 / 2) * particleSystem.boundary.h +
particleSystem.boundary.y;
let randomZ =
(Math.random() - 1 / 2) * particleSystem.boundary.d +
particleSystem.boundary.z;
positions = [vec(randomX, randomY, randomZ)];
}
if (line.current && positions.length > 2) {
let curve = new THREE.CatmullRomCurve3(positions).getPoints(
positions.length
);
let convertedToArray = [];
curve.forEach((item) => convertedToArray.push(item.x, item.y, item.z));
line.current.setPositions(convertedToArray);
}
});
return (
<>
<line2>
<lineGeometry ref={line} />
<lineMaterial
color={color}
linewidth={width}
resolution={new THREE.Vector2(10, 10)}
/>
</line2>
</>
);
}
P.S. vec() is just new THREE.Vector3(0,0,0)
Related
I am trying to create a random terrain in THREE JS using the plane geometry and randomizing the pos array using this method:
let side = 200;
const geometry = new PlaneGeometry(80, 20, side, 100);
// const material = new MeshStandardMaterial({ color: '#271033'});
let material = new THREE.MeshStandardMaterial({
roughness: 1,
color: new THREE.Color('#271033'),
flatShading: true,
});
const plane = new Mesh(geometry, material);
plane.rotation.x = - Math.PI / 2 - Math.PI / 20;
plane.position.y = - 4;
plane.position.z = - 5;
plane.castShadow = true;
plane.receiveShadow = true;
let pos = geometry.getAttribute("position");
let pa = pos.array as any;
const hVerts = geometry.parameters.heightSegments + 1;
const wVerts = geometry.parameters.widthSegments + 1;
let noise2D = createNoise2D();
for (let j = 0; j < hVerts; j++) {
for (let i = 0; i < wVerts; i++) {
const ex = 1.1;
pa[3 * (j * wVerts + i) + 2] =
( noise2D(i / 100, j / 100) + noise2D((i + 100) / 50, j / 50) * Math.pow(ex, 0) );
}
}
When I try to sample points on the mesh(so that I can place tree's or whatever at said point) the points don't seem to be valid points on the Mesh. I believe it may be return points from a plane that didn't receive the rotation/position transform, but I'm not sure.
Here is the code for the sampling:
const plane = createPlane();
plane1Sampler = new MeshSurfaceSampler(plane).build();
// add plane to scene etc...
for ( let i = 0; i < 10; i ++ ) {
const tree = await loadTree();
const _position = new THREE.Vector3();
const _normal = new THREE.Vector3();
plane1Sampler.sample( _position, _normal );
tree.scale.set(0.1, 0.1, 0.1);
tree.position.set(_position.x, _position.y, _position.z);
this.scene.add( tree );
}
Finally, here's a picture of the result, the tree's should be positioned on the first lighter purple plane. I'm not really sure what the issue here is so any help is very much appreciated! Also, the project is in React and TS.
For whatever reason the sampler was indeed not sampling from a plane with the translations applied. By applying the same translation to the points I was able to get valid points.
// translation applied to the plane, which for some reason the sampler doesn't take into account
_position.applyAxisAngle(new Vector3(1, 0, 0), - Math.PI / 2 - Math.PI / 20);
_position.y -= 6.1;
_position.z -= 5;
I'm trying to create an array of meshes I added to my THREE.JS scene because I used a for-loop with the load() function to create the instances of the meshes, and I wanted to reference them later for the animate() function. The array shows a list of values in the console, but the length shows up as 0 when logged. This is the code I have:
const manager = new THREE.LoadingManager(); //loading manager
const loader = new GLTFLoader(manager);
let pos_arr = []
manager.onLoad = function(e) {
scene.traverse( function (model) {
if (model.isMesh) {
pos_arr.push(model)
}
})
}
let pos = new THREE.Vector3()
//spheres and equators
let ball = new THREE.Mesh();
let equator = new THREE.Mesh()
for (let i = 1; i <= 12; i++) {
let scale = Math.floor(Math.random() * 3) + 1
let pos_x = Math.floor(Math.random() * 10) * 5 - 25
let pos_y = Math.floor(Math.random() * 10) * 5 - 25
let pos_z = Math.floor(Math.random() * 10) * 5 - 25
pos = new THREE.Vector3(pos_x, pos_y, pos_z)
loader.load( './ball.gltf', function ( gltf ) { //load sphere
gltf.scene.traverse(function(model) {
if (model.isMesh) {
model.castShadow = true;
model.material = sphereMaterial;
}
});
ball = gltf.scene
ball.position.set(pos_x, pos_y, pos_z)
ball.scale.set(scale, scale, scale)
scene.add( ball );
}, undefined, function ( error ) {
console.error( error );
} );
loader.load( './equator.gltf', function ( gltf2 ) {
gltf2.scene.traverse(function(model) { //for gltf shadows!
if (model.isMesh) {
model.castShadow = true
model.material = equatorMaterial
}
});
equator = gltf2.scene
equator.position.set(pos_x, pos_y, pos_z)
equator.scale.set(scale, scale, scale)
scene.add( equator )
}, undefined, function ( error ) {
console.error( error )
} );
}
//light
const light = new THREE.AmbientLight( 0xffffff );
scene.add( light );
console.log(pos_arr)
console.log(pos_arr.length)
This is what the console shows:
I got a recommendation that I use the onLoad attribute for the LoadingManager, which I used, but it didn't work. If anyone happens to know a possible solution, it would be greatly appreciated :)
When I click on the spin button, I want the value to be highlighted one by one in the canvas.
Here you can see the whole code: https://codesandbox.io/s/spinning-wheel-react-forked-s528m
renderWheel() {
// determine number/size of sectors that need to created
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
this.setState({
angle: arcSize
});
// dynamically generate sectors from state list
let angle = 0;
for (let i = 0; i < numOptions; i++) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
}
}
renderSector(index, text, start, arc) {
// create canvas arc for each list element
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
let x = canvas.width / 2;
let y = canvas.height / 2;
let radius = 200;
let startAngle = start;
let endAngle = start + arc - 0.02;
let angle = index * arc;
let baseSize = radius * 1.25;
let textRadius = baseSize - 48;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, false);
ctx.lineWidth = radius * 0.25;
ctx.strokeStyle = "white";
ctx.font = "25px Arial";
ctx.fillStyle = "blue";
ctx.stroke();
ctx.save();
ctx.translate(
baseSize + Math.cos(angle - arc / 2) * textRadius,
baseSize + Math.sin(angle - arc / 2) * textRadius
);
ctx.rotate(0);
ctx.fillText(text, -ctx.measureText(text).width / 2, 7);
ctx.restore();
}
When I click on the spin button, this spin () method is executed.
The iteration ends between two and five seconds as written in the setTimeout method.
spin = () => {
var index = 0;
let timer = setInterval(() => {
index = (index) % this.state.list.length + 1;
console.log(index)
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Remember: In canvas you cannot access a particular object, so the solution is always to erase and redraw. Don't worry about performance, this is pretty great. I leave you an idea with your code and at the end some suggestions. Try this code, the only thing I did is clean the canvas and redraw without the setTimeOut index
spin = () => {
let canvas = document.getElementById("wheel");
let ctx = canvas.getContext("2d");
var index = 0;
index = (index % this.state.list.length) + 1;
let angle = 0;
let numOptions = this.state.list.length;
let arcSize = (2 * Math.PI) / numOptions;
let timer = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.setState({
angle: arcSize
});
for (let i = 0; i < numOptions; i++) {
if (i !== index) {
let text = this.state.list[i];
this.renderSector(i + 1, text, angle, arcSize);
angle += arcSize;
} else {
}
}
}, 150);
// calcalute result after wheel stops spinning
setTimeout(() => {
clearInterval(timer);
this.setState({
hasFinishedLoading: true,
display: this.state.list[index]
});
}, Math.floor(Math.random() * 5000 + 2000));
this.setState({
spinning: true
});
};
Don't use "getElementById" in react, it's dangerous. Use a reference.
I am using DataMap, unlike the normal view for a map, I turned the map upside down, When mouseover, the tooltip location doesn't correspond, to the location of the mouse when I mouseover
I have tried different codes, in other to get accurate coordinates, but none is giving me what I need.
In the map react component
<Map mapRef= {this.myMap} makeMouseMove={this._onMouseMove} />
1st try
_onMouseMove = (e) => {
if (document.querySelector('.hoverinfo')) {
let mapTooltip = document.querySelector('.datamaps-hoverover');
let rect = e.target.getBoundingClientRect();
mapTooltip.style.left = e.clientX - rect.left + 'px';
mapTooltip.style.top = e.clientY - rect.top + 'px';
}
}
2nd Try
_onMouseMove = (e) => {
if (document.querySelector('.hoverinfo')) {
let mapTooltip = document.querySelector('.datamaps-hoverover');
mapTooltip.style.left = e.pageX - mapTooltip.offsetLeft+'px';
mapTooltip.style.left = e.pageY - mapTooltip.offsetTop+'px';
}
}
Unfortunately, I have not been able to achieve what I want to achieve, I would appreciate if someone that has experience handling this Datamap issue helps me with a clue.
In case someone else is having this type of issue, this is what I did to resolve it, apparently, the left value is correct, and there was no need for me to modify it.
_onMouseMove = (e) => {
if (document.querySelector('.hoverinfo')){
let mapTooltip = document.querySelector('.datamaps-hoverover');
let mapHoverSVG = document.querySelector('svg');
let point = mapHoverSVG.createSVGPoint();
point.x = e.clientX;
point.y = e.clientY;
let cursor = point.matrixTransform(mapHoverSVG.getScreenCTM().inverse());
mapTooltip.style.top = (cursor.y + 16) + "px";
console.log(cursor.y);
}
}
I have multiple firebase items that I am looping through and they have name, categoryId, lat, lon.
I am trying to calculate a distance from 2 lat,lon (one is the one in firebase), the other is the user's location.
All of this is fine, and I can calculate it fine too. However, how do I inject/map the new variable into the firebase array that I am subscribed to?
this.categoryId = this.navParams.get('categoryId');
afoDatabase.list('/list', {query: {
orderByChild: "categoryId",
equalTo: parseInt(this.categoryId)
}}).subscribe(listItems => {
this.items = listItems; //need to add distance within array
loadingPopup.dismiss().catch(() => {});
});
For example, I have a variable called distance, that calculates everything and has the value. How do I add it in the this?items - so I can call it from the HTML side?
Thanks for the help :)
To inject, first create a function and create a variable (e.g. this.distance) and get the value of your function on this variable. After that use a for loop to increment, based on length - and use this.items[i]["distance"] to add to each one. For example:
this.categoryId = this.navParams.get('categoryId');
afoDatabase.list('/list', {query: {
orderByChild: "categoryId",
equalTo: parseInt(this.categoryId)
}}).subscribe(listItems => {
this.items = listItems;
this.geolocation.getCurrentPosition({timeout:15000}).then((resp) => {
this.myLat = resp.coords.latitude;
this.myLong = resp.coords.longitude;
}).catch((error) => {
console.log('Error getting location', error);
});
for (var i = 0, len = this.items.length; i < len; i++) {
this.distance = this.calculateDistance(this.myLat, this.myLong, this.items[i].lat, this.items[i].lng);
this.items[i]["distance"] = Math.round(this.distance);
console.log('testing myLat', this.myLat)
console.log('testing myLong', this.myLong)
}
console.log('testing inject', this.items)
loadingPopup.dismiss().catch(() => {});
});
Function
calculateDistance(lat1:number,lon1:number,lat2:number,lon2:number){
let R = 6371; // km
let dLat = (lat2 - lat1) * Math.PI / 180;
let dLon = (lon2 - lon1) * Math.PI / 180;
let a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let d = R * c;
return d;
}