I’m currently working on my own development editor and creating a terrain generation maker as part of its functionality.
I have succeeded in changing the terrain by changing the Z coordinates of the vertices according to the Radius range specified by PointerMove, but I would like to add the ability to paint in any color.
But I don’t know how to change the Material to a Face or Fragment within any range.
Does anyone know a specific method?
The URL of the reference site is also acceptable.
TerrainMakerCapture(GIF):
VertexChangeCode:
/**
* Get vertices in range
* @param intersects : THREE.Intersection[]
* @param radius : Brush Range Raduis
* @returns
*/
const getVertexes = (intersects: Intersection[], radius: number): { vertexIndexes: number[], values: number[] } => {
const nearVertexIntexes: number[] = []; // the index of the vertex in the range
const values: number[] = []; // distance from center
if (intersects.length > 0 && intersects[0].object) {
const object: Mesh = intersects[0].object as Mesh;
const geometry = object.geometry;
const stride = geometry.attributes.position.itemSize;
let position: Vector3;
if (intersects.length > 0) {
position = intersects[0].point;
if (position) {
const vertices = geometry.attributes.position.array;
const maxDistance = radius * radius; // squared distance
for (let i = 0; i < vertices.length; i += stride) {
const v = new Vector3(vertices[i], vertices[i + 1], vertices[i + 2]);
// Consider rotation
v.applyMatrix4(object.matrixWorld);
if (v.distanceToSquared(position) < maxDistance) {
nearVertexIntexes.push(i / stride);
values.push(1 - v.distanceToSquared(position) / maxDistance);
}
}
}
}
}
return {
vertexIndexes: nearVertexIntexes,
values: values
};
}
/**
* Shape Change Processing
**/
const intersects = raycaster.intersectObject(planeRef.current);
const { vertexIndexes, values } = getVertexes(intersects, terrainManager.radius);
if (intersects.length > 0 && intersects[0]) {
const intersectPosition = intersects[0].point;
const object: Mesh = intersects[0].object as Mesh;
if (!object) return;
vertexIndexes.map((index, i) => {
const value = values[i];
if (brush == "sculpt"){
let position = object.geometry.attributes.position;
position.setZ(
index,
(position.getZ(index) + (value * (isReverse? -1 : 1)))
);
position.needsUpdate = true;
}
else if (brush == "paint") {
// How to get a Face or Fragment and attach a material
}
});
}
Hello.
I think it is not difficult to modify the color of limited surface of model.
Now, I write my example code. I wish it will help your good work.
const colorList = new Float32Array(mesh.geometry.attributes.color.array);
const geometryPositionsArray = Array.from(mesh.geometry.getAttribute(“position”).array);
const vertex = new Three.Vector3();
const areaCenter = new Three.Vector3(area.center.x, area.center.y, area.center.z);
const color = new Three.Color(AREA_NEW_COLOR);
const rgbValues = [color.r, color.g, color.b];
for (let i = 0; i <= geometryPositionsArray.length - 3; i += 3) {
vertex.set(geometryPositionsArray[i], geometryPositionsArray[i + 1], geometryPositionsArray[i + 2]);
const distance = vertex.distanceTo(areaCenter);
// if this vertex is within the radius, color it
if (distance <= radius) {
colorList.set(rgbValues, i);
}
}
// Will only work for non indexed geometry
const colorsAttribute = new Three.BufferAttribute(colorList, 3);
mesh.geometry.setAttribute("color", colorsAttribute);
mesh.geometry.attributes.color.needsUpdate = true;
Looking at the your code, you can understood the general flow, but there was no color in the geometry attribute.
Therefore, I used uv and formatted it with the following code, but the color is not drawn well, What’s wrong?
Fixed code
const radius = 10;
const area = intersects[0].point;
const colorList = new Float32Array(ref.current.geometry.attributes.uv.count * 3);
const geometryPositionsArray = ref.current.geometry.attributes.position.array;
const vertex = new Vector3();
const color = new Color(0xff0000);
const rgbValues = [color.r, color.g, color.b];
for (let i = 0; i <= geometryPositionsArray.length - 3; i += 3) {
vertex.set(geometryPositionsArray[i], geometryPositionsArray[i + 1], geometryPositionsArray[i + 2]);
const distance = vertex.distanceTo(area);
if (distance <= radius) {
colorList.set(rgbValues, i);
}
}
const colorsAttribute = new BufferAttribute(colorList, 3);
ref.current.geometry.setAttribute("color", colorsAttribute);
ref.current.geometry.attributes.color.needsUpdate = true;
Right.
Normally, the gltf model doesn’t have color attribute first.
You can code like this before above code. I mean you can create color attribute before call it.
let cloneGeometry = mesh?.geometry.clone();
const count = cloneGeometry.attributes.position.count;
const buffer = new Three.BufferAttribute( new Float32Array( count * 3 ), 3 );
cloneGeometry.setAttribute(“color”, buffer);
Like this, you can create color attribute. then you can call it.
I wish it will be a help.
Thanks.
Hi Sho, I was checking your code and I saw you finally solved, but just a question…
is this solution performing well?
from your code I imagine this: checking thousands of vertices at each frame…
right now I am working a project with a 7000+ vertices, so imagine looping through all those vertices every 16ms…
I was trying to create this in a shader with a render target and SD function… what’s your opinion?