# Is it possible to dynamically change only a portion of a material attached to a plane?

Thank you to everyone in the community

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[]
* @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;
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.

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
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;
``````

Thanks.

1 Like

Hello.
Thank you for being prompt.

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

1 Like

I am so grateful to have knowledgeable people like you in this community.

This works perfectly and I can confirm that it works.

Here is the code after the change.

``````const radius = terrainManager.radius;
const intersectPosition = intersects[0].point;
const cloneGeometry = ref.current.geometry.clone();
if (!cloneGeometry.attributes.color) {
const count = cloneGeometry.attributes.position.count;
const buffer = new BufferAttribute( new Float32Array( count * 3 ), 3 );
cloneGeometry.setAttribute("color", buffer);
}
const numVertices = cloneGeometry.attributes.color.array;
let colors = new Float32Array(numVertices);
const color = new Color(terrainManager.color);
const vertex = new Vector3();
const positionArray = Array.from(cloneGeometry.attributes.position.array);
for (let i = 0; i <= positionArray.length - 3; i += 3) {
vertex.set(positionArray[i], positionArray[i + 1], positionArray[i + 2]);
vertex.applyMatrix4(ref.current.matrixWorld); // Consider rotation
const distance = vertex.distanceTo(intersectPosition);