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):
terrain

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;

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

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.

terrain2

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);
  if (distance <= radius) {
    colors.set(color.toArray(), i);
  }
}
cloneGeometry.setAttribute("color", new BufferAttribute(colors, 3));
ref.current.geometry.copy(cloneGeometry);

I’m very glad for hearing your work is going well.
In the future, I wish we will help each other.
Cheers.

2 Likes