Three.js - Extruye cierto vértice / cara de BufferGeometry
Hice un new THREE.PlaneBufferGeometry(100, 100, 100, 100);
y he podido actualizar la posición de los vértices para cambiar la forma de la malla como sigue:

Lo logré siguiendo esta discusión: Threejs drag points
Que estoy buscando
Quiero poder extruir una cara (agarre 4 vértices), así que logro algo como esto:

Quiero mantenerlo todo como parte de la misma malla, para mantenerlo limpio, porque lo exportaré como una sola malla con ColladaExporter
.
Editar
Para lograr esto, necesitaría clonar los vértices y extruirlos hacia arriba. Esto significa, agregar 4 nuevos vértices y conectarlos.
Probé esto:
var geo = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
geo.rotateX(-Math.PI * 0.5);
geo.translate(0,0.5,0);
//And the merge them together
var newplane = BufferGeometryUtils.mergeBufferGeometries([plane, geo]);
newplane = BufferGeometryUtils.mergeVertices(newplane,1);
Y tengo esto:

Esperaba que todos los vértices se fusionaran con el plano, dejando un plano plano. Hice esto con fines de prueba, pero solo fusionó una esquina.
Comencé a construir un "cubo" con múltiples y los coloqué en el lugar correcto, para luego aplicar nuevamente BufferGeometryUtils.mergeVertices
, pero los vértices no parecen fusionarse correctamente:

Editar 2 / Progreso
Logré crear un PlaneBufferGeometry
y extruirlo modificando manualmente los vértices y las normales, como se dice en:https://threejs.org/docs/#api/en/core/BufferGeometry
El plano extruido tiene todos los vértices conectados, por lo que cada vez que arrastro un vértice arrastra una pieza completa, el problema ahora es que necesito conectar estos nuevos vértices a la cuadrícula original para evitar esto:

El objetivo es fusionar todos los vértices, ahora necesito encontrar una manera de fusionar el plano base con la nueva pieza extruida.
Editar 3 / Listo
Lo hice, publicaré la respuesta cuando tenga tiempo. Pasé todo el día en estos hoy, y ya estaba muy cansado.

Respuestas
No estoy seguro de si eso es lo que necesita, pero aquí está el ejemplo modificado de la respuesta a la que se refirió (observe la diferencia en la implementación de mouseMove). Lo he extendido solo por dos puntos, pero creo que debería hacerse una idea:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.PlaneBufferGeometry(10, 10, 10, 10);
geometry.rotateX(-Math.PI * 0.5);
var plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({
wireframe: true,
color: "red"
}));
scene.add(plane);
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
size: 0.25,
color: "yellow"
}));
scene.add(points);
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 0.25;
var mouse = new THREE.Vector2();
var intersects = null;
var plane = new THREE.Plane();
var planeNormal = new THREE.Vector3();
var currentIndex = null;
var planePoint = new THREE.Vector3();
var dragging = false;
window.addEventListener("mousedown", mouseDown, false);
window.addEventListener("mousemove", mouseMove, false);
window.addEventListener("mouseup", mouseUp, false);
function mouseDown(event) {
setRaycaster(event);
getIndex();
dragging = true;
}
function mouseMove(event) {
if (dragging && currentIndex !== null) {
setRaycaster(event);
raycaster.ray.intersectPlane(plane, planePoint);
var indicesToMoveUp = [currentIndex-1, currentIndex];
var delta_x = geometry.attributes.position.getX(currentIndex) - planePoint.x;
geometry.attributes.position.setXYZ(currentIndex, planePoint.x, planePoint.y, planePoint.z);
geometry.attributes.position.needsUpdate = true;
var old_x_neighbour = geometry.attributes.position.getX(currentIndex - 1);
geometry.attributes.position.setY(currentIndex-1, planePoint.y);
geometry.attributes.position.setZ(currentIndex-1, planePoint.z);
geometry.attributes.position.setX(currentIndex-1, old_x_neighbour - delta_x);
geometry.attributes.position.needsUpdate = true;
}
}
function mouseUp(event) {
dragging = false;
currentIndex = null;
}
function getIndex() {
intersects = raycaster.intersectObject(points);
if (intersects.length === 0) {
currentIndex = null;
return;
}
currentIndex = intersects[0].index;
setPlane(intersects[0].point);
}
function setPlane(point) {
planeNormal.subVectors(camera.position, point).normalize();
plane.setFromNormalAndCoplanarPoint(planeNormal, point);
}
function setRaycaster(event) {
getMouse(event);
raycaster.setFromCamera(mouse, camera);
}
function getMouse(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>