I am looking to connect plane buffer geometry grid tiles which have real elevation data from IndexedDB. My issue is the data resolution on the STRM elevation is not perfect so the edges between the tiles are not the same. I need to essentially average out all the grid edges between the touching vertices to create a seamless terrain.
When I copy paste the code into the console in the scene it works. However just in the code it doesn't. The sceneRef that is passed is valid and the rest of the codebase using the sceneRef correctly.
The tiles are a 3 x 3 with the current grid tile being the center at 1,1 from range 0,0 - 2,2.
function connectTiles(currGridKey, sceneRef){
console.log("connectTiles");
console.log("currGridKey");
// Current Tile Connection
for (var lat = 0; lat < currGridKey[0]+2; lat++) {
for (var long = 0; long < currGridKey[1]+2; long++) {
const currentTile = sceneRef.getObjectByName(`${lat}-${long}`);
// Current Grid Tile Per Loop
if (currentTile) {
const currentTileVerts = currentTile.geometry.attributes.position.array,
latPlusTile = sceneRef.getObjectByName(`${lat}-${long+1}`),
longPlusTile = sceneRef.getObjectByName(`${lat+1}-${long}`);
// Connect Latitudinally
if (latPlusTile) {
const latPlusTileVerts = latPlusTile.geometry.attributes.position.array;
for (var z = 0; z < currentTileVerts.length; z+=27) {
const newVertHeight = (currentTileVerts[z] + latPlusTileVerts[z]) / 2;
latPlusTileVerts[z] = newVertHeight;
currentTileVerts[z] = newVertHeight;
}
latPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
// Connection Longitudinally
if (longPlusTile) {
const longPlusTileVerts = longPlusTile.geometry.attributes.position.array;
for (var x = 0; x < currentTileVerts.length; x+=3) {
const newVertHeight = (currentTileVerts[x] + longPlusTileVerts[x]) / 2;
longPlusTileVerts[x] = newVertHeight;
currentTileVerts[x] = newVertHeight;
}
longPlusTile.geometry.attributes.position.needsUpdate = true;
currentTile.geometry.attributes.position.needsUpdate = true;
}
}
}
}
If all values inside the array are in fact being updated, maybe they're just not getting uploaded to the GPU. Instead of changing the value inside geometry.attributes.position directly, try using the .setAttribute() method. The docs state that using .setAttribute() and .getAttribute() is preferrable than accessing it directly because it has its own internal storage mechanism.
const latPlusTileVerts = latPlusTile.geometry.getAttribute("position").array;
// ... Loops
latPlusTile.geometry.getAttribute("position").needsUpdate = true;
// Or an alternative is to generate a new attribute...
// in case updating the old one fails
const posAttrib = new THREE.BufferAttribute(latPlusTileVerts, 3);
latPlusTile.geometry.setAttribute("position", posAttrib);
Related
I find the visibility algorithm in one great website
Since it is quiet long ago article, I am not sure that the author will give the answer.
As you can see, the interactive playground(try it yourself) does not work well in right upper side, especially overlapping edges.
// Pseudocode of main algorithm
var endpoints; # list of endpoints, sorted by angle
var open = []; # list of walls the sweep line intersects
loop over endpoints:
remember which wall is nearest
add any walls that BEGIN at this endpoint to 'walls'
remove any walls that END at this endpoint from 'walls'
figure out which wall is now nearest
if the nearest wall changed:
fill the current triangle and begin a new one
Here is also part of working JavaScript code
// Main algorithm with working Javascript code
export const calculateVisibility = (origin, endpoints) => {
let openSegments = [];
let output = [];
let beginAngle = 0;
endpoints.sort(endpointCompare);
for(let pass = 0; pass < 2; pass += 1) {
for (let i = 0; i < endpoints.length; i += 1) {
let endpoint = endpoints[i];
let openSegment = openSegments[0];
if (endpoint.beginsSegment) {
let index = 0
let segment = openSegments[index];
while (segment && segmentInFrontOf(endpoint.segment, segment, origin)) {
index += 1;
segment = openSegments[index]
}
if (!segment) {
openSegments.push(endpoint.segment);
} else {
openSegments.splice(index, 0, endpoint.segment);
}
} else {
let index = openSegments.indexOf(endpoint.segment)
if (index > -1) openSegments.splice(index, 1);
}
if (openSegment !== openSegments[0]) {
if (pass === 1) {
let trianglePoints = getTrianglePoints(origin, beginAngle, endpoint.angle, openSegment);
output.push(trianglePoints);
}
beginAngle = endpoint.angle;
}
}
}
return output;
};
In my opinion, it happens since the algorithm thinks right edge is shown whole part when it is the closest edge although it can be hidden from whole edges.
I think I can modify the algorithm to use the current closest edge and newer current closest edge but not sure...
How should I fix it?
I am trying to connect multiple PannerNodes to an output but I have a problem with proper connecting them (while testing I have no audio). Audio is multichannel so I want to process each channel separately and audio is form html video element.
My main problem is that I have 6 different PannerNodes each one have one output with 2 channels and I don't know how to properly connect them to the destination to have a stereo output (or any output but my system is supporting only stereo).
Here is what I am doing:
Creating audioContext, MediaElementSource, etc. and first connection.
const context = new AudioContext();
var source = context.createMediaElementSource(video);
var dest = context.createMediaStreamDestination();
//Spliter channels L, R, SL, SR, C, LFE
let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 6});
// I didn't used that later because I don't know if that is necessary and how to implement it properly (with that line commented it code is still not working correctly)
let channel_merger = new ChannelMergerNode(context, {numberOfInputs: 2});
let listener = context.listener;
source.connect(splitter, 0, 0);
Setup the listener
if(listener.forwardX) {
listener.forwardX.setValueAtTime(0, audioCtx.currentTime);
listener.forwardY.setValueAtTime(0, audioCtx.currentTime);
listener.forwardZ.setValueAtTime(-1, audioCtx.currentTime);
listener.upX.setValueAtTime(0, audioCtx.currentTime);
listener.upY.setValueAtTime(1, audioCtx.currentTime);
listener.upZ.setValueAtTime(0, audioCtx.currentTime);
} else {
listener.setOrientation(0,0,-1,0,1,0);
}
Creates PannerNodes and connects it to a splitter
var FrontLeft, FrontCenter, FrontRight, SurroundLeft, SurroundRight, Sub = null;
var pannerNodesObjects = [FrontLeft, FrontCenter, FrontRight, SurroundLeft, SurroundRight, Sub];
function set_pannerNode(node, panningModel /* 'HRTF' */, distanceModel /* Possible values are "linear", "inverse" and "exponential". The default value is "inverse". */, refDistance, maxDistance, rolloffFactor, coneInnerAngle, coneOuterAngle, coneOuterGain, x, y, z /* position */, n /* index of the pannerNodesObjects */) {
node = context.createPanner();
// Seting options
node.panningModel = panningModel;
node.distanceModel = distanceModel;
node.refDistance = refDistance;
node.maxDistance = maxDistance;
node.rolloffFactor = rolloffFactor;
node.coneInnerAngle = coneInnerAngle;
node.coneOuterAngle = coneOuterAngle;
node.coneOuterGain = coneOuterGain;
// Setting position
node.positionX = x;
node.positionY = y;
node.positionZ = z;
pannerNodesObjects[n] = node;
}
for (i=0; i< pannerNodesObjects.length; i++){
[nx, ny, nz] = set_rotation(distanceFromScreen,screenCenterY,angleList[i],i); // Here I get a position of the PannerNode
set_pannerNode(pannerNodesObjects[i],'HRTF',"exponential", 1,100,2,360,0,0, nx, ny, nz, i);
splitter.connect(pannerNodesObjects[i], i);
}
Connect PannerNodes to destination
for (i=0; i< pannerNodesObjects.length; i++){
pannerNodesObjects[i].connect(dest);
}
After that I am updating each PannerNode position in a loop. But I have no audio while playing the video so I probably done something wrong with AudioNodes connection.
I have a class, which contains a parameter called values. This is used to save values of points that represent specific shapes on a canvas.
I need to implement a functionality that lets me drag those shapes around, so I need to modify each specific point of the shape, removing from them the amount that was dragged.
So I decided that, as I trigger my mousedown event (which is the method StartMove), i would save the values of my points on a startValues variable, and as I move my mouse around (method move), I would then update the values, using startValues and the distance between the starting point and the current mouse position to determine my new point location.
The problem is, this.startValues is actually getting changed to match this.values every time my cursor moves, and I have no idea why. Is there anything simple I'm missing?
Since I store my values as values, and not coordinates (helps me with panning and zooming on the canvas), I first convert the values to position, then modify the position and then convert it back to a value. I've included the parent class, Grf, so you can see the methods which change values to position and position to values.
Class with the problems
class Test {
constructor(grf){
this.grf = grf; // Another class, which contains important methods
this.values = [];
this.startValues = [];
}
startMove(p0){ // p0 = [x,y]
const {grf} = this;
this.startValues = [...this.values]; //I also tried this.startValues = this.values
this.p0 = p0;
grf.canvas.addEventListener('mousemove',this.move);
grf.canvas.addEventListener('mouseup', this.endMove);
}
move = (evt) => { // arrow function so 'this' is bound to Test class instead of grf.canvas
const {grf, p0, values, startValues} = this;
const coords = grf.canvas.getBoundingClientRect();
const px = evt.clientX - coords.left;
const py = evt.clientY - coords.top;
for (let i = 0, iMax = this.values.length; i < iMax; i++){
values[i][0] = grf.valX(grf.posX(startValues[0]) - (p0[0] - px));
values[i][1] = grf.valY(grf.posY(startValues[1]) - (p0[1] - py));
}
console.log(this.startValues); // It changes to the same as this.values
}
endMove = (evt) => { // arrow function so 'this' is bound to Test class instead of grf.canvas
const {grf} = this;
grf.canvas.removeEventListener('mousemove',this.move);
grf.canvas.removeEventListener('mouseup',this.endMove);
}
}
The other class
class Grf {
constructor(canvas){ // Not the actual constructor, just an example of what the values could look like
this.translateX = 1000;
this.translateY = 1000;
this.scaleY = 10.7;
this.scaleX = 11.2;
this.canvas = canvas;
}
posX (value){
return (value-this.translateX)*this.scaleX;
}
posY (value){
return (this.canvas.height-(100*(value))-this.translateY)*this.scaleY;
};
valX(pos){
return (pos/this.scaleX) + this.translateX
}
valY(pos){
return (-1)*((pos/this.scaleY) + this.translateY - this.canvas.height)/100
}
}
How values are inserted into startValues and values in Test class? You probably insert exactly the same object in both without coping it so both arrays hold the same instances.
Take a look at the example:
const obj = { a : 10 };
const a = [];
a.push(obj);
const b = [...a]; // creates new array, but with same objects
a[0].a = 20;
console.log(b[0]) // gives "{ a : 20 }"
To make it separate you need to make a copy of a object:
a.push({...obj})
I am using a HalfEdge data structure to represent the connectivity between the faces on my mesh.
I am importing an external model, and I am constructing the HalfEdge structure during the import process. However, with meshes with many triangles, the construction process takes up too much time.
Specifically, it appears that the process of linking the half-edges take up the most time.
I would like to get some advice on how to improve my algorithm.
Below is the code I am using to initialize my data structure. The first for-loop creates a Face with the vertices data, while pushing the HalfEdges that compose the Face into a separate array to be used momentarily after.
The second for-loop is reponsible for looking into the array of all HalfEdges, and finding matching pairs (i.e., the two that are twins of one another).
I logged out the time before and after each process, and noticed that the second loop is what slows everything down.
Here are the time stamps
start constructing DCEL 14:55:22
start making faces 14:55:22
end making faces 14:55:22
/* this is where it takes long.. almost 6 seconds on a mesh with 13000 triangles */
start linking halfEdges 14:55:22
end linking halfEdges 14:55:28
end constructing DCEL 14:55:28
And here is the actual code
console.log('start constructing DCEL', new Date().toTimeString());
// initialize Half-Edge data structure (DCEL)
const initialFaceColor = new THREE.Color(1, 1, 1);
const { position } = geometry.attributes;
const faces = [];
const edges = [];
let newFace;
console.log('start making faces', new Date().toTimeString());
for (let faceIndex = 0; faceIndex < (position.count / 3); faceIndex++) {
newFace = new Face().create(
new THREE.Vector3().fromBufferAttribute(position, faceIndex * 3 + 0),
new THREE.Vector3().fromBufferAttribute(position, faceIndex * 3 + 1),
new THREE.Vector3().fromBufferAttribute(position, faceIndex * 3 + 2),
faceIndex);
edges.push(newFace.edge);
edges.push(newFace.edge.next);
edges.push(newFace.edge.prev);
newFace.color = initialFaceColor;
faces.push(newFace);
}
console.log('end making faces', new Date().toTimeString());
console.log('start linking halfEdges', new Date().toTimeString());
/**
* Find and connect twin Half-Edges
*
* if two Half-Edges are twins:
* Edge A TAIL ----> HEAD
* = =
* Edge B HEAD <---- TAIL
*/
let currentEdge;
let nextEdge;
for (let j = 0; j < edges.length; j++) {
currentEdge = edges[j];
// this edge has a twin already; skip to next one
if (currentEdge.twin !== null) continue;
for (let k = j + 1; k < edges.length; k++) {
nextEdge = edges[k];
// this edge has a twin already; skip to next one
if (nextEdge.twin !== null) continue;
if (currentEdge.head().equals(nextEdge.tail())
&& currentEdge.tail().equals(nextEdge.head())) {
currentEdge.setTwin(nextEdge);
}
}
}
console.log('end linking halfEdges', new Date().toTimeString());
console.log('end constructing DCEL', new Date().toTimeString());
How can I optimize the process of searching for twin edges?
I'd try to hash and look up the edges, e.g. like that:
function hash(p1, p2) {
return JSON.stringify(p1)+JSON.stringify(p2);
}
const lookup = {}
for (let j = 0; j < edges.length; j++) {
lookup[hash(edge.head(), edge.tail())] = edge;
}
for (let j = 0; j < edges.length; j++) {
const twin = lookup[hash(edge.tail(), edge.head())];
!edge.twin && twin && !twin.twin && edge.setTwin(twin);
}
I have a mighty strange JavaScript problem. I have made an object oriented maze generator, which works well, but only if I call "this" (or the alias "self") right before the generator.
See code below:
// Constructor for a maze
function Maze(mazeWidth, mazeHeight) {
// Always working reference to this
var self = this;
// Has the maze been generated?
var generated = false;
// Default dimensions
var width = 20;
var height = 20;
// Check if dimensions are given
if (!isNaN(mazeWidth) && mazeWidth >= 1) {
width = parseInt(mazeWidth);
}
if (!isNaN(mazeHeight) && mazeHeight >= 1) {
height = parseInt(mazeHeight);
}
// The maze itself
var maze = {};
// Populate the maze
for (var y = 0; y < height; y++) {
maze[y] = {};
for (var x = 0; x < width; x++) {
maze[y][x] = new MazeCell(x, y);
}
}
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
}
// For some mighty strange reason "self" (or "this") needs to be called here for the code below to work
self;
// Generate the maze
(function generateMaze() {
// Map directions to its reverse
var directionMap = {};
directionMap[Maze.prototype.N] = Maze.prototype.S;
directionMap[Maze.prototype.E] = Maze.prototype.W;
directionMap[Maze.prototype.S] = Maze.prototype.N;
directionMap[Maze.prototype.W] = Maze.prototype.E;
// Depth-first search to generate the maze
(function DFS(cell, entryDirection) {
// Set the cell as discovered and open the entry direction
cell._setDiscovered();
cell._open(entryDirection);
// Find the neighbour cells
var neighbours = {};
neighbours[Maze.prototype.N] = cell.getNeighbourCell(Maze.prototype.N);
neighbours[Maze.prototype.E] = cell.getNeighbourCell(Maze.prototype.E);
neighbours[Maze.prototype.S] = cell.getNeighbourCell(Maze.prototype.S);
neighbours[Maze.prototype.W] = cell.getNeighbourCell(Maze.prototype.W);
// Check the neighbour cells in random order
for (var i = 0; i < 4; i++) {
var direction = (function() {
var result;
var count = 0;
for (var direction in neighbours) {
if (Math.random() < 1/++count)
result = direction;
}
return result;
})();
var nextCell = neighbours[direction];
delete neighbours[direction];
if (nextCell == false)
continue;
if (nextCell._isDiscovered())
continue;
// Set exit opening of this cell
cell._open(direction);
// Process next cell
DFS(nextCell, directionMap[direction]);
}
})(self.getCell(Math.floor(Math.random()*width), Math.floor(Math.random()*height)), null); // This line is the problem
})();
// ......
If I don't call "self" above the generation code, this.getCell will be called, but the first parameter will be a reference to the generateMaze-function itself. The second parameter will be unset.
It also work if I change the dummy line from "self" to "this".
Just writing "self" (or "this") on an otherwise empty line doesn't really do anything, does it? Why is it needed?
You should add semicolons after assigning a value to an attribute or variable, even if the value is a function, like here:
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
}
It should look like this:
// Function to get a cell
this.getCell = function(x, y) {
return maze[y][x];
};
I think that might be the cause of your problem.