<> <> <> <> DIRECTORY CoordSys, DisplayListToTree, Feedback, Imager, Matrix3d, Preprocess3d, Rope, SV2d, SV3d, SVAssembly, SVBasicTypes, SVCaret, SVCastRays, SVEditUser, SVEvent, SVInterfaceTypes, SVMasterObject, SVMasterObjectTypes, SVModelTypes, SVRay, SVRayTypes, SVScene, SVSceneTypes, SVSelect, SVSelections, SVTransforms, SVUserInput, SVVector3d, SVViewersOnScene, SVViewerTools, SVWindow, TIPUser, ViewerClasses, ViewerTools; SVEventImplC: CEDAR PROGRAM IMPORTS CoordSys, DisplayListToTree, Feedback, Matrix3d, Preprocess3d, SVAssembly, SVCaret, SVCastRays, SVEditUser, SVEvent, SVRay, SVScene, SVSelect, SVSelections, SVTransforms, SVVector3d, SVViewersOnScene, SVViewerTools, SVWindow, ViewerTools EXPORTS SVEvent = BEGIN Artwork: TYPE = SVModelTypes.Artwork; ArtworkToolData: TYPE = SVInterfaceTypes.ArtworkToolData; Slice: TYPE = SVSceneTypes.Slice; SliceList: TYPE = SVSceneTypes.SliceList; BoundBox: TYPE = SVBasicTypes.BoundBox; BoundSphere: TYPE = SVBasicTypes.BoundSphere; Camera: TYPE = SVModelTypes.Camera; Classification: TYPE = SVRayTypes.Classification; Color: TYPE = Imager.Color; CoordSystem: TYPE = SVModelTypes.CoordSystem; CSGTree: TYPE = SVRayTypes.CSGTree; CylinderRec: TYPE = SVMasterObjectTypes.CylinderRec; EditToolData: TYPE = SVInterfaceTypes.EditToolData; FileCamera: TYPE = SVSceneTypes.FileCamera; FrameBox: TYPE = SVModelTypes.FrameBox; MasterObject: TYPE = SVSceneTypes.MasterObject; Matrix4by4: TYPE = SV3d.Matrix4by4; Point2d: TYPE = SV2d.Point2d; Point3d: TYPE = SV3d.Point3d; Primitive: TYPE = SVRayTypes.Primitive; Ray: TYPE = SVRayTypes.Ray; Scene: TYPE = SVSceneTypes.Scene; SearchDepth: TYPE = SVRayTypes.SearchDepth; Selection: TYPE = SVInterfaceTypes.Selection; SelectionGenerator: TYPE = SVInterfaceTypes.SelectionGenerator; Shape: TYPE = SVSceneTypes.Shape; SkitterMode: TYPE = SVSceneTypes.SkitterMode; ToolData: TYPE = SVSceneTypes.ToolData; TrigLine: TYPE = SV2d.TrigLine; Vector3d: TYPE = SV3d.Vector3d; SVData: TYPE = SVInterfaceTypes.SVData; SelectAll: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { editToolData: EditToolData _ svData.editToolData; scene: Scene _ svData.scene; SVSelect.DeselectAll[svData.scene, normal]; SVSelect.SelectAll[svData.scene, normal]; SVViewersOnScene.PaintSceneAllViewers[paintAction: $SelectionChanged, editToolData: editToolData, scene: scene, edited: FALSE]; }; JackPivotX: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { JackPivotAux[event, svData, 1]; }; JackPivotY: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { JackPivotAux[event, svData, 2]; }; JackPivotZ: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { JackPivotAux[event, svData, 3]; }; JackPivotAux: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData, axis: [1..3]] = { <> editToolData: EditToolData _ svData.editToolData; scene: Scene _ svData.scene; degrees: REAL _ NARROW[event.rest.first, REF REAL]^; targetSel: Selection; coincident: Slice; targetSel _ SVSelections.TopTarget[]; IF targetSel = NIL THEN RETURN; coincident _ targetSel.coincident; <> SVTransforms.Rotate[coincident, scene, axis, degrees, coincident.coordSys, TRUE]; SVViewersOnScene.PaintSceneAllViewers[paintAction: $SelectionChanged, editToolData: editToolData, scene: scene, edited: TRUE]; }; SourcePivotX: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { SourcePivotAux[event, svData, 1]; }; SourcePivotY: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { SourcePivotAux[event, svData, 2]; }; SourcePivotZ: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { SourcePivotAux[event, svData, 3]; }; SourcePivotAux: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData, axis: [1..3]] = { <> editToolData: EditToolData _ svData.editToolData; scene: Scene _ svData.scene; degrees: REAL _ NARROW[event.rest.first, REF REAL]^; sourceSel: Selection; jack, indirect: Slice; sourceSel _ SVSelections.TopMovee[]; IF sourceSel = NIL THEN RETURN; SELECT sourceSel.referentType FROM hook => { jack _ sourceSel.coincident; indirect _ sourceSel.indirect; }; coordSys => { jack _ sourceSel.coincident; indirect _ sourceSel.coincident; }; ENDCASE => ERROR; SVViewersOnScene.SceneNewVersion[editToolData.currentSVData]; SVTransforms.Rotate[indirect, scene, axis, degrees, jack.coordSys, TRUE]; svData.refresh.addedObject _ indirect; SVViewersOnScene.PaintSceneAllViewers[$ObjectChangedBoundBoxProvided, editToolData, scene]; }; DropAnchor: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { editToolData: EditToolData _ svData.editToolData; SVCaret.Copy[from: editToolData.skitter, to: svData.scene.anchor]; <<[] _ GGAlign.CreateAnchorTrigger[ggData.anchor, ggData.hitTest.triggerBag]; -- anchor can trigger>> <<[] _ GGAlign.CreateAnchorTrigger[ggData.anchor, ggData.hitTest.sceneBag]; -- anchor is itself gravity active>> SVWindow.RestoreScreenAndInvariants[paintAction: $AnchorAdded, svData: svData, remake: objectBag, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE]; }; LiftAnchor: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { IF SVCaret.Exists[svData.scene.anchor] THEN { SVCaret.Kill[svData.scene.anchor]; <> <> SVWindow.RestoreScreenAndInvariants[paintAction: $AnchorRemoved, svData: svData, remake: triggerBag, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE]; }; }; <<>> ArrowShoot: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { <> source, target: Slice; sourceSel, firstTargetSel: Selection; editToolData: EditToolData; sourceSel _ SVSelections.PopMovee[]; IF sourceSel = NIL THEN RETURN; source _ sourceSel.coincident; svData _ sourceSel.svData; editToolData _ svData.editToolData; firstTargetSel _ SVSelections.PopTarget[]; IF firstTargetSel = NIL THEN RETURN; FOR targetSel: Selection _ firstTargetSel, SVSelections.NextTarget[] UNTIL targetSel = NIL DO IF targetSel.svData # sourceSel.svData THEN { Feedback.Append[svData.feedback, "Can't shoot arrows between viewers.", oneLiner]; Feedback.Blink[svData.feedback]; LOOP; }; target _ targetSel.coincident; ArrowShootAux[source.coordSys, target.coordSys, svData]; ENDLOOP; SVViewersOnScene.SceneNewVersion[editToolData.currentSVData]; SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene]; }; ArrowShootAux: PROC [sourceCS, targetCS: CoordSystem, svData: SVData] = { <> sOriginWorld, tOriginWorld: Point3d; tree: CSGTree; ray: Ray; class: Classification; t: REAL; worldDirection, primitiveNormal, worldNormal: Vector3d; selectionMat: Matrix4by4; surfacePtInWorld: Point3d; primitive: Primitive; assembly: Slice; scene: Scene; camera: Camera; scene _ svData.scene; camera _ svData.camera; tree _ DisplayListToTree.AssemblyToTree[scene.assembly, scene, camera]; [] _ Preprocess3d.PreprocessForCatScan[tree, camera]; sOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[sourceCS]]; tOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[targetCS]]; ray _ SVRay.GetRayFromPool[]; worldDirection _ SVVector3d.Sub[tOriginWorld, sOriginWorld]; SVRay.StuffWorldRay[ray, sOriginWorld, worldDirection, camera]; class _ SVCastRays.RayCastBoundingSpheres[ray, tree.son]; FOR i: NAT IN [1..class.count] DO t _ class.params[i]; -- the parameter of the ray intersection primitiveNormal _ class.normals[i]; primitive _ class.primitives[i]; worldNormal _ Matrix3d.UpdateVectorWithInverse[primitive.worldWRTPrim, primitiveNormal]; surfacePtInWorld[1] _ sOriginWorld[1] + t*worldDirection[1]; surfacePtInWorld[2] _ sOriginWorld[2] + t*worldDirection[2]; surfacePtInWorld[3] _ sOriginWorld[3] + t*worldDirection[3]; assembly _ NARROW[primitive.assembly]; selectionMat _ MakeAlignedMat[worldNormal, surfacePtInWorld, assembly.coordSys]; SVSelections.UpdateSkitter[assembly, primitive, svData]; SVSelections.PositionSkitterFromFrame[svData, [0,0], selectionMat]; SVSelections.SetModeSkitter[svData, surface]; SVEvent.SkitterMakes[NIL, svData]; ENDLOOP; SVCastRays.ReturnClassToPool[class]; SVRay.ReturnRayToPool[ray]; }; Sign: PROC [r: REAL] RETURNS [INT] = { IF r = 0.0 THEN RETURN[2]; IF r < 0.0 THEN RETURN[-1] ELSE RETURN[1]; }; AntiParallel: PRIVATE PROC [v1, v2: Vector3d] RETURNS [BOOL] = { RETURN[Sign[v1[1]] = -Sign[v2[1]] OR Sign[v1[2]] = -Sign[v2[2]] OR Sign[v1[3]] = -Sign[v2[3]] ]; }; MakeAlignedMat: PRIVATE PROC [worldNormal: Vector3d, surfacePtInWorld: Point3d, cs: CoordSystem] RETURNS [mat: Matrix4by4] = { <> yAxisOfCS: Vector3d _ Matrix3d.YAxisOfMatrix[CoordSys.WRTWorld[cs]]; xAxis: Vector3d; IF SVVector3d.Parallel[yAxisOfCS, worldNormal] THEN { xAxis _ Matrix3d.XAxisOfMatrix[CoordSys.WRTWorld[cs]]; IF AntiParallel[yAxisOfCS, worldNormal] THEN xAxis _ SVVector3d.Negate[xAxis]; <> } ELSE xAxis _ SVVector3d.CrossProduct[yAxisOfCS, worldNormal]; mat _ Matrix3d.MakeMatFromZandXAxis[worldNormal, xAxis, surfacePtInWorld]; }; MoveUntilTouch: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { <> source, target: Slice; sourceSel, targetSel: Selection; editToolData: EditToolData; sourceSel _ SVSelections.PopMovee[]; IF sourceSel = NIL THEN RETURN; source _ sourceSel.coincident; svData _ sourceSel.svData; editToolData _ svData.editToolData; targetSel _ SVSelections.PopTarget[]; IF targetSel = NIL THEN RETURN; IF targetSel.svData # sourceSel.svData THEN { Feedback.Append[svData.feedback, "Can't skewer between viewers.", oneLiner]; Feedback.Blink[svData.feedback]; RETURN; }; target _ targetSel.coincident; MoveUntilTouchAux[source, target, svData]; SVViewersOnScene.SceneNewVersion[editToolData.currentSVData]; SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene]; }; MoveUntilTouchAux: PROC [source, target: Slice, svData: SVData] = { <> sOriginWorld, tOriginWorld: Point3d; tree, sourceTree: CSGTree; worldDirection: Vector3d; scene: Scene; camera: Camera; boundSphere: BoundSphere; R: REAL; basisMat: Matrix4by4; scene _ svData.scene; camera _ svData.camera; sourceTree _ DisplayListToTree.AssemblyToTree[source, scene, camera]; boundSphere _ Preprocess3d.PreprocessForCatScan[sourceTree, camera]; R _ boundSphere.radius; tree _ DisplayListToTree.AssemblyToTree[scene.assembly, scene, camera]; [] _ Preprocess3d.PreprocessForCatScan[tree, camera]; sOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[source.coordSys]]; tOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[target.coordSys]]; worldDirection _ SVVector3d.Sub[tOriginWorld, sOriginWorld]; <> basisMat _ Matrix3d.MakeHorizontalMatFromZAxis[worldDirection, sOriginWorld]; MoveUntilTouchAuxAux[source, basisMat, worldDirection, R, scene, camera, tree]; }; -- end of MoveUntilTouchAux MoveUntilTouchAuxAux: PROC [source: Slice, basisMat: Matrix4by4, worldDirection: Vector3d, R: REAL, scene: Scene, camera: Camera, tree: CSGTree] = { ray: Ray; minDeltaT, lastSourceT, firstOtherT: REAL; class: Classification; success, someData: BOOL; basisBasePt, worldBasePt: Point3d; moveVector: Vector3d; raysPerHalfSide: NAT = 20; raysPerHalfSideF: REAL = 20.0; ray _ SVRay.GetRayFromPool[]; someData _ FALSE; FOR i: INT IN [-raysPerHalfSide..raysPerHalfSide-1] DO basisBasePt[1] _ (i+0.5)*R/raysPerHalfSideF; FOR j: INT IN [-raysPerHalfSide..raysPerHalfSide-1] DO basisBasePt[2] _ (j+0.5)*R/raysPerHalfSideF; basisBasePt[3] _ 0.0; worldBasePt _ Matrix3d.Update[basisBasePt, basisMat]; SVRay.StuffWorldRay[ray, worldBasePt, worldDirection, camera]; class _ SVCastRays.RayCastBoundingSpheres[ray, tree.son, FALSE]; [lastSourceT, firstOtherT, success] _ SourceAndOtherT[class, source]; IF NOT success THEN { SVCastRays.ReturnClassToPool[class]; LOOP; }; IF firstOtherT < lastSourceT THEN { Feedback.AppendRaw[$Solidviews, "Source is already touching.", oneLiner]; Feedback.BlinkRaw[$Solidviews]; RETURN; }; IF NOT someData THEN { minDeltaT _ firstOtherT-lastSourceT; someData _ TRUE; } ELSE minDeltaT _ MIN[minDeltaT, firstOtherT-lastSourceT]; SVCastRays.ReturnClassToPool[class]; ENDLOOP; -- j ENDLOOP; -- i IF NOT someData THEN { Feedback.AppendRaw[$Solidviews, "No Obstacles Found. Object NOT moved.", oneLiner]; Feedback.BlinkRaw[$Solidviews]; RETURN; }; moveVector _ SVVector3d.Scale[worldDirection, minDeltaT]; SVTransforms.Translate[source, scene, moveVector]; SVRay.ReturnRayToPool[ray]; }; SourceAndOtherT: PROC [class: Classification, source: Slice] RETURNS [lastSourceT, firstOtherT: REAL, success: BOOL _ FALSE] = { sourceFound, otherFound: BOOL; thisAssembly: Slice; sourceFound _ FALSE; otherFound _ FALSE; IF class.count = 0 THEN RETURN [0,0,FALSE]; FOR i: NAT IN [1..class.count] DO thisAssembly _ NARROW[class.primitives[i].assembly]; IF DisplayListToTree.IsSuccessorOf[thisAssembly, source] THEN { lastSourceT _ class.params[i]; sourceFound _ TRUE; } ELSE IF NOT otherFound THEN { firstOtherT _ class.params[i]; otherFound _ TRUE; }; ENDLOOP; success _ sourceFound AND otherFound; }; Skewer: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { <> source, target: Slice; sourceSel, firstTargetSel: Selection; editToolData: EditToolData; sourceSel _ SVSelections.PopMovee[]; IF sourceSel = NIL THEN RETURN; source _ sourceSel.coincident; svData _ sourceSel.svData; editToolData _ svData.editToolData; firstTargetSel _ SVSelections.PopTarget[]; IF firstTargetSel = NIL THEN RETURN; IF firstTargetSel.svData # sourceSel.svData THEN { Feedback.Append[svData.feedback, "Can't skewer between viewers.", oneLiner]; Feedback.Blink[svData.feedback]; RETURN; }; target _ firstTargetSel.coincident; SkewerAux[source, target, svData]; SVViewersOnScene.SceneNewVersion[editToolData.currentSVData]; SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, svData.editToolData, svData.scene]; }; SkewerAux: PROC [source, target: Slice, svData: SVData] = { <> sOriginWorld, tOriginWorld: Point3d; tree: CSGTree; ray: Ray; class: Classification; t: REAL; directionWorld: Vector3d; surfacePtInWorld, lastSurfacePtInWorld: Point3d; primitive: Primitive; assembly, lastAssembly: Slice; moveVector, totalVector, localDiff: Vector3d; scene: Scene; camera: Camera; sourceCS, targetCS: CoordSystem; sourceCS _ source.coordSys; targetCS _ target.coordSys; scene _ svData.scene; camera _ svData.camera; tree _ DisplayListToTree.AssemblyToTree[scene.assembly, scene, camera]; [] _ Preprocess3d.PreprocessForCatScan[tree, camera]; sOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[sourceCS]]; tOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[targetCS]]; ray _ SVRay.GetRayFromPool[]; directionWorld _ SVVector3d.Sub[tOriginWorld, sOriginWorld]; <> sOriginWorld _ SVVector3d.Sub[sOriginWorld, SVVector3d.Scale[directionWorld, 0.3]]; directionWorld _ SVVector3d.Scale[directionWorld, 1.6]; <> SVRay.StuffWorldRay[ray, sOriginWorld, directionWorld, camera]; class _ SVCastRays.RayCastBoundingSpheres[ray, tree.son, FALSE]; SVCastRays.SortClassByPrimitive[class]; IF class.count = 0 THEN { SVCastRays.ReturnClassToPool[class]; SVRay.ReturnRayToPool[ray]; RETURN; }; lastAssembly _ NARROW[class.primitives[1].assembly]; t _ class.params[1]; lastSurfacePtInWorld[1] _ sOriginWorld[1] + t*directionWorld[1]; lastSurfacePtInWorld[2] _ sOriginWorld[2] + t*directionWorld[2]; lastSurfacePtInWorld[3] _ sOriginWorld[3] + t*directionWorld[3]; totalVector _ [0,0,0]; FOR i: NAT IN [2..class.count] DO t _ class.params[i]; -- the parameter of the ray intersection IF t > 1.0 THEN EXIT; -- Compression occurs only BETWEEN source and target. primitive _ class.primitives[i]; surfacePtInWorld[1] _ sOriginWorld[1] + t*directionWorld[1]; surfacePtInWorld[2] _ sOriginWorld[2] + t*directionWorld[2]; surfacePtInWorld[3] _ sOriginWorld[3] + t*directionWorld[3]; assembly _ NARROW[primitive.assembly]; IF lastAssembly # assembly THEN { localDiff _ SVVector3d.Sub[lastSurfacePtInWorld, surfacePtInWorld]; moveVector _ SVVector3d.Add[localDiff, totalVector]; totalVector _ SVVector3d.Add[totalVector, localDiff]; SVTransforms.Translate[assembly, scene, moveVector]; }; lastAssembly _ assembly; lastSurfacePtInWorld _ surfacePtInWorld; ENDLOOP; SVCastRays.ReturnClassToPool[class]; SVRay.ReturnRayToPool[ray]; }; AddCylinder: PUBLIC PROC [event: LIST OF REF ANY, svData: SVData] = { <> sourceSel, firstTargetSel: Selection; sourceCS, targetCS: CoordSystem; editToolData: EditToolData; radius: REAL; scene: Scene; sourceSel _ SVSelections.PopMovee[]; IF sourceSel = NIL THEN RETURN; firstTargetSel _ SVSelections.PopTarget[]; IF firstTargetSel = NIL THEN RETURN; IF sourceSel.svData # firstTargetSel.svData THEN { Feedback.Append[svData.feedback, "Can't add cylinder between viewers.", oneLiner]; Feedback.Blink[svData.feedback]; RETURN; }; svData _ sourceSel.svData; scene _ svData.scene; editToolData _ svData.editToolData; sourceCS _ sourceSel.coincident.coordSys; radius _ SVViewerTools.GetReal[editToolData.cylinderSection.radius, 20]; FOR targetSel: Selection _ firstTargetSel, SVSelections.NextTarget[] UNTIL targetSel = NIL DO targetCS _ targetSel.coincident.coordSys; AddCylinderAux[sourceCS, targetCS, radius, editToolData, scene]; ENDLOOP; SVViewersOnScene.SceneNewVersion[editToolData.currentSVData]; SVViewersOnScene.PaintSceneAllViewers[$PaintEntireScene, editToolData, svData.scene]; }; AddCylinderAux: PROC [sourceCS, targetCS: CoordSystem, radius: REAL, editToolData: EditToolData, scene: Scene] = { <> cylMO: MasterObject; moFound: BOOL; cylinderRec: CylinderRec; sOriginWorld, tOriginWorld, midPointWorld: Point3d; cylWorld, superWorld, cylSuper: Matrix4by4; cylinderY: Vector3d; height: REAL; newAssembly, superAssembly: Slice; success: BOOL; addSucceeds: BOOL _ TRUE; cylName: Rope.ROPE; [cylMO, moFound] _ SVScene.FindObjectFromName["cylinder", scene]; IF NOT moFound THEN ERROR; cylinderRec _ NARROW[cylMO.mainBody]; sOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[sourceCS]]; tOriginWorld _ Matrix3d.OriginOfMatrix[CoordSys.WRTWorld[targetCS]]; midPointWorld _ SVVector3d.Add[sOriginWorld, tOriginWorld]; midPointWorld _ SVVector3d.Scale[midPointWorld, 0.5]; cylinderY _ SVVector3d.Sub[tOriginWorld, sOriginWorld]; height _ SVVector3d.Magnitude[cylinderY]; cylWorld _ MakeHorizontalMatFromYAxis[cylinderY, midPointWorld]; radius _ radius/cylinderRec.radius; height _ height/cylinderRec.height; cylName _ ViewerTools.GetContents[editToolData.sceneSection.new]; cylName _ CoordSys.UniqueNameFrom[cylName, scene.coordSysRoot]; [superAssembly, success] _ SVEditUser.GetParent[editToolData]; IF NOT success THEN RETURN; superWorld _ CoordSys.WRTWorld[superAssembly.coordSys]; cylSuper _ Matrix3d.AInTermsOfB[cylWorld, superWorld]; [newAssembly, ----, addSucceeds] _ SVAssembly.CreatePrimitive[cylName, "cylinder", scene]; IF NOT addSucceeds THEN RETURN; addSucceeds _ SVAssembly.AddPrimitive[newAssembly, [radius, height, radius], superAssembly, cylSuper, scene]; }; -- end of AddCylinderAux MakeHorizontalMatFromYAxis: PROC [yAxis: Vector3d, origin: Point3d] RETURNS [mat: Matrix4by4] = { <> xAxis: Vector3d; IF yAxis[1] = 0 AND yAxis[3] = 0 THEN xAxis _ [1,0,0] ELSE xAxis _ SVVector3d.CrossProduct[[0,1,0], yAxis]; mat _ Matrix3d.MakeMatFromYandXAxis[yAxis, xAxis, origin]; }; END.