I am new to this forum, not sure if this is how to post a question with some code, but I need some help please.
I have scene with some GridHelper objects to represent ‘pulleys’ which I need to drag but if I click on a child object of the GridHelper only the child is dragged, not the complete GridHelper.
// each pulley is a GridHelper with some child objects such as circle and ‘rim’ added for presentation
// I need to drag the GridHelper with its children in the dragcontrols
// but if the child object is clicked in the dragcontrols only the child is dragged
// if the GridHelper is clicked it move with the child objects - this is the required behavior
Sample project code based on Sean Bradley code follows
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Three.js TypeScript Tutorials by Sean Bradley : https://sbcode.net/threejs</title>
<style>
body {
overflow: hidden;
margin: 0px;
}
</style>
</head>
<body>
<script type="module" src="bundle.js"></script>
</body>
</html>
client.ts script
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
//import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { DragControls } from 'three/examples/jsm/controls/DragControls'
import Stats from 'three/examples/jsm/libs/stats.module'
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const light = new THREE.SpotLight()
light.position.set(12.5, 12.5, 12.5)
light.castShadow = true
light.shadow.mapSize.width = 1024
light.shadow.mapSize.height = 1024
scene.add(light)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.set(0, 0, 200)
const renderer = new THREE.WebGLRenderer()
//renderer.shadowMap.enabled = true
//renderer.outputEncoding = THREE.sRGBEncoding
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
scene.background = new THREE.Color(0xffffff);
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.enableRotate = false
let cvDragObjects: THREE.Object3D[] = [];
const dragcontrols = new DragControls(cvDragObjects, camera, renderer.domElement)
const pickableObjects: THREE.Mesh[] = []
let intersectedObject: THREE.Object3D | null
const originalMaterials: { [id: string]: THREE.Material | THREE.Material[] } =
{}
const highlightedMaterial = new THREE.MeshBasicMaterial({
wireframe: true,
color: 0x00ff00
})
// create some data to draw
// each pulley is a GridHelper with some child objects such as circle and 'rim' added for presentation
// I need to drag the GridHelper with its children in the dragcontrols
// but if the child object is clicked in the dragcontrols only the child is dragged
// if the GridHelper is clicked it move with the child objects - this is the required behavior
let data = []
const datarow1 = { pType: "Bend", screenSize: 16, screenX: -50, screenZ: 50 }
data.push(datarow1)
const datarow2 = { pType: "Bend", screenSize: 12, screenX: 100, screenZ: 100 }
data.push(datarow2)
const datarow3 = { pType: "Bend", screenSize: 20, screenX: 50, screenZ: 100 }
data.push(datarow3)
const drive = { pType: "Drive", screenSize: 18, screenX: 150, screenZ: 70 }
data.push(drive)
const cv = new THREE.Object3D(); // main object in scene
// add the pulleys to the cv object
data.forEach(element => {
const pull = drawPulley(element)
cvDragObjects.push(pull)
cv.add(pull)
});
scene.add(cv);
function drawPulley(dr1: any) {
//var combinedgeometry = new THREE.Geometry();
var rimmaterial = new THREE.LineBasicMaterial({ color: 0x000000 });
var cenmaterial = new THREE.MeshBasicMaterial({ color: 0xa7a2a2 });
var whitematerial = new THREE.MeshBasicMaterial({ color: 0xf7f7f7 });
var redmaterial = new THREE.MeshBasicMaterial({ color: 0xf81010 });
var blackmaterial = new THREE.MeshBasicMaterial({ color: 0x171616 });
var bluematerial = new THREE.MeshBasicMaterial({ color: 0x1212fa });
var gridhelpermaterial = new THREE.MeshBasicMaterial({ color: 0xa7a2a2, transparent: true, opacity: 0.01, visible: false });
//var pulleyGrid = new THREE.PolarGridHelper(dr1.r, 16, 8, 4, cenmaterial.color, cenmaterial.color);
var pulleyGrid = new THREE.GridHelper(dr1.screenSize * 0.5, 2, gridhelpermaterial.color, gridhelpermaterial.color);
//pulleyGrid.rotateX(Math.PI / 2);
if (dr1.pType === 'Drive') {
//var r = dr1.r * .98;
var r = dr1.screenSize * 0.5 * .98;
var startAngle = 0;
var angleIncr = Math.PI * 0.5;
var geometry1 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle1 = new THREE.Mesh(geometry1, redmaterial);
circle1.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'driveface',
isDragable: false
};
pulleyGrid.add(circle1);
//pulleyGrid.attach(circle1);
startAngle = startAngle + angleIncr;
var geometry2 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle2 = new THREE.Mesh(geometry2, whitematerial);
circle2.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'driveface',
isDragable: false
};
pulleyGrid.add(circle2);
//pulleyGrid.attach(circle2);
startAngle = startAngle + angleIncr;
var geometry3 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle3 = new THREE.Mesh(geometry3, redmaterial);
circle3.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'driveface',
isDragable: false
};
pulleyGrid.add(circle3);
//pulleyGrid.attach(circle3);
startAngle = startAngle + angleIncr;
var geometry4 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle4 = new THREE.Mesh(geometry4, whitematerial);
circle4.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'driveface',
isDragable: false
};
pulleyGrid.add(circle4);
//pulleyGrid.attach(circle4);
}
else if (dr1.pType === 'Brake') {
//var r = dr1.r * .98;
var r = dr1.screenSize * 0.5 * .98;
var startAngle = 0;
var angleIncr = Math.PI * 0.5;
var geometry1 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle1 = new THREE.Mesh(geometry1, bluematerial);
circle1.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'brakeface',
isDragable: false
};
pulleyGrid.add(circle1);
startAngle = startAngle + angleIncr;
var geometry2 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle2 = new THREE.Mesh(geometry2, whitematerial);
circle2.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'brakeface',
isDragable: false
};
pulleyGrid.add(circle2);
startAngle = startAngle + angleIncr;
var geometry3 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle3 = new THREE.Mesh(geometry3, bluematerial);
circle3.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'brakeface',
isDragable: false
};
pulleyGrid.add(circle3);
startAngle = startAngle + angleIncr;
var geometry4 = new THREE.CircleGeometry(r, 32, startAngle, angleIncr);
var circle4 = new THREE.Mesh(geometry4, whitematerial);
circle4.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'brakeface',
isDragable: false
};
pulleyGrid.add(circle4);
}
else if (dr1.pType === 'Takeup') {
var r = dr1.screenSize * 0.5 * .98;
var x = Math.cos(30 * Math.PI / 180) * r;
var y = Math.sin(30 * Math.PI / 180) * r;
var z = 0;
//var material = new THREE.MeshStandardMaterial({ color: 0x000000 });
var material = new THREE.MeshBasicMaterial({ color: 0x000000 });
//create a triangular geometry
//var geometry = new THREE.Geometry();
const points = [];
points.push(new THREE.Vector3(-x, -y, 0)); // use anticlockwise winding otherwise not shown
points.push(new THREE.Vector3(x, -y, 0));
points.push(new THREE.Vector3(0, r, 0));
var geometry = new THREE.BufferGeometry().setFromPoints(points);
//create a new face using vertices 0, 1, 2
var normal = new THREE.Vector3(0, 0, 0); //optional
var color = new THREE.Color(0x171616); //optional
var materialIndex = 0; //optional
//var vertexColors = [new THREE.Color(0x000000),
// new THREE.Color(0x000000),
// new THREE.Color(0x000000)];
//var face = new THREE.Face3(0, 1, 2, normal, color, materialIndex);
// code below not working Face3 removed
// var face = new THREE.Face3(0, 1, 2);
// //add the face to the geometry's faces array
// geometry.faces.push(face);
// //geometry.faces[0].vertexColors = vertexColors;
// geometry.computeFaceNormals();
geometry.computeVertexNormals();
var facemesh = new THREE.Mesh(geometry, material);
facemesh.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'takeupface',
isDragable: false
};
pulleyGrid.add(facemesh);
}
else { // normal pulley
var geometry1 = new THREE.CircleGeometry(dr1.screenSize * 0.5 - 1, 32);
var circle1 = new THREE.Mesh(geometry1, cenmaterial);
circle1.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'takeupface',
isDragable: false
};
pulleyGrid.add(circle1);
// var pullFace = this.drawPulleyFace(dr1.screenSize * 0.5, cenmaterial);
// pulleyGrid.add(pullFace);
}
var rim = drawPulleyRim(dr1.screenSize * 0.5, rimmaterial);
pulleyGrid.add(rim);
//pulleyGrid.rotation.x = Math.PI / 2;
pulleyGrid.userData = {
isLineItem: false,
isPullItem: true,
no: dr1.no,
pType: dr1.pType,
isDragable: true
};
pulleyGrid.position.set(dr1.screenX, dr1.screenZ, 0/*dr1.y*/);
pulleyGrid.name = dr1.pType + '-' + dr1.no;
//console.log('pulleyGrid', pulleyGrid);
return pulleyGrid;
};
function drawPulleyFace(radius: number, material: THREE.MeshBasicMaterial) {
//var geometry1 = new THREE.CircleGeometry(dr1.r, 32);
var geometry1 = new THREE.CircleGeometry(radius - 1, 32);
//var circle1 = new THREE.Mesh(geometry1, cenmaterial, 0, Math.PI * 2);
var circle1 = new THREE.Mesh(geometry1, material);
//circle1.name('pulleyFill') // no name property
//pulleyGrid.add(circle1);
//circle1.removeEventListener;
circle1.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'pulleyface',
isDragable: false
};
return circle1;
}
function drawPulleyRim(radius: number, material: THREE.LineBasicMaterial) {
var segmentCount = 32;
//geometry = new THREE.Geometry();
//geometry = new THREE.BufferGeometry();
//material = new THREE.LineBasicMaterial({ color: 0x555 });
const vertices = [];
for (var i = 0; i <= segmentCount; i++) {
var theta = (i / segmentCount) * Math.PI * 2;
// geometry.vertices.push(
// new THREE.Vector3(
// Math.cos(theta) * radius,
// Math.sin(theta) * radius,
// 0));
// see https://sbcode.net/threejs/geometry-to-buffergeometry/ on jow to use the new BufferGeometry
vertices.push(
new THREE.Vector3(
Math.cos(theta) * radius,
Math.sin(theta) * radius,
0));
}
// draw a second rim for extra thickness
for (var i = 0; i <= segmentCount; i++) {
var theta = (i / segmentCount) * Math.PI * 2;
vertices.push(
new THREE.Vector3(
Math.cos(theta) * (radius - 0.5),
Math.sin(theta) * (radius - 0.5),
0));
}
//geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
let geometry = new THREE.BufferGeometry().setFromPoints(vertices);
let rim = new THREE.Line(geometry, material);
rim.userData = {
isLineItem: false,
isPullItem: true,
no: -1,
pType: 'pulleyrim',
isDragable: false
};
return rim;
};
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const raycaster = new THREE.Raycaster()
let intersects: THREE.Intersection[]
// document.addEventListener('mousemove', onDocumentMouseMove, false)
// function onDocumentMouseMove(event: MouseEvent) {
// raycaster.setFromCamera(
// {
// x: (event.clientX / renderer.domElement.clientWidth) * 2 - 1,
// y: -(event.clientY / renderer.domElement.clientHeight) * 2 + 1
// },
// camera
// )
// intersects = raycaster.intersectObjects(pickableObjects, false)
// if (intersects.length > 0) {
// intersectedObject = intersects[0].object
// } else {
// intersectedObject = null
// }
// pickableObjects.forEach((o: THREE.Mesh, i) => {
// if (intersectedObject && intersectedObject.name === o.name) {
// pickableObjects[i].material = highlightedMaterial
// } else {
// pickableObjects[i].material = originalMaterials[o.name]
// }
// })
// }
const stats = Stats()
document.body.appendChild(stats.dom)
function animate() {
requestAnimationFrame(animate)
controls.update()
render()
stats.update()
}
function render() {
renderer.render(scene, camera)
}
animate()