891 | function TensionBelts:createTensionBelt(belt, isDummy, objects, playSound) |
892 | local spec = self.spec_tensionBelts |
893 | |
894 | local tensionBelt = TensionBeltGeometryConstructor.new() |
895 | |
896 | local beltData = spec.beltData |
897 | local width = spec.width or beltData.width |
898 | |
899 | tensionBelt:setWidth(beltData.width) |
900 | tensionBelt:setMaxEdgeLength(spec.maxEdgeLength) |
901 | if isDummy then |
902 | tensionBelt:setMaterial(beltData.dummyMaterial.materialId) |
903 | tensionBelt:setUVscale(beltData.dummyMaterial.uvScale) |
904 | else |
905 | tensionBelt:setMaterial(beltData.material.materialId) |
906 | tensionBelt:setUVscale(beltData.material.uvScale) |
907 | end |
908 | |
909 | if spec.ratchetPosition ~= nil and beltData.ratchet ~= nil then |
910 | tensionBelt:addAttachment(0, spec.ratchetPosition, beltData.ratchet.sizeRatio*width) |
911 | end |
912 | |
913 | if spec.useHooks and beltData.hook ~= nil then |
914 | tensionBelt:addAttachment(0, 0, beltData.hook.sizeRatio*width) |
915 | tensionBelt:addAttachment(1, 0, (beltData.hook2 or beltData.hook).sizeRatio*width) |
916 | end |
917 | |
918 | tensionBelt:setFixedPoints(belt.startNode, belt.endNode) |
919 | tensionBelt:setGeometryBias(spec.geometryBias) |
920 | tensionBelt:setLinkNode(spec.linkNode) |
921 | |
922 | for _, pointNode in pairs(belt.intersectionNodes) do |
923 | local x,y,z = getWorldTranslation(pointNode) |
924 | local dirX, dirY, dirZ = localDirectionToWorld(pointNode, 1, 0, 0) |
925 | tensionBelt:addIntersectionPoint(x,y,z, dirX, dirY, dirZ) |
926 | end |
927 | |
928 | for _, object in pairs(objects) do |
929 | for _, node in pairs(object.visuals) do |
930 | if getSplitType(node) ~= 0 then |
931 | -- Allow a larger range of uvs for trees |
932 | tensionBelt:addShape(node, -100, 100, -100, 100) |
933 | else |
934 | tensionBelt:addShape(node, 0, 1, 0, 1) |
935 | end |
936 | end |
937 | end |
938 | |
939 | local beltShape, _, beltLength = tensionBelt:finalize() |
940 | if beltShape ~= 0 then |
941 | if isDummy then |
942 | belt.dummy = beltShape |
943 | else |
944 | local currentIndex = 0 |
945 | if spec.ratchetPosition ~= nil and beltData.ratchet ~= nil then |
946 | if getNumOfChildren(beltShape) > currentIndex then |
947 | local scale = width |
948 | local ratched = clone(beltData.ratchet.node, false, false, false) |
949 | link(getChildAt(beltShape, 0), ratched) |
950 | setScale(ratched, scale, scale, scale) |
951 | currentIndex = currentIndex + 1 |
952 | end |
953 | end |
954 | if spec.useHooks then |
955 | if beltData.hook ~= nil and getNumOfChildren(beltShape) > currentIndex+1 then |
956 | local scale = width |
957 | local hookStart = clone(beltData.hook.node, false, false, false) |
958 | link(getChildAt(beltShape, currentIndex), hookStart) |
959 | setScale(hookStart, scale, scale, scale) |
960 | |
961 | local hook2 = beltData.hook2 or beltData.hook |
962 | local hookEnd = clone(hook2.node, false, false, false) |
963 | link(getChildAt(beltShape, currentIndex+1), hookEnd) |
964 | setRotation(hookEnd, 0, math.pi, 0) |
965 | setTranslation(hookEnd, 0, 0, hook2.sizeRatio*width) |
966 | setScale(hookEnd, scale, scale, scale) |
967 | |
968 | setShaderParameter(beltShape, "beltClipOffsets", 0, beltData.hook.sizeRatio*width, beltLength-hook2.sizeRatio*width, beltLength, false) |
969 | end |
970 | end |
971 | |
972 | belt.mesh = beltShape |
973 | spec.belts[beltShape] = beltShape |
974 | if belt.dummy ~= nil then |
975 | delete(belt.dummy) |
976 | belt.dummy = nil |
977 | end |
978 | |
979 | if playSound ~= false and self.isClient then |
980 | g_soundManager:playSample(spec.samples.toggleBelt) |
981 | g_soundManager:playSample(spec.samples.addBelt) |
982 | end |
983 | end |
984 | |
985 | return beltShape |
986 | end |
987 | |
988 | return nil |
989 | end |
1020 | function TensionBelts:getObjectToMount(belt) |
1021 | local spec = self.spec_tensionBelts |
1022 | |
1023 | local markerStart = spec.startNode |
1024 | local markerEnd = spec.endNode |
1025 | local offsetLeft = nil |
1026 | local offsetRight = nil |
1027 | local offset = nil |
1028 | local height = nil |
1029 | if belt ~= nil then |
1030 | markerStart = belt.startNode |
1031 | markerEnd = belt.endNode |
1032 | |
1033 | offsetLeft = belt.offsetLeft |
1034 | offsetRight = belt.offsetRight |
1035 | offset = belt.offset |
1036 | height = belt.height |
1037 | if offsetLeft == nil then |
1038 | if spec.sortedBelts[belt.id-1] ~= nil and spec.sortedBelts[belt.id-1].mesh ~= nil then |
1039 | local x,_,_ = localToLocal(markerStart, spec.sortedBelts[belt.id-1].startNode, 0, 0, 0) |
1040 | offsetLeft = math.abs(x) |
1041 | end |
1042 | end |
1043 | if offsetRight == nil then |
1044 | if spec.sortedBelts[belt.id+1] ~= nil and spec.sortedBelts[belt.id+1].mesh ~= nil then |
1045 | local x,_,_ = localToLocal(markerStart, spec.sortedBelts[belt.id+1].startNode, 0, 0, 0) |
1046 | offsetRight = math.abs(x) |
1047 | end |
1048 | end |
1049 | |
1050 | end |
1051 | |
1052 | if offsetLeft == nil then |
1053 | offsetLeft = spec.defaultOffsetSide |
1054 | end |
1055 | if offsetRight == nil then |
1056 | offsetRight = spec.defaultOffsetSide |
1057 | end |
1058 | if offset == nil then |
1059 | offset = spec.defaultOffset |
1060 | end |
1061 | if height == nil then |
1062 | height = spec.defaultHeight |
1063 | end |
1064 | |
1065 | local sizeX = (offsetLeft+offsetRight) * 0.5 |
1066 | local sizeY = height * 0.5 |
1067 | local _, _, width = localToLocal(markerEnd, markerStart, 0, 0, 0) |
1068 | local sizeZ = width*0.5 - 2*offset |
1069 | |
1070 | local centerX = (offsetLeft-offsetRight)*0.5 |
1071 | local centerY = height*0.5 |
1072 | local centerZ = width*0.5 |
1073 | local x,y,z = localToWorld(markerStart, centerX, centerY, centerZ) |
1074 | |
1075 | if TensionBelts.debugRendering then |
1076 | local box = {} |
1077 | box.points = {} |
1078 | local colorR = math.random(0, 1) |
1079 | local colorG = math.random(0, 1) |
1080 | local colorB = math.random(0, 1) |
1081 | box.color = {colorR,colorG,colorB} |
1082 | |
1083 | local blx, bly, blz = localToWorld(markerStart, centerX-sizeX, centerY-sizeY, centerZ-sizeZ) |
1084 | local brx, bry, brz = localToWorld(markerStart, centerX+sizeX, centerY-sizeY, centerZ-sizeZ) |
1085 | local flx, fly, flz = localToWorld(markerStart, centerX-sizeX, centerY-sizeY, centerZ+sizeZ) |
1086 | local frx, fry, frz = localToWorld(markerStart, centerX+sizeX, centerY-sizeY, centerZ+sizeZ) |
1087 | |
1088 | local tblx, tbly, tblz = localToWorld(markerStart, centerX-sizeX, centerY+sizeY, centerZ-sizeZ) |
1089 | local tbrx, tbry, tbrz = localToWorld(markerStart, centerX+sizeX, centerY+sizeY, centerZ-sizeZ) |
1090 | local tflx, tfly, tflz = localToWorld(markerStart, centerX-sizeX, centerY+sizeY, centerZ+sizeZ) |
1091 | local tfrx, tfry, tfrz = localToWorld(markerStart, centerX+sizeX, centerY+sizeY, centerZ+sizeZ) |
1092 | |
1093 | table.insert(box.points, { blx, bly, blz } ) -- lower: lb |
1094 | table.insert(box.points, { brx, bry, brz } ) -- rb |
1095 | table.insert(box.points, { frx, fry, frz } ) -- rf |
1096 | table.insert(box.points, { flx, fly, flz } ) -- lf |
1097 | |
1098 | table.insert(box.points, { tblx, tbly, tblz } ) -- upper: lb |
1099 | table.insert(box.points, { tbrx, tbry, tbrz } ) -- rb |
1100 | table.insert(box.points, { tfrx, tfry, tfrz } ) -- rf |
1101 | table.insert(box.points, { tflx, tfly, tflz } ) -- lf |
1102 | table.insert(box.points, { x, y, z } ) -- center |
1103 | |
1104 | spec.checkBoxes[markerStart] = box |
1105 | end |
1106 | |
1107 | local rx,ry,rz = getWorldRotation(markerStart) |
1108 | spec.objectsInTensionBeltRange = {} |
1109 | spec.numObjectsIntensionBeltRange = 0 |
1110 | |
1111 | overlapBox(x, y, z, rx, ry, rz, sizeX, sizeY, sizeZ, "objectOverlapCallback", self, CollisionMask.TRIGGER_DYNAMIC_MOUNT, true, false, true) |
1112 | |
1113 | return spec.objectsInTensionBeltRange, spec.numObjectsIntensionBeltRange |
1114 | end |
27 | function TensionBelts.initSpecialization() |
28 | g_configurationManager:addConfigurationType("tensionBelts", g_i18n:getText("configuration_tensionBelts"), "tensionBelts", nil, nil, nil, ConfigurationUtil.SELECTOR_MULTIOPTION) |
29 | |
30 | local schema = Vehicle.xmlSchema |
31 | schema:setXMLSpecializationType("TensionBelts") |
32 | |
33 | local key = "vehicle.tensionBelts.tensionBeltsConfigurations.tensionBeltsConfiguration(?).tensionBelts" |
34 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, "vehicle.tensionBelts.tensionBeltsConfigurations.tensionBeltsConfiguration(?)") |
35 | |
36 | schema:register(XMLValueType.FLOAT, key .. "#totalInteractionRadius", "Total interaction radius", 6) |
37 | schema:register(XMLValueType.FLOAT, key .. "#interactionRadius", "Interaction radius", 1) |
38 | schema:register(XMLValueType.NODE_INDEX, key .. "#interactionBaseNode", "Interaction base node", "Vehicle root node") |
39 | schema:register(XMLValueType.NODE_INDEX, key .. "#activationTrigger", "Activation trigger") |
40 | schema:register(XMLValueType.STRING, key .. "#tensionBeltType", "Supports tension belts", "basic") |
41 | |
42 | schema:register(XMLValueType.FLOAT, key .. "#width", "Belt width", "Used from belt definitions") |
43 | schema:register(XMLValueType.FLOAT, key .. "#ratchetPosition", "Ratchet position") |
44 | schema:register(XMLValueType.BOOL, key .. "#useHooks", "Use hooks", true) |
45 | schema:register(XMLValueType.FLOAT, key .. "#maxEdgeLength", "Max. edge length", 0.1) |
46 | schema:register(XMLValueType.FLOAT, key .. "#geometryBias", "Geometry bias", 0.01) |
47 | schema:register(XMLValueType.FLOAT, key .. "#defaultOffsetSide", "Default offset side", 0.1) |
48 | schema:register(XMLValueType.FLOAT, key .. "#defaultOffset", "Default offset", 0) |
49 | schema:register(XMLValueType.FLOAT, key .. "#defaultHeight", "Default height", 5) |
50 | |
51 | schema:register(XMLValueType.BOOL, key .. "#allowFoldingWhileFasten", "Folding is allowed while tension belts are fasten", true) |
52 | |
53 | schema:register(XMLValueType.NODE_INDEX, key .. "#linkNode", "Link node") |
54 | schema:register(XMLValueType.NODE_INDEX, key .. "#rootNode", "Root node", "Root component") |
55 | schema:register(XMLValueType.NODE_INDEX, key .. "#jointNode", "Joint node", "rootNode") |
56 | |
57 | schema:register(XMLValueType.NODE_INDEX, key .. ".tensionBelt(?)#startNode", "Start node") |
58 | schema:register(XMLValueType.NODE_INDEX, key .. ".tensionBelt(?)#endNode", "End node") |
59 | schema:register(XMLValueType.FLOAT, key .. ".tensionBelt(?)#offsetLeft", "Offset left") |
60 | schema:register(XMLValueType.FLOAT, key .. ".tensionBelt(?)#offsetRight", "Offset right") |
61 | schema:register(XMLValueType.FLOAT, key .. ".tensionBelt(?)#offset", "Offset") |
62 | schema:register(XMLValueType.FLOAT, key .. ".tensionBelt(?)#height", "Height") |
63 | schema:register(XMLValueType.NODE_INDEX, key .. ".tensionBelt(?).intersectionNode(?)#node", "Intersection node") |
64 | |
65 | SoundManager.registerSampleXMLPaths(schema, key .. ".sounds", "toggleBelt") |
66 | SoundManager.registerSampleXMLPaths(schema, key .. ".sounds", "addBelt") |
67 | SoundManager.registerSampleXMLPaths(schema, key .. ".sounds", "removeBelt") |
68 | |
69 | schema:setXMLSpecializationType() |
70 | |
71 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
72 | schemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?).tensionBelts.belt(?)#isActive", "Belt is active", false) |
73 | end |
703 | function TensionBelts:lockTensionBeltObject(objectId, objectsToJointTable, isDynamic, jointNode, object) |
704 | if objectsToJointTable[objectId] == nil then |
705 | local useDynamicMount = false |
706 | local useKinematicMount = false |
707 | local useSplitShapeMount = false |
708 | |
709 | if isDynamic then |
710 | if self.isServer then |
711 | useDynamicMount = true |
712 | end |
713 | else |
714 | if object ~= nil and object.mountKinematic ~= nil then |
715 | local allowKinematicMounting = true |
716 | if object.getSupportsMountKinematic ~= nil and not object:getSupportsMountKinematic() then |
717 | allowKinematicMounting = false |
718 | end |
719 | |
720 | if object.rootVehicle ~= nil then |
721 | local vehicles = object.rootVehicle.childVehicles |
722 | if #vehicles > 1 then |
723 | allowKinematicMounting = false |
724 | end |
725 | end |
726 | |
727 | if allowKinematicMounting then |
728 | useKinematicMount = true |
729 | else |
730 | useDynamicMount = true |
731 | end |
732 | else |
733 | -- only for split shapes on server side |
734 | if self.isServer then |
735 | useSplitShapeMount = true |
736 | end |
737 | end |
738 | end |
739 | |
740 | if useDynamicMount then |
741 | local constr = JointConstructor.new() |
742 | constr:setActors(jointNode, objectId) |
743 | |
744 | -- create joint at center of mass to avoid jittering (e.g. tree trunks pivots are not at center of mass position) |
745 | -- create a transformGroup at the joint position to move the joint afterwards based on this transformGroup |
746 | local jointTransform = createTransformGroup("tensionBeltJoint") |
747 | link(self.spec_tensionBelts.linkNode, jointTransform) |
748 | local x,y,z = localToWorld(objectId, getCenterOfMass(objectId)) |
749 | setWorldTranslation(jointTransform, x,y,z) |
750 | |
751 | constr:setJointTransforms(jointTransform, jointTransform) |
752 | |
753 | constr:setRotationLimit(0, 0, 0) |
754 | constr:setRotationLimit(1, 0, 0) |
755 | constr:setRotationLimit(2, 0, 0) |
756 | |
757 | local springForce = 1000 |
758 | local springDamping = 10 |
759 | constr:setRotationLimitSpring(springForce, springDamping, springForce, springDamping, springForce, springDamping) |
760 | constr:setTranslationLimitSpring(springForce, springDamping, springForce, springDamping, springForce, springDamping) |
761 | local jointIndex = constr:finalize() |
762 | |
763 | if object ~= nil then |
764 | if object.setReducedComponentMass ~= nil then |
765 | object:setReducedComponentMass(true) |
766 | self:setMassDirty() |
767 | end |
768 | if object.setCanBeSold ~= nil then |
769 | object:setCanBeSold(false) |
770 | end |
771 | end |
772 | |
773 | objectsToJointTable[objectId] = {jointIndex=jointIndex, jointTransform=jointTransform, object=object} |
774 | elseif useKinematicMount then |
775 | local parentNode = getParent(objectId) |
776 | local x, y, z = localToLocal(objectId, jointNode, 0, 0, 0) |
777 | local rx, ry, rz = localRotationToLocal(objectId, jointNode, 0, 0, 0) |
778 | |
779 | object:mountKinematic(self, jointNode, x, y, z, rx, ry, rz) |
780 | |
781 | objectsToJointTable[objectId] = {parent=parentNode, object=object} |
782 | elseif useSplitShapeMount then |
783 | local parentNode = getParent(objectId) |
784 | local x, y, z = localToLocal(objectId, jointNode, 0, 0, 0) |
785 | local rx, ry, rz = localRotationToLocal(objectId, jointNode, 0, 0, 0) |
786 | |
787 | setRigidBodyType(objectId, RigidBodyType.KINEMATIC) |
788 | link(jointNode, objectId) |
789 | setTranslation(objectId, x, y, z) |
790 | setRotation(objectId, rx, ry, rz) |
791 | objectsToJointTable[objectId] = {parent=parentNode, object=object} |
792 | end |
793 | end |
794 | end |
123 | function TensionBelts:onLoad(savegame) |
124 | local spec = self.spec_tensionBelts |
125 | |
126 | local tensionBeltConfigurationId = Utils.getNoNil(self.configurations["tensionBelts"], 1) |
127 | local configKey = string.format("vehicle.tensionBelts.tensionBeltsConfigurations.tensionBeltsConfiguration(%d).tensionBelts", tensionBeltConfigurationId -1) |
128 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.tensionBelts.tensionBeltsConfigurations.tensionBeltsConfiguration", tensionBeltConfigurationId , self.components, self) |
129 | |
130 | |
131 | spec.hasTensionBelts = true |
132 | if not self.xmlFile:hasProperty(configKey) then |
133 | spec.hasTensionBelts = false |
134 | |
135 | SpecializationUtil.removeEventListener(self, "onPostLoad", TensionBelts) |
136 | SpecializationUtil.removeEventListener(self, "onDelete", TensionBelts) |
137 | SpecializationUtil.removeEventListener(self, "onPreDelete", TensionBelts) |
138 | SpecializationUtil.removeEventListener(self, "onReadStream", TensionBelts) |
139 | SpecializationUtil.removeEventListener(self, "onWriteStream", TensionBelts) |
140 | SpecializationUtil.removeEventListener(self, "onUpdate", TensionBelts) |
141 | SpecializationUtil.removeEventListener(self, "onUpdateTick", TensionBelts) |
142 | SpecializationUtil.removeEventListener(self, "onDraw", TensionBelts) |
143 | SpecializationUtil.removeEventListener(self, "onRegisterActionEvents", TensionBelts) |
144 | |
145 | return |
146 | end |
147 | |
148 | spec.belts = {} |
149 | spec.tensionBelts = {} |
150 | spec.singleBelts = {} |
151 | spec.sortedBelts = {} |
152 | |
153 | spec.activatable = TensionBeltsActivatable.new(self) |
154 | |
155 | spec.totalInteractionRadius = self.xmlFile:getValue(configKey.."#totalInteractionRadius", 6) |
156 | spec.interactionRadius = self.xmlFile:getValue(configKey.."#interactionRadius", 1) |
157 | spec.interactionBaseNode = self.xmlFile:getValue(configKey.."#interactionBaseNode", self.rootNode, self.components, self.i3dMappings) |
158 | spec.activationTrigger = self.xmlFile:getValue(configKey.."#activationTrigger", nil, self.components, self.i3dMappings) |
159 | if spec.activationTrigger ~= nil then |
160 | if not CollisionFlag.getHasFlagSet(spec.activationTrigger, CollisionFlag.TRIGGER_PLAYER) then |
161 | Logging.xmlError(self.xmlFile, "Missing player collision mask bit '%d' for tension belt trigger.", CollisionFlag.getBit(CollisionFlag.TRIGGER_PLAYER)) |
162 | else |
163 | addTrigger(spec.activationTrigger, "tensionBeltActivationTriggerCallback", self) |
164 | end |
165 | end |
166 | |
167 | spec.allowFoldingWhileFasten = self.xmlFile:getValue(configKey.."#allowFoldingWhileFasten", true) |
168 | |
169 | spec.isPlayerInTrigger = false |
170 | spec.checkSizeOffsets = {0*0.5, 5*0.5, 3*0.5} |
171 | spec.numObjectsIntensionBeltRange = 0 |
172 | |
173 | local tensionBeltType = self.xmlFile:getValue(configKey.."#tensionBeltType", "basic") |
174 | local beltData = g_tensionBeltManager:getBeltData(tensionBeltType) |
175 | |
176 | if beltData ~= nil then |
177 | spec.width = self.xmlFile:getValue(configKey.."#width") |
178 | spec.ratchetPosition = self.xmlFile:getValue(configKey.."#ratchetPosition") |
179 | spec.useHooks = self.xmlFile:getValue(configKey.."#useHooks", true) |
180 | spec.maxEdgeLength = self.xmlFile:getValue(configKey.."#maxEdgeLength", 0.1) |
181 | spec.geometryBias = self.xmlFile:getValue(configKey.."#geometryBias", 0.01) |
182 | spec.defaultOffsetSide = self.xmlFile:getValue(configKey.."#defaultOffsetSide", 0.1) |
183 | spec.defaultOffset = self.xmlFile:getValue(configKey.."#defaultOffset", 0) |
184 | spec.defaultHeight = self.xmlFile:getValue(configKey.."#defaultHeight", 5) |
185 | spec.beltData = beltData |
186 | spec.linkNode = self.xmlFile:getValue(configKey.."#linkNode", nil, self.components, self.i3dMappings) |
187 | spec.rootNode = self.xmlFile:getValue(configKey.."#rootNode", self.components[1].node, self.components, self.i3dMappings) |
188 | spec.jointNode = self.xmlFile:getValue(configKey.."#jointNode", spec.rootNode, self.components, self.i3dMappings) |
189 | spec.checkTimerDuration = 500 |
190 | spec.checkTimer = spec.checkTimerDuration |
191 | |
192 | if spec.linkNode == nil then |
193 | Logging.xmlError(self.xmlFile, "No tension belts link node given at %s%s", configKey, "#linkNode") |
194 | self:setLoadingState(VehicleLoadingUtil.VEHICLE_LOAD_ERROR) |
195 | return |
196 | end |
197 | |
198 | if getRigidBodyType(spec.jointNode) ~= RigidBodyType.DYNAMIC and getRigidBodyType(spec.jointNode) ~= RigidBodyType.KINEMATIC then |
199 | Logging.xmlError(self.xmlFile, "Given jointNode '"..getName(spec.jointNode).."' has invalid rigidBodyType. Have to be 'Dynamic' or 'Kinematic'! Using '"..getName(self.components[1].node).."' instead!") |
200 | spec.jointNode = self.components[1].node |
201 | end |
202 | |
203 | local rigidBodyType = getRigidBodyType(spec.jointNode) |
204 | spec.isDynamic = rigidBodyType == RigidBodyType.DYNAMIC |
205 | |
206 | local x,y,z = localToLocal(spec.linkNode, spec.jointNode, 0,0,0) |
207 | local rx,ry,rz = localRotationToLocal(spec.linkNode, spec.jointNode, 0,0,0) |
208 | spec.linkNodePosition = {x, y, z} |
209 | spec.linkNodeRotation = {rx, ry, rz} |
210 | |
211 | spec.jointComponent = self:getParentComponent(spec.jointNode) |
212 | |
213 | local i = 0 |
214 | while true do |
215 | local key = string.format(configKey..".tensionBelt(%d)", i) |
216 | if not self.xmlFile:hasProperty(key) then |
217 | break |
218 | end |
219 | |
220 | if #spec.sortedBelts == 2^TensionBelts.NUM_SEND_BITS then |
221 | Logging.xmlWarning(self.xmlFile, "Max number of tension belts is ".. 2^TensionBelts.NUM_SEND_BITS.."!") |
222 | break |
223 | end |
224 | |
225 | local startNode = self.xmlFile:getValue(key.."#startNode", nil, self.components, self.i3dMappings) |
226 | local endNode = self.xmlFile:getValue(key.."#endNode", nil, self.components, self.i3dMappings) |
227 | if startNode ~= nil and endNode ~= nil then |
228 | local endX,endY,_ = getTranslation(endNode) |
229 | if math.abs(endX) < 0.0001 and math.abs(endY) < 0.0001 then |
230 | if spec.linkNode == nil then |
231 | spec.linkNode = getParent(startNode) |
232 | end |
233 | if spec.startNode == nil then |
234 | spec.startNode = startNode |
235 | end |
236 | spec.endNode = endNode |
237 | |
238 | local offsetLeft = self.xmlFile:getValue(key.."#offsetLeft") |
239 | local offsetRight = self.xmlFile:getValue(key.."#offsetRight") |
240 | local offset = self.xmlFile:getValue(key.."#offset") |
241 | local height = self.xmlFile:getValue(key.."#height") |
242 | |
243 | local intersectionNodes = {} |
244 | local j = 0 |
245 | while true do |
246 | local intersectionKey = string.format(key..".intersectionNode(%d)", j) |
247 | if not self.xmlFile:hasProperty(intersectionKey) then |
248 | break |
249 | end |
250 | local node = self.xmlFile:getValue(intersectionKey.."#node", nil, self.components, self.i3dMappings) |
251 | if node ~= nil then |
252 | table.insert(intersectionNodes, node) |
253 | end |
254 | j = j + 1 |
255 | end |
256 | |
257 | local belt = {id=i+1, startNode=startNode, endNode=endNode, offsetLeft=offsetLeft, offsetRight=offsetRight, offset=offset, height=height, mesh=nil, intersectionNodes=intersectionNodes, dummy=nil, objectsToMount=nil} |
258 | spec.singleBelts[belt] = belt |
259 | table.insert(spec.sortedBelts, belt) |
260 | else |
261 | Logging.xmlWarning(self.xmlFile, "x and y position of endNode need to be 0 for tension belt '"..key.."'") |
262 | end |
263 | end |
264 | |
265 | i = i + 1 |
266 | end |
267 | |
268 | local minX, minZ = math.huge, math.huge |
269 | local maxX, maxZ = -math.huge, -math.huge |
270 | for _, belt in pairs(spec.singleBelts) do |
271 | local sx,_,sz = localToLocal(belt.startNode, spec.interactionBaseNode, 0, 0, 0) |
272 | local ex,_,ez = localToLocal(belt.endNode, spec.interactionBaseNode, 0, 0, 0) |
273 | minX = math.min(minX, sx, ex) |
274 | minZ = math.min(minZ, sz, ez) |
275 | maxX = math.max(maxX, sx, ex) |
276 | maxZ = math.max(maxZ, sz, ez) |
277 | end |
278 | |
279 | spec.interactionBasePointX = (maxX + minX) / 2 |
280 | spec.interactionBasePointZ = (maxZ + minZ) / 2 |
281 | |
282 | for _, belt in pairs(spec.singleBelts) do |
283 | local sx,_,sz = localToLocal(belt.startNode, spec.interactionBaseNode, 0, 0, 0) |
284 | local sl = MathUtil.vector2Length(spec.interactionBasePointX-sx, spec.interactionBasePointZ-sz)+1 |
285 | local el = MathUtil.vector2Length(spec.interactionBasePointX-sx, spec.interactionBasePointZ-sz)+1 |
286 | spec.totalInteractionRadius = math.max(spec.totalInteractionRadius, sl, el) |
287 | end |
288 | |
289 | else |
290 | Logging.xmlWarning(self.xmlFile, "No belt data found for tension belt type %s", tensionBeltType) |
291 | end |
292 | |
293 | spec.hasTensionBelts = #spec.sortedBelts > 0 |
294 | |
295 | spec.checkBoxes = {} |
296 | spec.objectsToJoint = {} |
297 | spec.isPlayerInRange = false |
298 | spec.currentBelt = nil |
299 | spec.areBeltsFasten = false |
300 | spec.fastedAllBeltsIndex = -1 |
301 | spec.fastedAllBeltsState = true |
302 | |
303 | if self.isClient then |
304 | spec.samples = {} |
305 | spec.samples.toggleBelt = g_soundManager:loadSampleFromXML(self.xmlFile, configKey .. ".sounds", "toggleBelt", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self) |
306 | spec.samples.addBelt = g_soundManager:loadSampleFromXML(self.xmlFile, configKey .. ".sounds", "addBelt", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self) |
307 | spec.samples.removeBelt = g_soundManager:loadSampleFromXML(self.xmlFile, configKey .. ".sounds", "removeBelt", self.baseDirectory, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self) |
308 | end |
309 | |
310 | spec.texts = {} |
311 | spec.texts.warningFoldingTensionBelts = g_i18n:getText("warning_foldingNotWhileTensionBeltsFasten") |
312 | |
313 | if not spec.hasTensionBelts then |
314 | SpecializationUtil.removeEventListener(self, "onPostLoad", TensionBelts) |
315 | SpecializationUtil.removeEventListener(self, "onDelete", TensionBelts) |
316 | SpecializationUtil.removeEventListener(self, "onPreDelete", TensionBelts) |
317 | SpecializationUtil.removeEventListener(self, "onReadStream", TensionBelts) |
318 | SpecializationUtil.removeEventListener(self, "onWriteStream", TensionBelts) |
319 | SpecializationUtil.removeEventListener(self, "onUpdate", TensionBelts) |
320 | SpecializationUtil.removeEventListener(self, "onUpdateTick", TensionBelts) |
321 | SpecializationUtil.removeEventListener(self, "onDraw", TensionBelts) |
322 | SpecializationUtil.removeEventListener(self, "onRegisterActionEvents", TensionBelts) |
323 | |
324 | -- delete activation trigger as onDelete event is removed |
325 | if spec.activationTrigger ~= nil then |
326 | removeTrigger(spec.activationTrigger) |
327 | spec.activationTrigger = nil |
328 | end |
329 | else |
330 | g_messageCenter:subscribe(MessageType.TREE_SHAPE_CUT, self.onTensionBeltTreeShapeCut, self) |
331 | end |
332 | end |
436 | function TensionBelts:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
437 | local spec = self.spec_tensionBelts |
438 | |
439 | if spec.beltsToLoad ~= nil then |
440 | if #spec.beltsToLoad > 0 then |
441 | local beltIndex = spec.beltsToLoad[#spec.beltsToLoad] |
442 | |
443 | local noEventSend = false |
444 | if not self.isServer then |
445 | -- do not resend event to server after sync. Only send event after loading to make sure client have visible tension belts |
446 | noEventSend = true |
447 | end |
448 | self:setTensionBeltsActive(true, spec.sortedBelts[beltIndex].id, noEventSend, false) |
449 | |
450 | table.remove(spec.beltsToLoad, #spec.beltsToLoad) |
451 | else |
452 | spec.beltsToLoad = nil |
453 | end |
454 | end |
455 | |
456 | if self.isServer and spec.isDynamic then |
457 | local x,y,z = localToLocal(spec.linkNode, spec.jointNode, 0,0,0) |
458 | local rx,ry,rz = localRotationToLocal(spec.linkNode, spec.jointNode, 0,0,0) |
459 | |
460 | local isDirty = false |
461 | if math.abs(x - spec.linkNodePosition[1]) > 0.001 or math.abs(y - spec.linkNodePosition[2]) > 0.001 or math.abs(z - spec.linkNodePosition[3]) > 0.001 or |
462 | math.abs(rx - spec.linkNodeRotation[1]) > 0.001 or math.abs(ry - spec.linkNodeRotation[2]) > 0.001 or math.abs(rz - spec.linkNodeRotation[3]) > 0.001 then |
463 | isDirty = true |
464 | end |
465 | if isDirty then |
466 | spec.linkNodePosition[1], spec.linkNodePosition[2], spec.linkNodePosition[3] = x, y, z |
467 | spec.linkNodeRotation[1], spec.linkNodeRotation[2], spec.linkNodeRotation[3] = rx, ry, rz |
468 | for _, joint in pairs(spec.objectsToJoint) do |
469 | setJointFrame(joint.jointIndex, 0, joint.jointTransform) |
470 | end |
471 | end |
472 | end |
473 | |
474 | if self.isClient then |
475 | if spec.fastedAllBeltsIndex > 0 then |
476 | local belt = spec.sortedBelts[spec.fastedAllBeltsIndex] |
477 | if belt ~= nil then |
478 | self:setTensionBeltsActive(spec.fastedAllBeltsState, belt.id, false) |
479 | end |
480 | |
481 | spec.fastedAllBeltsIndex = spec.fastedAllBeltsIndex + 1 |
482 | if spec.fastedAllBeltsIndex > #spec.sortedBelts then |
483 | spec.fastedAllBeltsIndex = -1 |
484 | end |
485 | end |
486 | end |
487 | |
488 | if TensionBelts.debugRendering then |
489 | for belt,_ in pairs(spec.belts) do |
490 | DebugUtil.drawDebugNode(belt) |
491 | for i=0, getNumOfChildren(belt)-1 do |
492 | DebugUtil.drawDebugNode(getChildAt(belt, i)) |
493 | end |
494 | end |
495 | |
496 | if spec.checkBoxes ~= nil then |
497 | for _, box in pairs(spec.checkBoxes) do |
498 | local p = box.points |
499 | local c = box.color |
500 | |
501 | -- bottom |
502 | drawDebugLine(p[1][1],p[1][2],p[1][3], c[1],c[2],c[3], p[2][1],p[2][2],p[2][3], c[1],c[2],c[3]) |
503 | drawDebugLine(p[2][1],p[2][2],p[2][3], c[1],c[2],c[3], p[3][1],p[3][2],p[3][3], c[1],c[2],c[3]) |
504 | drawDebugLine(p[3][1],p[3][2],p[3][3], c[1],c[2],c[3], p[4][1],p[4][2],p[4][3], c[1],c[2],c[3]) |
505 | drawDebugLine(p[4][1],p[4][2],p[4][3], c[1],c[2],c[3], p[1][1],p[1][2],p[1][3], c[1],c[2],c[3]) |
506 | -- top |
507 | drawDebugLine(p[5][1],p[5][2],p[5][3], c[1],c[2],c[3], p[6][1],p[6][2],p[6][3], c[1],c[2],c[3]) |
508 | drawDebugLine(p[6][1],p[6][2],p[6][3], c[1],c[2],c[3], p[7][1],p[7][2],p[7][3], c[1],c[2],c[3]) |
509 | drawDebugLine(p[7][1],p[7][2],p[7][3], c[1],c[2],c[3], p[8][1],p[8][2],p[8][3], c[1],c[2],c[3]) |
510 | drawDebugLine(p[8][1],p[8][2],p[8][3], c[1],c[2],c[3], p[5][1],p[5][2],p[5][3], c[1],c[2],c[3]) |
511 | -- left |
512 | drawDebugLine(p[1][1],p[1][2],p[1][3], c[1],c[2],c[3], p[5][1],p[5][2],p[5][3], c[1],c[2],c[3]) |
513 | drawDebugLine(p[4][1],p[4][2],p[4][3], c[1],c[2],c[3], p[8][1],p[8][2],p[8][3], c[1],c[2],c[3]) |
514 | -- right |
515 | drawDebugLine(p[2][1],p[2][2],p[2][3], c[1],c[2],c[3], p[6][1],p[6][2],p[6][3], c[1],c[2],c[3]) |
516 | drawDebugLine(p[3][1],p[3][2],p[3][3], c[1],c[2],c[3], p[7][1],p[7][2],p[7][3], c[1],c[2],c[3]) |
517 | drawDebugPoint(p[9][1], p[9][2], p[9][3], 1, 1, 1, 1) |
518 | end |
519 | end |
520 | |
521 | local a,b,c = localToWorld(spec.interactionBaseNode, spec.interactionBasePointX, 0, spec.interactionBasePointZ) |
522 | drawDebugPoint(a,b,c, 0,0,1, 1,1,1,1) |
523 | |
524 | for i=0, 360-10, 10 do |
525 | local x,y,z = localToWorld(spec.interactionBaseNode, spec.interactionBasePointX+math.cos(math.rad(i))*spec.totalInteractionRadius,0, spec.interactionBasePointZ+math.sin(math.rad(i))*spec.totalInteractionRadius) |
526 | drawDebugPoint(x, y, z, 1,1,1,1) |
527 | for _, belt in pairs(spec.singleBelts) do |
528 | x,y,z = localToWorld(belt.startNode, math.cos(math.rad(i))*spec.interactionRadius, 0, math.sin(math.rad(i))*spec.interactionRadius) |
529 | drawDebugPoint(x, y, z, 0,1,0,1) |
530 | x,y,z = localToWorld(belt.endNode, math.cos(math.rad(i))*spec.interactionRadius, 0, math.sin(math.rad(i))*spec.interactionRadius) |
531 | drawDebugPoint(x, y, z, 1,0,0,1) |
532 | end |
533 | end |
534 | end |
535 | |
536 | if spec.isPlayerInTrigger or spec.isPlayerInRange then |
537 | self:raiseActive() |
538 | end |
539 | |
540 | -- create tension belt preview and control activation |
541 | local currentBelt |
542 | local wasInRange = spec.isPlayerInRange |
543 | spec.isPlayerInRange, currentBelt = self:getIsPlayerInTensionBeltsRange() |
544 | if spec.isPlayerInRange then |
545 | if currentBelt ~= spec.currentBelt then |
546 | if spec.currentBelt ~= nil and spec.currentBelt.dummy ~= nil then |
547 | delete(spec.currentBelt.dummy) |
548 | spec.currentBelt.dummy = nil |
549 | end |
550 | spec.currentBelt = currentBelt |
551 | |
552 | if spec.currentBelt ~= nil and spec.currentBelt.mesh == nil then |
553 | local objects, _ = self:getObjectToMount(spec.currentBelt) |
554 | self:createTensionBelt(spec.currentBelt, true, objects) |
555 | end |
556 | end |
557 | g_currentMission.activatableObjectsSystem:addActivatable(spec.activatable) |
558 | else |
559 | if wasInRange then |
560 | g_currentMission.activatableObjectsSystem:removeActivatable(spec.activatable) |
561 | if spec.currentBelt ~= nil and spec.currentBelt.dummy ~= nil then |
562 | delete(spec.currentBelt.dummy) |
563 | spec.currentBelt.dummy = nil |
564 | spec.currentBelt = nil |
565 | end |
566 | end |
567 | end |
568 | end |