25 | function FillVolume.initSpecialization() |
26 | g_configurationManager:addConfigurationType("fillVolume", g_i18n:getText("configuration_fillVolume"), "fillVolume", nil, nil, nil, ConfigurationUtil.SELECTOR_MULTIOPTION) |
27 | |
28 | local schema = Vehicle.xmlSchema |
29 | schema:setXMLSpecializationType("FillVolume") |
30 | |
31 | local basePath = "vehicle.fillVolume.fillVolumeConfigurations.fillVolumeConfiguration(?)" |
32 | |
33 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".volumes.volume(?)#node", "Fill volume node") |
34 | schema:register(XMLValueType.INT, basePath .. ".volumes.volume(?)#fillUnitIndex", "Fill unit index") |
35 | schema:register(XMLValueType.FLOAT, basePath .. ".volumes.volume(?)#fillUnitFactor", "Fill unit factor", 1) |
36 | schema:register(XMLValueType.BOOL, basePath .. ".volumes.volume(?)#allSidePlanes", "All side planes", true) |
37 | schema:register(XMLValueType.BOOL, basePath .. ".volumes.volume(?)#retessellateTop", "Retessellate top plane for better triangulation quality", false) |
38 | |
39 | schema:register(XMLValueType.STRING, basePath .. ".volumes.volume(?)#defaultFillType", "Default fill type name") |
40 | schema:register(XMLValueType.STRING, basePath .. ".volumes.volume(?)#forcedVolumeFillType", "Forced fill type name") |
41 | |
42 | schema:register(XMLValueType.FLOAT, basePath .. ".volumes.volume(?)#maxDelta", "Max. heap size above above input surface [m]", 1.0) |
43 | schema:register(XMLValueType.ANGLE, basePath .. ".volumes.volume(?)#maxAllowedHeapAngle", "Max. allowed heap surface slope angle [deg]", 35) |
44 | schema:register(XMLValueType.FLOAT, basePath .. ".volumes.volume(?)#maxSurfaceDistanceError", "Max. allowed distance from input mesh surface to created fill plane mesh [m]", 0.05) |
45 | schema:register(XMLValueType.FLOAT, basePath .. ".volumes.volume(?)#maxSubDivEdgeLength", "Max. length of sub division edges [m]", 0.9) |
46 | schema:register(XMLValueType.FLOAT, basePath .. ".volumes.volume(?)#syncMaxSubDivEdgeLength", "Max. length of sub division edges used to sync in multiplayer [m]", 1.35) |
47 | |
48 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".volumes.volume(?).deformNode(?)#node", "Deformer node") |
49 | |
50 | FillVolume.registerInfoNodeXMLPaths(schema, "vehicle.fillVolume.loadInfos.loadInfo(?)") |
51 | FillVolume.registerInfoNodeXMLPaths(schema, "vehicle.fillVolume.unloadInfos.unloadInfo(?)") |
52 | |
53 | schema:register(XMLValueType.INT, "vehicle.fillVolume.heightNodes.heightNode(?)#fillVolumeIndex", "Fill volume index") |
54 | schema:register(XMLValueType.NODE_INDEX, "vehicle.fillVolume.heightNodes.heightNode(?).refNode(?)#node", "Reference node") |
55 | |
56 | schema:register(XMLValueType.NODE_INDEX, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#node", "Height node") |
57 | schema:register(XMLValueType.VECTOR_SCALE, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#baseScale", "Base scale", "1 1 1") |
58 | schema:register(XMLValueType.VECTOR_3, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#scaleAxis", "Scale axis", "0 0 0") |
59 | schema:register(XMLValueType.VECTOR_SCALE, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#scaleMax", "Max. scale", "0 0 0") |
60 | schema:register(XMLValueType.VECTOR_3, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#transAxis", "Translation axis", "0 0 0") |
61 | schema:register(XMLValueType.VECTOR_TRANS, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#transMax", "Max. translation", "0 0 0") |
62 | schema:register(XMLValueType.FLOAT, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#minHeight", "Min. fill volume height used for heigth node", 0) |
63 | schema:register(XMLValueType.FLOAT, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#heightOffset", "Fill plane height offset", 0) |
64 | schema:register(XMLValueType.BOOL, "vehicle.fillVolume.heightNodes.heightNode(?).node(?)#orientateToWorldY", "Orientate to world Y", false) |
65 | |
66 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, basePath) |
67 | |
68 | schema:register(XMLValueType.INT, Cylindered.MOVING_TOOL_XML_KEY .. ".fillVolume#fillVolumeIndex", "Fill Unit index which includes the deformers", 1) |
69 | schema:register(XMLValueType.VECTOR_N, Cylindered.MOVING_TOOL_XML_KEY .. ".fillVolume#deformerNodeIndices", "Indices of deformer nodes to update") |
70 | schema:register(XMLValueType.INT, Cylindered.MOVING_PART_XML_KEY .. ".fillVolume#fillVolumeIndex", "Fill Unit index which includes the deformers", 1) |
71 | schema:register(XMLValueType.VECTOR_N, Cylindered.MOVING_PART_XML_KEY .. ".fillVolume#deformerNodeIndices", "Indices of deformer nodes to update") |
72 | |
73 | schema:setXMLSpecializationType() |
74 | end |
387 | function FillVolume:loadFillVolume(xmlFile, key, entry) |
388 | local spec = self.spec_fillVolume |
389 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key .. "#index", key .. "#node") -- FS17 |
390 | |
391 | entry.baseNode = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
392 | if entry.baseNode == nil then |
393 | print("Warning: fillVolume '"..tostring(key).."' has an invalid 'node' in '"..self.configFileName.."'!") |
394 | return false |
395 | end |
396 | |
397 | local fillUnitIndex = xmlFile:getValue(key.."#fillUnitIndex") |
398 | entry.fillUnitIndex = fillUnitIndex |
399 | if fillUnitIndex == nil then |
400 | print("Warning: fillVolume '"..tostring(key).."' has no 'fillUnitIndex' given in '"..self.configFileName.."'!") |
401 | return false |
402 | end |
403 | if not self:getFillUnitExists(fillUnitIndex) then |
404 | print("Warning: fillVolume '"..tostring(key).."' has an invalid 'fillUnitIndex' in '"..self.configFileName.."'!") |
405 | return false |
406 | end |
407 | |
408 | entry.fillUnitFactor = xmlFile:getValue(key.."#fillUnitFactor", 1.0) |
409 | |
410 | if spec.fillUnitFillVolumeMapping[fillUnitIndex] == nil then |
411 | spec.fillUnitFillVolumeMapping[fillUnitIndex] = {fillVolumes={}, sumFactors=0} |
412 | end |
413 | table.insert(spec.fillUnitFillVolumeMapping[fillUnitIndex].fillVolumes, entry) |
414 | spec.fillUnitFillVolumeMapping[fillUnitIndex].sumFactors = entry.fillUnitFactor |
415 | |
416 | entry.allSidePlanes = xmlFile:getValue(key .. "#allSidePlanes", true) |
417 | entry.retessellateTop = xmlFile:getValue(key .. "#retessellateTop", false) |
418 | |
419 | local defaultFillTypeStr = xmlFile:getValue(key .. "#defaultFillType") |
420 | if defaultFillTypeStr ~= nil then |
421 | local defaultFillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(defaultFillTypeStr) |
422 | if defaultFillTypeIndex == nil then |
423 | print("Warning: Invalid defaultFillType '"..tostring(defaultFillTypeStr).."' for '"..tostring(key).."' in '"..self.configFileName.."'") |
424 | return false |
425 | else |
426 | entry.defaultFillType = defaultFillTypeIndex |
427 | end |
428 | else |
429 | entry.defaultFillType = self:getFillUnitFirstSupportedFillType(fillUnitIndex) |
430 | end |
431 | |
432 | local forcedVolumeFillTypeStr = xmlFile:getValue(key .. "#forcedVolumeFillType") |
433 | if forcedVolumeFillTypeStr ~= nil then |
434 | local forcedVolumeFillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(forcedVolumeFillTypeStr) |
435 | if forcedVolumeFillTypeIndex ~= nil then |
436 | entry.forcedVolumeFillType = forcedVolumeFillTypeIndex |
437 | else |
438 | print("Warning: Invalid forcedVolumeFillType '"..tostring(forcedVolumeFillTypeStr).."' for '"..tostring(key).."' in '"..self.configFileName.."'") |
439 | return false |
440 | end |
441 | end |
442 | |
443 | entry.maxDelta = xmlFile:getValue(key.."#maxDelta", 1.0) |
444 | entry.maxSurfaceAngle = xmlFile:getValue(key.."#maxAllowedHeapAngle", 35) |
445 | |
446 | entry.maxPhysicalSurfaceAngle = math.rad(35) |
447 | entry.maxSurfaceDistanceError = xmlFile:getValue(key.."#maxSurfaceDistanceError", 0.05) |
448 | entry.maxSubDivEdgeLength = xmlFile:getValue(key.."#maxSubDivEdgeLength", 0.9) |
449 | entry.syncMaxSubDivEdgeLength = xmlFile:getValue(key.."#syncMaxSubDivEdgeLength", 1.35) |
450 | |
451 | entry.uvPosition = {0, 0, 0} |
452 | |
453 | entry.deformers = {} |
454 | local j = 0 |
455 | while true do |
456 | local deformerKey = string.format("%s.deformNode(%d)", key, j) |
457 | if not xmlFile:hasProperty(deformerKey) then |
458 | break |
459 | end |
460 | |
461 | XMLUtil.checkDeprecatedXMLElements(xmlFile, deformerKey .. "#index", deformerKey .. "#node") -- FS17 |
462 | |
463 | local node = xmlFile:getValue(deformerKey .. "#node", nil, self.components, self.i3dMappings) |
464 | if node ~= nil then |
465 | local initPos = {localToLocal(node, entry.baseNode, 0,0,0)} |
466 | local deformer = {node = node, initPos = initPos, posX = initPos[1], posZ = initPos[3], polyline = nil, volume = entry.volume, baseNode = entry.baseNode} |
467 | table.insert(entry.deformers, deformer) |
468 | spec.fillVolumeDeformersByNode[node] = deformer |
469 | end |
470 | j = j + 1 |
471 | end |
472 | |
473 | entry.lastFillType = FillType.UNKNOWN |
474 | |
475 | return true |
476 | end |
526 | function FillVolume:loadFillVolumeHeightNode(xmlFile, key, entry) |
527 | entry.isDirty = false |
528 | |
529 | entry.fillVolumeIndex = xmlFile:getValue(key.."#fillVolumeIndex", 1) |
530 | |
531 | if self.spec_fillVolume.volumes[entry.fillVolumeIndex] == nil then |
532 | Logging.xmlWarning(self.xmlFile, "Invalid fillVolumeIndex '%d' for heightNode '%s'. Igoring heightNode!", entry.fillVolumeIndex, key) |
533 | return false |
534 | end |
535 | |
536 | entry.refNodes = {} |
537 | local i = 0 |
538 | while true do |
539 | local nodeKey = key .. string.format(".refNode(%d)", i) |
540 | if not xmlFile:hasProperty(nodeKey) then |
541 | break |
542 | end |
543 | |
544 | XMLUtil.checkDeprecatedXMLElements(xmlFile, nodeKey .. "#index", nodeKey .. "#node") -- FS17 to FS19 |
545 | |
546 | local node = xmlFile:getValue(nodeKey.."#node", nil, self.components, self.i3dMappings) |
547 | if node ~= nil then |
548 | table.insert(entry.refNodes, {refNode = node}) |
549 | else |
550 | Logging.xmlWarning(self.xmlFile, "Missing node for '%s'", nodeKey) |
551 | end |
552 | |
553 | i = i + 1 |
554 | end |
555 | |
556 | entry.nodes = {} |
557 | i = 0 |
558 | while true do |
559 | local nodeKey = key .. string.format(".node(%d)", i) |
560 | if not xmlFile:hasProperty(nodeKey) then |
561 | break |
562 | end |
563 | |
564 | XMLUtil.checkDeprecatedXMLElements(xmlFile, nodeKey .. "#index", nodeKey .. "#node") -- FS17 to FS19 |
565 | |
566 | local node = xmlFile:getValue(nodeKey.."#node", nil, self.components, self.i3dMappings) |
567 | if node ~= nil then |
568 | local nodeEntry = {} |
569 | nodeEntry.node = node |
570 | nodeEntry.baseScale = xmlFile:getValue(nodeKey.."#baseScale", "1 1 1", true) |
571 | nodeEntry.scaleAxis = xmlFile:getValue(nodeKey.."#scaleAxis", "0 0 0", true) |
572 | nodeEntry.scaleMax = xmlFile:getValue(nodeKey.."#scaleMax", "0 0 0", true) |
573 | nodeEntry.basePosition = {getTranslation(node)} |
574 | nodeEntry.transAxis = xmlFile:getValue(nodeKey.."#transAxis", "0 0 0", true) |
575 | nodeEntry.transMax = xmlFile:getValue(nodeKey.."#transMax", "0 0 0", true) |
576 | nodeEntry.minHeight = xmlFile:getValue(nodeKey.."#minHeight", 0) |
577 | nodeEntry.heightOffset = xmlFile:getValue(nodeKey.."#heightOffset", 0) |
578 | nodeEntry.orientateToWorldY = xmlFile:getValue(nodeKey.."#orientateToWorldY", false) |
579 | table.insert(entry.nodes, nodeEntry) |
580 | else |
581 | Logging.xmlWarning(self.xmlFile, "Missing node for '%s'", nodeKey) |
582 | end |
583 | |
584 | i = i + 1 |
585 | end |
586 | |
587 | return true |
588 | end |
715 | function FillVolume:onFillUnitFillLevelChanged(fillUnitIndex, fillLevelDelta, _, toolType, fillPositionData, appliedDelta) |
716 | local spec = self.spec_fillVolume |
717 | |
718 | local mapping = spec.fillUnitFillVolumeMapping[fillUnitIndex] |
719 | if mapping == nil then |
720 | return |
721 | end |
722 | |
723 | local fillLevel = self:getFillUnitFillLevel(fillUnitIndex) |
724 | local fillType = self:getFillUnitFillType(fillUnitIndex) |
725 | |
726 | for _, volume in ipairs(mapping.fillVolumes) do |
727 | local baseNode = volume.baseNode |
728 | local volumeNode = volume.volume |
729 | if baseNode == nil or volumeNode == nil then |
730 | return |
731 | end |
732 | |
733 | if volume.forcedFillType ~= nil then |
734 | fillType = volume.forcedFillType |
735 | end |
736 | if fillLevel == 0 then |
737 | volume.forcedFillType = nil |
738 | end |
739 | |
740 | if fillType ~= volume.lastFillType then |
741 | local maxPhysicalSurfaceAngle |
742 | local fillTypeInfo = g_fillTypeManager:getFillTypeByIndex(fillType) |
743 | if fillTypeInfo ~= nil then |
744 | maxPhysicalSurfaceAngle = fillTypeInfo.maxPhysicalSurfaceAngle |
745 | end |
746 | if maxPhysicalSurfaceAngle ~= nil then |
747 | if volume.volume ~= nil then |
748 | setFillPlaneMaxPhysicalSurfaceAngle(volume.volume, maxPhysicalSurfaceAngle) |
749 | volume.maxPhysicalSurfaceAngle = maxPhysicalSurfaceAngle |
750 | end |
751 | end |
752 | end |
753 | |
754 | setVisibility(volume.volume, fillLevel > 0) |
755 | |
756 | if fillType ~= FillType.UNKNOWN and fillType ~= volume.lastFillType then |
757 | local textureArrayIndex = g_fillTypeManager:getTextureArrayIndexByFillTypeIndex(fillType) |
758 | if textureArrayIndex ~= nil then |
759 | setShaderParameter(volume.volume, "fillTypeId", textureArrayIndex - 1, 0, 0, 0, false) |
760 | end |
761 | end |
762 | |
763 | if fillPositionData ~= nil then |
764 | for i=#spec.availableFillNodes, 1, -1 do |
765 | spec.availableFillNodes[i] = nil |
766 | end |
767 | |
768 | if fillPositionData.nodes ~= nil then |
769 | local neededPriority = fillPositionData.nodes[1].priority |
770 | |
771 | while #spec.availableFillNodes == 0 and neededPriority >= 1 do |
772 | for _,node in pairs(fillPositionData.nodes) do |
773 | if node.priority >= neededPriority then |
774 | local doInsert = true |
775 | |
776 | if node.minHeight ~= nil or node.maxHeight ~= nil then |
777 | local height = -math.huge |
778 | if node.fillVolumeHeightIndex ~= nil and spec.heightNodes[node.fillVolumeHeightIndex] ~= nil then |
779 | for _,refNode in pairs(spec.heightNodes[node.fillVolumeHeightIndex].refNodes) do |
780 | local x,_,z = localToLocal(refNode.refNode, baseNode, 0,0,0) |
781 | height = math.max(height, getFillPlaneHeightAtLocalPos(volumeNode, x, z) - volume.heightOffset) |
782 | end |
783 | else |
784 | local x,_,z = localToLocal(node.node, baseNode, 0,0,0) |
785 | height = math.max(height, getFillPlaneHeightAtLocalPos(volumeNode, x, z) - volume.heightOffset) |
786 | end |
787 | |
788 | if node.minHeight ~= nil and height < node.minHeight then |
789 | doInsert = false |
790 | end |
791 | if node.maxHeight ~= nil and height > node.maxHeight then |
792 | doInsert = false |
793 | end |
794 | |
795 | if node.heightForTranslation ~= nil then |
796 | if height > node.heightForTranslation then |
797 | node.translationAlpha = node.translationAlpha + 0.01 |
798 | local x,y,z = MathUtil.vector3ArrayLerp(node.translationStart, node.translationEnd, node.translationAlpha) |
799 | setTranslation(node.node, x,y,z) |
800 | else |
801 | node.translationAlpha = node.translationAlpha - 0.01 |
802 | end |
803 | node.translationAlpha = MathUtil.clamp(node.translationAlpha, 0, 1) |
804 | end |
805 | end |
806 | |
807 | if node.minFillLevelPercentage ~= nil or node.maxFillLevelPercentage ~= nil then |
808 | local percentage = fillLevel / self:getFillUnitCapacity(fillUnitIndex) |
809 | |
810 | if node.minFillLevelPercentage ~= nil and percentage < node.minFillLevelPercentage then |
811 | doInsert = false |
812 | end |
813 | if node.maxFillLevelPercentage ~= nil and percentage > node.maxFillLevelPercentage then |
814 | doInsert = false |
815 | end |
816 | end |
817 | |
818 | if doInsert then |
819 | table.insert(spec.availableFillNodes, node) |
820 | end |
821 | end |
822 | end |
823 | if #spec.availableFillNodes > 0 then |
824 | break |
825 | end |
826 | neededPriority = neededPriority - 1 |
827 | end |
828 | else |
829 | table.insert(spec.availableFillNodes, fillPositionData) |
830 | end |
831 | |
832 | local numFillNodes = #spec.availableFillNodes |
833 | local avgX, avgZ = 0, 0 |
834 | |
835 | for i=1,numFillNodes do |
836 | local node = spec.availableFillNodes[i] |
837 | |
838 | local x0,y0,z0 = getWorldTranslation(node.node) |
839 | local d1x,d1y,d1z = localDirectionToWorld(node.node, node.width,0,0) |
840 | local d2x,d2y,d2z = localDirectionToWorld(node.node, 0,0,node.length) |
841 | |
842 | if VehicleDebug.state == VehicleDebug.DEBUG then |
843 | drawDebugLine( x0,y0,z0, 1,0,0, x0+d1x, y0+d1y, z0+d1z, 1,0,0 ) |
844 | drawDebugLine( x0,y0,z0, 0,0,1, x0+d2x, y0+d2y, z0+d2z, 0,0,1 ) |
845 | drawDebugPoint( x0,y0,z0, 1,1,1,1 ) |
846 | drawDebugPoint( x0+d1x, y0+d1y, z0+d1z, 1,0,0,1 ) |
847 | drawDebugPoint( x0+d2x, y0+d2y, z0+d2z, 0,0,1,1 ) |
848 | end |
849 | x0 = x0 - (d1x + d2x) / 2 |
850 | y0 = y0 - (d1y + d2y) / 2 |
851 | z0 = z0 - (d1z + d2z) / 2 |
852 | fillPlaneAdd(volume.volume, appliedDelta/numFillNodes, x0,y0,z0, d1x,d1y,d1z, d2x,d2y,d2z) |
853 | |
854 | local newX, _, newZ = localToLocal(node.node, volume.volume, 0, 0, 0) |
855 | avgX, avgZ = avgX+newX, avgZ+newZ |
856 | end |
857 | |
858 | local newX = avgX / numFillNodes |
859 | local newZ = avgZ / numFillNodes |
860 | if math.abs(newX-spec.lastPositionInfoSent[1]) > FillVolume.SEND_PRECISION or math.abs(newZ-spec.lastPositionInfoSent[2]) > FillVolume.SEND_PRECISION then |
861 | spec.lastPositionInfoSent[1] = newX |
862 | spec.lastPositionInfoSent[2] = newZ |
863 | |
864 | self:raiseDirtyFlags(spec.dirtyFlag) |
865 | end |
866 | else |
867 | -- increase size of fill info if there should not be a heap since a small fill info will still produce a heap |
868 | local loadSize = 0.1 |
869 | if volume.maxPhysicalSurfaceAngle == 0 or volume.maxSurfaceAngle == 0 then |
870 | loadSize = 10 |
871 | end |
872 | |
873 | local x,y,z = localToWorld(volume.volume, -loadSize * 0.5, 0, -loadSize * 0.5) |
874 | local d1x,d1y,d1z = localDirectionToWorld(volume.volume, loadSize, 0, 0) |
875 | local d2x,d2y,d2z = localDirectionToWorld(volume.volume, 0, 0, loadSize) |
876 | |
877 | if not self.isServer then |
878 | if spec.lastPositionInfo[1] ~= 0 and spec.lastPositionInfo[2] ~= 0 then |
879 | x, y, z = localToWorld(volume.volume, spec.lastPositionInfo[1], 0, spec.lastPositionInfo[2]) |
880 | end |
881 | end |
882 | |
883 | local steps = MathUtil.clamp(math.floor(appliedDelta/400), 1, 25) |
884 | for _=1, steps do |
885 | fillPlaneAdd(volume.volume, appliedDelta/steps, x,y,z, d1x,d1y,d1z, d2x,d2y,d2z) |
886 | end |
887 | end |
888 | |
889 | local heightNodes = spec.fillVolumeIndexToHeightNode[volume.index] |
890 | if heightNodes ~= nil then |
891 | for _, heightNode in ipairs(heightNodes) do |
892 | heightNode.isDirty = true |
893 | end |
894 | end |
895 | |
896 | for _,deformer in pairs(volume.deformers) do |
897 | deformer.isDirty = true |
898 | end |
899 | |
900 | volume.lastFillType = fillType |
901 | end |
902 | end |
128 | function FillVolume:onLoad(savegame) |
129 | local spec = self.spec_fillVolume |
130 | |
131 | local fillVolumeConfigurationId = Utils.getNoNil(self.configurations["fillVolume"], 1) |
132 | local configKey = string.format("vehicle.fillVolume.fillVolumeConfigurations.fillVolumeConfiguration(%d).volumes", fillVolumeConfigurationId -1) |
133 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.fillVolume.fillVolumeConfigurations.fillVolumeConfiguration", fillVolumeConfigurationId , self.components, self) |
134 | |
135 | spec.volumes = {} |
136 | spec.fillVolumeDeformersByNode = {} |
137 | spec.fillUnitFillVolumeMapping = {} |
138 | |
139 | self.xmlFile:iterate(configKey .. ".volume", function(_, key) |
140 | local entry = {} |
141 | if self:loadFillVolume(self.xmlFile, key, entry) then |
142 | table.insert(spec.volumes, entry) |
143 | entry.index = #spec.volumes |
144 | end |
145 | end) |
146 | |
147 | -- create fill units |
148 | for _, mapping in ipairs(spec.fillUnitFillVolumeMapping) do |
149 | for _, fillVolume in ipairs(mapping.fillVolumes) do |
150 | fillVolume.fillUnitFactor = fillVolume.fillUnitFactor / mapping.sumFactors |
151 | end |
152 | end |
153 | |
154 | for _, fillVolume in ipairs(spec.volumes) do |
155 | local capacity = self:getFillUnitCapacity(fillVolume.fillUnitIndex) |
156 | local fillVolumeCapacity = capacity * fillVolume.fillUnitFactor |
157 | fillVolume.volume = createFillPlaneShape(fillVolume.baseNode, "fillPlane", fillVolumeCapacity, fillVolume.maxDelta, fillVolume.maxSurfaceAngle, fillVolume.maxPhysicalSurfaceAngle, fillVolume.maxSurfaceDistanceError, fillVolume.maxSubDivEdgeLength, fillVolume.syncMaxSubDivEdgeLength, fillVolume.allSidePlanes, fillVolume.retessellateTop) |
158 | if fillVolume.volume == nil or fillVolume.volume == 0 then |
159 | print("Warning: fillVolume '"..tostring(getName(fillVolume.baseNode)).."' could not create actual fillVolume in '"..self.configFileName.."'! Simplifying the mesh could help") |
160 | fillVolume.volume = nil |
161 | else |
162 | setVisibility(fillVolume.volume, false) |
163 | |
164 | for i=#fillVolume.deformers, 1, -1 do |
165 | local deformer = fillVolume.deformers[i] |
166 | deformer.polyline = findPolyline(fillVolume.volume, deformer.posX, deformer.posZ) |
167 | if deformer.polyline == nil and deformer.polyline ~= -1 then |
168 | print("Warning: Could not find 'polyline' for '"..tostring(getName(deformer.node)).."' in '"..self.configFileName.."'") |
169 | table.remove(fillVolume.deformers, i) |
170 | end |
171 | end |
172 | |
173 | link(fillVolume.baseNode, fillVolume.volume) |
174 | |
175 | local fillVolumeMaterial = g_materialManager:getBaseMaterialByName("fillPlane") |
176 | if fillVolumeMaterial ~= nil then |
177 | setMaterial(fillVolume.volume, fillVolumeMaterial, 0) |
178 | g_fillTypeManager:assignFillTypeTextureArrays(fillVolume.volume, true, true, true) |
179 | else |
180 | Logging.error("Failed to assign material to fill volume. Base Material 'fillPlane' not found!") |
181 | end |
182 | |
183 | -- check offset between pivot and bottom plane of fill volume |
184 | fillPlaneAdd(fillVolume.volume, 1, 0, 1, 0, 11, 0, 0, 0, 0, 11) |
185 | fillVolume.heightOffset = getFillPlaneHeightAtLocalPos(fillVolume.volume, 0, 0) |
186 | fillPlaneAdd(fillVolume.volume, -1, 0, 1, 0, 11, 0, 0, 0, 0, 11) |
187 | end |
188 | end |
189 | |
190 | spec.loadInfos = {} |
191 | self.xmlFile:iterate("vehicle.fillVolume.loadInfos.loadInfo", function(_, key) |
192 | local entry = {} |
193 | if self:loadFillVolumeInfo(self.xmlFile, key, entry) then |
194 | table.insert(spec.loadInfos, entry) |
195 | end |
196 | end) |
197 | |
198 | spec.unloadInfos = {} |
199 | self.xmlFile:iterate("vehicle.fillVolume.unloadInfos.unloadInfo", function(_, key) |
200 | local entry = {} |
201 | if self:loadFillVolumeInfo(self.xmlFile, key, entry) then |
202 | table.insert(spec.unloadInfos, entry) |
203 | end |
204 | end) |
205 | |
206 | spec.heightNodes = {} |
207 | spec.fillVolumeIndexToHeightNode = {} |
208 | self.xmlFile:iterate("vehicle.fillVolume.heightNodes.heightNode", function(_, key) |
209 | local entry = {} |
210 | if self:loadFillVolumeHeightNode(self.xmlFile, key, entry) then |
211 | table.insert(spec.heightNodes, entry) |
212 | |
213 | if spec.fillVolumeIndexToHeightNode[entry.fillVolumeIndex] == nil then |
214 | spec.fillVolumeIndexToHeightNode[entry.fillVolumeIndex] = {} |
215 | end |
216 | table.insert(spec.fillVolumeIndexToHeightNode[entry.fillVolumeIndex], entry) |
217 | end |
218 | end) |
219 | |
220 | spec.lastPositionInfo = {0, 0} |
221 | spec.lastPositionInfoSent = {0, 0} |
222 | spec.availableFillNodes = {} |
223 | spec.dirtyFlag = self:getNextDirtyFlag() |
224 | |
225 | if not self.isClient or (#spec.volumes == 0 and #spec.heightNodes == 0) then |
226 | SpecializationUtil.removeEventListener(self, "onUpdate", FillVolume) |
227 | end |
228 | end |
281 | function FillVolume:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
282 | if self.isClient then |
283 | local spec = self.spec_fillVolume |
284 | |
285 | -- deform (fill)volume |
286 | for _,volume in pairs(spec.volumes) do |
287 | for _,deformer in ipairs(volume.deformers) do |
288 | if deformer.isDirty and deformer.polyline ~= nil and deformer.polyline ~= -1 then |
289 | deformer.isDirty = false |
290 | |
291 | local posX, posY, posZ = localToLocal(deformer.node, deformer.baseNode, 0,0,0) |
292 | if math.abs(posX - deformer.posX) > 0.0001 or math.abs(posZ - deformer.posZ) > 0.0001 then |
293 | --#debug local x, y, z = localToWorld(deformer.baseNode, posX, posY, posZ) |
294 | --#debug drawDebugLine(x, y, z, 0, 1, 0, x, y + 4, z, 0, 1, 0, true) |
295 | |
296 | deformer.lastPosX = posX |
297 | deformer.lastPosZ = posZ |
298 | local dx = posX - deformer.initPos[1] |
299 | local dz = posZ - deformer.initPos[3] |
300 | setPolylineTranslation(volume.volume, deformer.polyline, dx,dz) |
301 | end |
302 | end |
303 | end |
304 | |
305 | local uvScrollSpeedX, uvScrollSpeedY, uvScrollSpeedZ = self:getFillVolumeUVScrollSpeed(volume.index) |
306 | if uvScrollSpeedX ~= 0 or uvScrollSpeedY ~= 0 or uvScrollSpeedZ ~= 0 then |
307 | volume.uvPosition[1] = volume.uvPosition[1] + uvScrollSpeedX * (dt/1000) |
308 | volume.uvPosition[2] = volume.uvPosition[2] + uvScrollSpeedY * (dt/1000) |
309 | volume.uvPosition[3] = volume.uvPosition[3] + uvScrollSpeedZ * (dt/1000) |
310 | setShaderParameter(volume.volume, "uvOffset", volume.uvPosition[1], volume.uvPosition[2], volume.uvPosition[3], 0, false) |
311 | end |
312 | end |
313 | |
314 | -- update heightNodes |
315 | for _,heightNode in pairs(spec.heightNodes) do |
316 | if heightNode.isDirty then |
317 | heightNode.isDirty = false |
318 | |
319 | local fillVolume = spec.volumes[heightNode.fillVolumeIndex] |
320 | |
321 | local baseNode = fillVolume.baseNode |
322 | local volumeNode = fillVolume.volume |
323 | |
324 | if baseNode ~= nil and volumeNode ~= nil then |
325 | |
326 | local minHeight = math.huge |
327 | local maxHeight = -math.huge |
328 | local maxHeightWorld = -math.huge |
329 | for _, refNode in pairs(heightNode.refNodes) do |
330 | local x, _, z = localToLocal(refNode.refNode, baseNode, 0, 0, 0) |
331 | local height = getFillPlaneHeightAtLocalPos(volumeNode, x, z) - fillVolume.heightOffset |
332 | minHeight = math.min(minHeight, height) |
333 | maxHeight = math.max(maxHeight, height) |
334 | local _, yw, _ = localToWorld(baseNode, x, height, z) |
335 | maxHeightWorld = math.max(maxHeightWorld, yw) |
336 | end |
337 | |
338 | heightNode.currentMinHeight = minHeight |
339 | heightNode.currentMaxHeight = maxHeight |
340 | heightNode.currentMaxHeightWorld = maxHeightWorld |
341 | |
342 | for _,node in pairs(heightNode.nodes) do |
343 | local nodeHeight = math.max(minHeight + node.heightOffset, node.minHeight) |
344 | |
345 | local sx = node.scaleAxis[1] * nodeHeight |
346 | local sy = node.scaleAxis[2] * nodeHeight |
347 | local sz = node.scaleAxis[3] * nodeHeight |
348 | if node.scaleMax[1] > 0 then |
349 | sx = math.min(node.scaleMax[1], sx) |
350 | end |
351 | if node.scaleMax[2] > 0 then |
352 | sy = math.min(node.scaleMax[2], sy) |
353 | end |
354 | if node.scaleMax[3] > 0 then |
355 | sz = math.min(node.scaleMax[3], sz) |
356 | end |
357 | local tx = node.transAxis[1] * nodeHeight |
358 | local ty = node.transAxis[2] * nodeHeight |
359 | local tz = node.transAxis[3] * nodeHeight |
360 | if node.transMax[1] > 0 then |
361 | tx = math.min(node.transMax[1], tx) |
362 | end |
363 | if node.transMax[2] > 0 then |
364 | ty = math.min(node.transMax[2], ty) |
365 | end |
366 | if node.transMax[3] > 0 then |
367 | tz = math.min(node.transMax[3], tz) |
368 | end |
369 | |
370 | setScale(node.node, node.baseScale[1]+sx, node.baseScale[2]+sy, node.baseScale[3]+sz) |
371 | setTranslation(node.node, node.basePosition[1]+tx, node.basePosition[2]+ty, node.basePosition[3]+tz) |
372 | |
373 | if node.orientateToWorldY then |
374 | local _,dy,_ = localDirectionToWorld(getParent(node.node), 0,1,0) |
375 | local alpha = math.acos(dy) |
376 | setRotation(node.node, alpha,0,0) |
377 | end |
378 | end |
379 | end |
380 | end |
381 | end |
382 | end |
383 | end |