public void SetSmallestExtension(float t) { TelescopeShell shell = (Reversed) ? shells[1] : LastShell; shell.extensionRatio = t; shell.SetTransform(); }
public void SetShellExtensions(float t) { float tRemaining = t; float tStep = 1f / (shells.Count - 1); shells[0].extensionRatio = Mathf.Clamp01(t); for (int i = 1; i < shells.Count; i++) { TelescopeShell ts = shells[i]; if (tRemaining > tStep) { ts.extensionRatio = 1; tRemaining -= tStep; } else if (tRemaining > 0) { float tNormalized = tRemaining / tStep; ts.extensionRatio = tNormalized; tRemaining = 0; } else { ts.extensionRatio = 0; } } if (LastShell.extensionRatio < MinExt) { LastShell.extensionRatio = MinExt; LastShell.SetTransform(); } }
public void PrependShell() { Vector3 loc = shells[0].getLocalLocationAlongPath(-1); Quaternion rot = shells[0].getLocalRotationAlongPath(-1); TelescopeParameters p = new TelescopeParameters(shells[0].getParameters()); p.radius += p.thickness; // Create the new shell object GameObject newShell = new GameObject(); TelescopeShell shell = newShell.AddComponent <TelescopeShell>(); shell.GenerateGeometry(p, null); shell.setMaterial(material); Vector3 shellLoc = shells[0].transform.position; Quaternion shellRot = shells[0].transform.rotation; // Move the new shell to be in position to receive the old one shell.transform.parent = shells[0].transform.parent; shell.transform.position = shellLoc + shellRot * loc; shell.transform.rotation = shellRot * rot; // Reparent the old starting shell so it becomes the child of the new one shells[0].baseTranslation = Vector3.zero; shells[0].baseRotation = Quaternion.identity; shells[0].transform.parent = shell.transform; shell.extensionRatio = shells[0].extensionRatio; shells.Insert(0, shell); }
public TelescopeShell addChildShell(TelescopeShell parent, TelescopeParameters parentParams, TelescopeParameters childParams, TelescopeParameters nextParams, bool isNotLast) { int i = shells.Count; // Make the new shell, and set the previous shell as its parent GameObject shellObj = new GameObject(); shellObj.transform.parent = parent.transform; shellObj.name = name + "-shell" + i; // Make the geometry, etc. TelescopeShell newShell = shellObj.AddComponent <TelescopeShell>(); newShell.GenerateGeometry(childParams, nextParams, doOverhang: true); newShell.setMaterial(material); // Set the shell's rest transformation relative to its parent. // When the shell's current extension ratio is 0, this is where // it is located relative to its parent. // newShell.baseRadians = newShell.radiansOfLength(wallThickness); newShell.baseTranslation = TelescopeUtils.childBasePosition(parentParams, childParams); newShell.baseRotation = TelescopeUtils.childBaseRotation(parentParams, childParams); shells.Add(newShell); shellObj.layer = 8; newShell.containingSegment = this; return(newShell); }
public void Deselect() { if (selected) { selected.setMaterial(defaultTelescopeMaterial); } selected = null; }
public void ShrinkToFit() { if (selected) { TelescopeSegment segment = selected.containingSegment; List <TelescopeParameters> allParams = segment.getParamList(); TelescopeUtils.growChainToFit(allParams, shrinkFit: true); segment.MakeAllShells(allParams); segment.SetShellExtensions(1); selected = null; } }
Vector3 contactDirection(TelescopeShell shell, bool reversed) { if (!reversed) { return(shell.transform.forward); } else { Vector3 dirLocal = shell.getLocalRotationAlongPath(1) * Vector3.forward; Vector3 dir = shell.transform.rotation * dirLocal; dir *= -1; return(dir); } }
public void SetTransform() { float extendT = Mathf.Clamp01(extensionRatio * 2); if (AlwaysExtend) { extendT = 1; } float twistT = Mathf.Clamp01(extensionRatio * 2 - 1); if (!Reversed) { Vector3 localTranslation = getLocalLocationAlongPath(extendT); Quaternion localRotation = getLocalRotationAlongPath(extendT); // Add twist angle. Vector3 forwardAxis = localRotation * baseRotation * Vector3.forward; Quaternion roll = Quaternion.AngleAxis(-twistAngle * twistT, forwardAxis); localRotation = roll * localRotation; // Set the shell's local translation from parent based on how extended it is. transform.localPosition = baseRotation * localTranslation + baseTranslation; transform.localRotation = baseRotation * localRotation; } else { TelescopeShell parent = transform.parent.GetComponent <TelescopeShell>(); if (!parent) { return; } Vector3 localTranslation = parent.getInvLocationAlongPath(extendT); Quaternion localRotation = parent.getLocalRotationAlongPath(extendT); Vector3 forwardPosition = Quaternion.Inverse(baseRotation) * localTranslation - parent.baseTranslation; Quaternion forwardRotation = Quaternion.Inverse(parent.baseRotation) * Quaternion.Inverse(localRotation); // Set the shell's local translation from parent based on how extended it is. transform.localRotation = forwardRotation; transform.localPosition = forwardPosition; // Now we need to rotate by the twist angle, with the pivot being the parent's angle. Vector3 parentWorld = parent.transform.position; Vector3 axis = parent.transform.forward; transform.RotateAround(parentWorld, axis, parent.twistAngle * twistT); } }
void Rescale(float newRadius) { float radiusDiff = newRadius - Radius; // Offset us from the parent segment so that we are in contact again. if (parentSegment) { Vector3 normal = parentSegment.WorldEndTangent(); Vector3 offset = -radiusDiff * normal; transform.position = transform.position + offset; } // Offset child segments from this bulb so that they are in contact. foreach (TelescopeSegment seg in childSegments) { float childRadius = seg.shells[0].radius; float childOffset = Mathf.Sqrt(newRadius * newRadius - childRadius * childRadius); if (seg.Reversed) { TelescopeShell shell = seg.shells[0]; Vector3 shellOffset = shell.transform.rotation * shell.getLocalLocationAlongPath(1); Vector3 localPos = seg.LocalContactTangent() * childOffset; Vector3 desiredShellEnd = transform.rotation * localPos + transform.position; Vector3 shellBaseWorld = shell.transform.position; Vector3 currentShellEnd = shellBaseWorld + shellOffset; Vector3 worldOffset = desiredShellEnd - currentShellEnd; seg.transform.position += worldOffset;// + shellOffset; } else { seg.transform.localPosition = seg.LocalContactTangent() * childOffset; } } // Rescale the bulb itself Radius = newRadius; }
public Vector3 LocalContactTangent() { TelescopeShell firstShell = shells[0]; if (!Reversed) { Quaternion local = firstShell.transform.localRotation; return(local * Vector3.forward); } else { Quaternion baseQ = firstShell.transform.localRotation; Quaternion pathQ = firstShell.getLocalRotationAlongPath(1); Quaternion local = baseQ * pathQ; return(-(local * Vector3.forward)); } }
public bool CollisionIteration(float delta) { bool hasCollision = false; if (parentSegment) { foreach (TelescopeSegment seg in childSegments) { TelescopeShell parentShell = parentSegment.LastShell; TelescopeShell childShell = seg.FirstShell; float moveDistance; Vector3 moveDirection; if (parentShell.CollidesWith(childShell, out moveDistance, out moveDirection)) { hasCollision = true; childShell.transform.position += 0.5f * moveDistance * delta * moveDirection; parentShell.transform.position -= 0.5f * moveDistance * delta * moveDirection; } } } for (int i = 0; i < childSegments.Count; i++) { for (int j = i + 1; j < childSegments.Count; j++) { TelescopeShell shell1 = childSegments[i].FirstShell; TelescopeShell shell2 = childSegments[j].FirstShell; float moveDistance; Vector3 moveDirection; if (shell1.CollidesWith(shell2, out moveDistance, out moveDirection)) { hasCollision = true; shell2.transform.position += 0.5f * moveDistance * delta * moveDirection; shell1.transform.position -= 0.5f * moveDistance * delta * moveDirection; } } } return(!hasCollision); }
/* * public Vector3 WorldEndPosition() * { * TelescopingShell lastShell = shells[shells.Count - 1]; * }*/ public Vector3 WorldEndTangent() { TelescopeShell lastShell = shells[shells.Count - 1]; if (!Reversed) { Quaternion baseQ = lastShell.transform.rotation; Quaternion local = lastShell.getLocalRotationAlongPath(1); Quaternion combined = baseQ * local; Vector3 worldForward = combined * Vector3.forward; return(-worldForward); } else { Quaternion local = lastShell.transform.rotation; return(local * Vector3.forward); } }
public void SelectShell(TelescopeShell selection) { if (!selection) { return; } if (selected) { selected.setMaterial(defaultTelescopeMaterial); } selected = selection; selected.setMaterial(selectedTelescopeMaterial); TelescopeParameters tp = selected.getParameters(); lengthField.text = tp.length.ToString(); radiusField.text = tp.radius.ToString(); curvatureField.text = tp.curvature.ToString(); twistField.text = tp.twistFromParent.ToString(); }
public bool CollidesWith(TelescopeShell otherShell, out float moveDistance, out Vector3 contactNormal) { float tStep = 1f / (Constants.CUTS_PER_CYLINDER - 1); float angleStep = 2f * Mathf.PI / Constants.VERTS_PER_CIRCLE; float minDistance = 1001f; contactNormal = Vector3.zero; int thisShellCuts = Constants.CUTS_PER_CYLINDER; if (HasOverhang) { thisShellCuts += Constants.OVERHANG_CUTS; } int otherShellCuts = Constants.CUTS_PER_CYLINDER; if (otherShell.HasOverhang) { otherShellCuts += Constants.OVERHANG_CUTS; } // Compare every ring on this shell against the other shell for (int ring = 0; ring < thisShellCuts; ring++) { // Compute the normal of the plane that is orthogonal to the shell here Vector3 ringNormal = CenterTangentWS(ring * tStep); Vector3 ringCenter = CenterPointWS(ring * tStep); // Need to loop over all vertices of the other shell. for (int cut = 0; cut < otherShellCuts; cut++) { for (int vert = -1; vert < Constants.VERTS_PER_CIRCLE; vert++) { Vector3 otherVert; // Use -1 as a hack to check the center point too if (vert == -1) { otherVert = otherShell.CenterPointWS(cut * tStep); } else { // Get the vertex on the surface of the other shell float otherT = cut * tStep; float otherAngle = angleStep * vert; otherVert = otherShell.SurfacePointWS(otherT, otherAngle); } // See if it is near the normal plane to the ring of the current shell Vector3 vecToOther = otherVert - ringCenter; float orthoDistance = Vector3.Dot(vecToOther, ringNormal); // If it is, compute the projected distance along the normal plane. // If the distance is close to being on this ring's plane, // compare it against the old min if (Mathf.Abs(orthoDistance) < tStep * length * 2) { Vector3 planeVec = vecToOther - orthoDistance * ringNormal; float planeDist = planeVec.magnitude; if (planeDist < minDistance) { minDistance = planeDist; Vector3 otherRingCenter = otherShell.CenterPointWS(cut * tStep); contactNormal = (otherRingCenter - ringCenter).normalized; } } } } } // We have now computed the minimum distance from any vertex on the // other shell to any vertex on the center line of this shell. // If any such distance is closer than this shell's radius, then // the shells are colliding. moveDistance = (radius + Constants.COLLISION_GAP - minDistance) + 0.01f; return(minDistance < radius + Constants.COLLISION_GAP); }
// Update is called once per frame void Update() { if (Input.GetButtonDown("ToggleMode")) { if (currentMode == DesignerMode.Shell) { currentMode = DesignerMode.Curve; modeText.text = "Curve mode"; } else if (currentMode == DesignerMode.Curve) { currentMode = DesignerMode.Curvature; modeText.text = "Curvature mode"; } else if (currentMode == DesignerMode.Curvature) { currentMode = DesignerMode.Shell; modeText.text = "Shell mode"; } } RaycastHit hitInfo = new RaycastHit(); if (Input.GetMouseButtonDown(0) && RaycastShells(Input.mousePosition, out hitInfo)) { TelescopeShell selection = hitInfo.collider.GetComponent <TelescopeShell>(); DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (selection) { SelectShell(selection); } else if (draggablePt) { draggable = draggablePt; selectedDepth = Camera.main.WorldToScreenPoint(draggablePt.transform.position).z; } } else if (Input.GetKeyDown("r") && RaycastShells(Input.mousePosition, out hitInfo)) { DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (draggablePt && draggablePt.parentSpline) { Debug.Log("Reverse " + draggablePt.parentSpline.name); draggablePt.parentSpline.Reverse(); } } else if (Input.GetKeyDown("j") && RaycastShells(Input.mousePosition, out hitInfo)) { DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (draggablePt) { if (draggablePt.Type == PointType.Bulb) { draggablePt.SwitchBulbType(PointType.EmptyJuncture); } else if (draggablePt.Type == PointType.EmptyJuncture) { draggablePt.SwitchBulbType(PointType.Bulb); } } } else if (Input.GetMouseButton(0) && draggable) { Vector3 clickPos = Input.mousePosition; clickPos.z = selectedDepth; Vector3 worldPos = Camera.main.ScreenToWorldPoint(clickPos); DraggablePoint intersectedBulb = splineCanvas.IntersectedBulb(worldPos); if (draggable.Type == PointType.Spline && draggable.IsEndPoint() && intersectedBulb) { draggable.AttachToBulb(intersectedBulb); } else { draggable.Move(worldPos); } } else if (Input.GetMouseButtonDown(1) && RaycastShells(Input.mousePosition, out hitInfo)) { Vector3 clickPos = Input.mousePosition; DraggablePoint clicked = hitInfo.collider.GetComponent <DraggablePoint>(); if (clicked) { if (Input.GetKey("left ctrl")) { clicked.Delete(); } else { clicked.Duplicate(); } } // Store the eye-space position of the click. // Use eye space because we always want moving up/down to correspond // to bigger or smaller, regardless of camera orientation. lastMousePos = clickPos; lastMousePos.z = selectedDepth; lastMousePos = Camera.main.ScreenToViewportPoint(lastMousePos); } else if (Input.GetMouseButtonDown(2) && RaycastShells(Input.mousePosition, out hitInfo)) { Vector3 clickPos = Input.mousePosition; // Store the eye-space position of the click. // Use eye space because we always want moving up/down to correspond // to bigger or smaller, regardless of camera orientation. lastMousePos = clickPos; lastMousePos.z = selectedDepth; lastMousePos = Camera.main.ScreenToViewportPoint(lastMousePos); } else if (Input.mouseScrollDelta.y != 0 && RaycastShells(Input.mousePosition, out hitInfo)) { DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (draggablePt) { float change = Input.mouseScrollDelta.y * 0.05f; draggablePt.Resize(change); } } else if (Input.GetKeyDown("delete") && RaycastShells(Input.mousePosition, out hitInfo)) { DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (draggablePt) { draggablePt.Delete(); } } else if (Input.GetKeyDown("b") && RaycastShells(Input.mousePosition, out hitInfo)) { DraggablePoint draggablePt = hitInfo.collider.GetComponent <DraggablePoint>(); if (draggablePt) { draggablePt.ReplaceWithBulb(); } } else if (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(2)) { draggable = null; } else if (Input.GetButtonDown("Cancel")) { Deselect(); } else if (Input.GetButtonDown("Submit")) { Debug.Log("submit"); } shootTime += Time.deltaTime; if (shootTime > shootDelay && Input.GetKey("z")) { ShootSphere(); } }
// Write a set of STLs for a juncture, and an OpenSCAD file that // compiles them all into one fused part. public string WriteSTLOfJuncture(Vector3 minOffset, string objName) { List <string> unionSTLs = new List <string>(); List <string> diffSTLs = new List <string>(); string bulbSTL = ""; // If there is a parent segment, write that if (parentSegment) { TelescopeShell last = parentSegment.LastShell; // Export them in world coordinates so that the full // object can just be assembled without moving pieces around STLWriter.VectorTransform f = (v => last.transform.rotation * v + last.transform.position - minOffset); if (parentSegment.NumShells > 1 && parentSegment.Reversed) { Mesh inner = last.GenerateInnerVolume(parentSegment.shells[parentSegment.NumShells - 2].getParameters(), -Constants.WALL_THICKNESS, extraRings: Constants.CUTS_PER_CYLINDER / 2); string parentVolumeSTL = objName + "-parentInner.stl"; STLWriter.WriteSTLOfMesh(inner, "scad/" + parentVolumeSTL, f); diffSTLs.Add(parentVolumeSTL); } } // Write each child segment as well int childNum = 0; foreach (TelescopeSegment childSegment in childSegments) { TelescopeShell first = childSegment.FirstShell; STLWriter.VectorTransform f = (v => first.transform.rotation * v + first.transform.position - minOffset); // Write a mesh for the inner volume contained in the shell, // so that it can be subtracted out from the juncture if (childSegment.NumShells > 1 && !childSegment.Reversed) { Mesh inner = first.GenerateInnerVolume(childSegment.shells[1].getParameters(), -Constants.WALL_THICKNESS, extraRings: Constants.CUTS_PER_CYLINDER / 2); string childVolumeSTL = objName + "-childInner" + childNum + ".stl"; STLWriter.WriteSTLOfMesh(inner, "scad/" + childVolumeSTL, f); diffSTLs.Add(childVolumeSTL); } childNum++; } // Write this piece if (junctureType != JunctureType.None) { STLWriter.VectorTransform f = (v => transform.rotation * v + transform.position - minOffset); bulbSTL = objName + ".stl"; STLWriter.WriteSTLOfMesh(mFilter.mesh, "scad/" + bulbSTL, f); } string scadFile = objName + ".scad"; STLWriter.WriteSCADOfJunctureSTLs(bulbSTL, unionSTLs, diffSTLs, "scad/" + scadFile); return(scadFile); }
public void MakeAllShells(List <TelescopeParameters> paramList, bool reversed = false) { DeleteTelescope(); // Create an object for the first shell GameObject rootShellObj = new GameObject(); rootShellObj.name = name + "-shell0"; rootShellObj.transform.parent = this.transform; rootShellObj.transform.localPosition = Vector3.zero; telescopeRootShell = rootShellObj; // Make the shell geometry TelescopeShell shell = rootShellObj.AddComponent <TelescopeShell>(); // Get the twist impulse of the shell after, since we need to cut the // grooves in this shell to enable it TelescopeParameters params1 = (paramList.Count > 1) ? paramList[1] : null; shell.GenerateGeometry(paramList[0], params1, outerGroove: false); shell.setMaterial(material); // Shells don't know anything about their position/rotation, // so we set that here. Quaternion initialFacing = Quaternion.LookRotation(initialDirection, initialUp); rootShellObj.transform.rotation = initialFacing; rootShellObj.layer = 8; shell.containingSegment = this; // shell.baseRadians = 0; shell.baseTranslation = Vector3.zero; shell.baseRotation = Quaternion.identity; shells.Add(shell); shell.isRoot = true; // Make all of the child shells here. TelescopeShell prevShell = shell; TelescopeParameters previousParams = paramList[0]; TelescopeParameters currentParams = paramList[0]; float accumulatedTaper = shell.getTaperLoss(); for (int i = 1; i < paramList.Count; i++) { // Get the computed parameters for this and the previous shell. currentParams = paramList[i]; previousParams = paramList[i - 1]; // Shrink the radius by the accumulated taper so far. currentParams.radius -= accumulatedTaper; // Get the twist impulse for the next shell, so that we can cut the grooves. TelescopeParameters nextParams = (i < paramList.Count - 1) ? paramList[i + 1] : null; TelescopeParameters taperedParams = (nextParams != null) ? new TelescopeParameters(nextParams) : null; if (taperedParams != null) { taperedParams.radius -= accumulatedTaper; } // Add it. bool isNotLast = (i < paramList.Count - 1); prevShell = addChildShell(prevShell, previousParams, currentParams, taperedParams, isNotLast); accumulatedTaper += prevShell.getTaperLoss(); } if (fountainPrefab) { GameObject fountain = Instantiate <GameObject>(fountainPrefab); fountain.transform.parent = prevShell.transform; } }