444 | function PushHandTool:customVehicleCharacterLoaded(success, arguments) |
445 | local enterableSpec = self.spec_enterable |
446 | if success then |
447 | local character = enterableSpec.vehicleCharacter |
448 | if character ~= nil then |
449 | character:updateVisibility() |
450 | -- do not initial update the IK chains, so the character keeps the T pose while setting up the ik solver below |
451 | end |
452 | |
453 | SpecializationUtil.raiseEvent(self, "onVehicleCharacterChanged", character) |
454 | |
455 | local spec = self.spec_pushHandTool |
456 | |
457 | spec.characterIKNodes = {} |
458 | |
459 | for name, ikChain in pairs(character.playerModel.ikChains) do |
460 | if spec.ikChainTargets[name] ~= nil then |
461 | for k, nodeData in pairs(ikChain.nodes) do |
462 | if spec.characterIKNodes[nodeData.node] == nil then |
463 | local duplicate = createTransformGroup(getName(nodeData.node) .. "_ikChain") |
464 | |
465 | local parent = getParent(nodeData.node) |
466 | if spec.characterIKNodes[parent] ~= nil then |
467 | parent = spec.characterIKNodes[parent] |
468 | end |
469 | |
470 | link(parent, duplicate) |
471 | setTranslation(duplicate, getTranslation(nodeData.node)) |
472 | setRotation(duplicate, getRotation(nodeData.node)) |
473 | |
474 | spec.characterIKNodes[nodeData.node] = duplicate |
475 | end |
476 | end |
477 | end |
478 | |
479 | for k, nodeData in pairs(ikChain.nodes) do |
480 | if spec.characterIKNodes[nodeData.node] ~= nil then |
481 | nodeData.node = spec.characterIKNodes[nodeData.node] |
482 | end |
483 | end |
484 | end |
485 | |
486 | character.ikChainTargets = spec.ikChainTargets |
487 | for ikChainId, target in pairs(spec.ikChainTargets) do |
488 | IKUtil.setTarget(character.playerModel.ikChains, ikChainId, target) |
489 | end |
490 | |
491 | spec.ikChains = character.playerModel.ikChains |
492 | |
493 | for name, ikChain in pairs(character.playerModel.ikChains) do |
494 | ikChain.ikChainSolver = IKChain.new(#ikChain.nodes) |
495 | for i, node in ipairs(ikChain.nodes) do |
496 | local minRx, maxRx, minRy, maxRy, minRz, maxRz, damping, localLimits = node.minRx, node.maxRx, node.minRy, node.maxRy, node.minRz, node.maxRz, node.damping, node.localLimits |
497 | |
498 | for j=1, #spec.customChainLimits do |
499 | local customLimit = spec.customChainLimits[j] |
500 | if customLimit.chainId == name and customLimit.nodeIndex == i then |
501 | minRx = customLimit.minRx or minRx |
502 | maxRx = customLimit.maxRx or maxRx |
503 | minRy = customLimit.minRy or minRy |
504 | maxRy = customLimit.maxRy or maxRy |
505 | minRz = customLimit.minRz or minRz |
506 | maxRz = customLimit.maxRz or maxRz |
507 | damping = customLimit.damping or damping |
508 | |
509 | if customLimit.localLimits ~= nil then |
510 | localLimits = customLimit.localLimits |
511 | end |
512 | end |
513 | end |
514 | |
515 | ikChain.ikChainSolver:setJointTransformGroup(i-1, node.node, minRx, maxRx, minRy, maxRy, minRz, maxRz, damping, localLimits) |
516 | end |
517 | |
518 | ikChain.numIterations = 40 |
519 | ikChain.positionThreshold = 0.0001 |
520 | end |
521 | |
522 | character:setDirty() |
523 | |
524 | if character ~= nil and character.animationCharsetId ~= nil and character.animationPlayer ~= nil then |
525 | for key, parameter in pairs(spec.animationParameters) do |
526 | conditionalAnimationRegisterParameter(character.animationPlayer, parameter.id, parameter.type, key) |
527 | end |
528 | initConditionalAnimation(character.animationPlayer, character.animationCharsetId, self.configFileName, "vehicle.pushHandTool.playerConditionalAnimation") |
529 | |
530 | conditionalAnimationZeroiseTrackTimes(character.animationPlayer) |
531 | end |
532 | |
533 | IKUtil.updateAlignNodes(character.playerModel.ikChains, self.getParentComponent, self) |
534 | end |
535 | end |
19 | function PushHandTool.initSpecialization() |
20 | local schema = Vehicle.xmlSchema |
21 | schema:setXMLSpecializationType("PushHandTool") |
22 | |
23 | schema:register(XMLValueType.NODE_INDEX, "vehicle.pushHandTool.raycast#node1", "Front raycast node") |
24 | schema:register(XMLValueType.NODE_INDEX, "vehicle.pushHandTool.raycast#node2", "Back raycast node") |
25 | schema:register(XMLValueType.NODE_INDEX, "vehicle.pushHandTool.raycast#playerNode", "Player node to adjust") |
26 | |
27 | schema:register(XMLValueType.VECTOR_N, "vehicle.pushHandTool.wheels#front", "Indices of front wheels") |
28 | schema:register(XMLValueType.VECTOR_N, "vehicle.pushHandTool.wheels#back", "Indices of back wheels") |
29 | |
30 | schema:register(XMLValueType.NODE_INDEX, "vehicle.pushHandTool.handle#node", "Handle node") |
31 | schema:register(XMLValueType.FLOAT, "vehicle.pushHandTool.handle#upperLimit", "Max. upper distance between handle node and hand ik root node", 0.4) |
32 | schema:register(XMLValueType.FLOAT, "vehicle.pushHandTool.handle#lowerLimit", "Max. lower distance between handle node and hand ik root node", 0.4) |
33 | schema:register(XMLValueType.FLOAT, "vehicle.pushHandTool.handle#interpolateDistance", "Interpolation distance if limit is exceeded", 0.4) |
34 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.handle#minRot", "Min. rotation of handle", -20) |
35 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.handle#maxRot", "Max. rotation of handle", 20) |
36 | |
37 | IKUtil.registerIKChainTargetsXMLPaths(schema, "vehicle.pushHandTool.ikChains") |
38 | EffectManager.registerEffectXMLPaths(schema, "vehicle.pushHandTool.effect") |
39 | |
40 | schema:register(XMLValueType.STRING, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#chainId", "Chain identifier string", 20) |
41 | schema:register(XMLValueType.INT, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#nodeIndex", "Index of node") |
42 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#minRx", "Min. X rotation") |
43 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#maxRx", "Max. X rotation") |
44 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#minRy", "Min. Y rotation") |
45 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#maxRy", "Max. Y rotation") |
46 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#minRz", "Min. Z rotation") |
47 | schema:register(XMLValueType.ANGLE, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#maxRz", "Max. Z rotation") |
48 | schema:register(XMLValueType.FLOAT, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#damping", "Damping") |
49 | schema:register(XMLValueType.BOOL, "vehicle.pushHandTool.customChainLimits.customChainLimit(?)#localLimits", "Local limits") |
50 | |
51 | -- values loaded only by engine, registration just for documentation/validation purposes |
52 | local registerConditionalAnimation = function(xmlKey) |
53 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?)#id", "") |
54 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?)#entryTransitionDuration", "") |
55 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?)#exitTransitionDuration", "") |
56 | |
57 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).clips#speedScaleType", "") |
58 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?).clips#speedScaleParameter", "") |
59 | schema:register(XMLValueType.BOOL, xmlKey .. ".item(?).clips#blended", "") |
60 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).clips#blendingParameter", "") |
61 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).clips#blendingParameterType", "") |
62 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).clips.clip(?)#clipName", "") |
63 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).clips.clip(?)#id", "") |
64 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?).clips.clip(?)#blendingThreshold", "") |
65 | |
66 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).conditions.conditionGroup(?).condition(?)#parameter", "") |
67 | schema:register(XMLValueType.BOOL, xmlKey .. ".item(?).conditions.conditionGroup(?).condition(?)#equal", "") |
68 | schema:register(XMLValueType.STRING, xmlKey .. ".item(?).conditions.conditionGroup(?).condition(?)#between", "") |
69 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?).conditions.conditionGroup(?).condition(?)#greater", "") |
70 | schema:register(XMLValueType.FLOAT, xmlKey .. ".item(?).conditions.conditionGroup(?).condition(?)#lower", "") |
71 | end |
72 | |
73 | registerConditionalAnimation("vehicle.pushHandTool.playerConditionalAnimation") |
74 | |
75 | schema:setXMLSpecializationType() |
76 | end |
119 | function PushHandTool:onLoad(savegame) |
120 | local spec = self.spec_pushHandTool |
121 | |
122 | spec.animationParameters = {} |
123 | spec.animationParameters.absSmoothedForwardVelocity = {id=1, value=0.0, type=1} |
124 | spec.animationParameters.smoothedForwardVelocity = {id=2, value=0.0, type=1} |
125 | spec.animationParameters.accelerate = {id=3, value=false, type=0} |
126 | spec.animationParameters.leftRightWeight = {id=4, value=0.0, type=1} |
127 | |
128 | |
129 | spec.raycastNode1 = self.xmlFile:getValue("vehicle.pushHandTool.raycast#node1", nil, self.components, self.i3dMappings) |
130 | spec.raycastNode2 = self.xmlFile:getValue("vehicle.pushHandTool.raycast#node2", nil, self.components, self.i3dMappings) |
131 | spec.playerNode = self.xmlFile:getValue("vehicle.pushHandTool.raycast#playerNode", nil, self.components, self.i3dMappings) |
132 | spec.playerTargetNode = createTransformGroup("playerTargetNode") |
133 | if spec.playerNode ~= nil then |
134 | link(getParent(spec.playerNode), spec.playerTargetNode) |
135 | setTranslation(spec.playerTargetNode, getTranslation(spec.playerNode)) |
136 | end |
137 | |
138 | local frontWheels = self.xmlFile:getValue("vehicle.pushHandTool.wheels#front", nil, true) |
139 | spec.frontWheels = {} |
140 | for i=1, #frontWheels do |
141 | local wheel = self:getWheelFromWheelIndex(frontWheels[i]) |
142 | if wheel ~= nil then |
143 | table.insert(spec.frontWheels, wheel) |
144 | end |
145 | end |
146 | |
147 | local backWheels = self.xmlFile:getValue("vehicle.pushHandTool.wheels#back", nil, true) |
148 | spec.backWheels = {} |
149 | for i=1, #backWheels do |
150 | local wheel = self:getWheelFromWheelIndex(backWheels[i]) |
151 | if wheel ~= nil then |
152 | table.insert(spec.backWheels, wheel) |
153 | end |
154 | end |
155 | |
156 | spec.handle = {} |
157 | spec.handle.node = self.xmlFile:getValue("vehicle.pushHandTool.handle#node", nil, self.components, self.i3dMappings) |
158 | spec.handle.upperLimit = self.xmlFile:getValue("vehicle.pushHandTool.handle#upperLimit", 0.4) |
159 | spec.handle.lowerLimit = self.xmlFile:getValue("vehicle.pushHandTool.handle#lowerLimit", 0.4) |
160 | spec.handle.interpolateDistance = self.xmlFile:getValue("vehicle.pushHandTool.handle#interpolateDistance", 0.4) |
161 | spec.handle.minRot = self.xmlFile:getValue("vehicle.pushHandTool.handle#minRot", -20) |
162 | spec.handle.maxRot = self.xmlFile:getValue("vehicle.pushHandTool.handle#maxRot", 20) |
163 | |
164 | spec.characterIKNodes = {} |
165 | spec.ikChainTargets = {} |
166 | IKUtil.loadIKChainTargets(self.xmlFile, "vehicle.pushHandTool.ikChains", self.components, spec.ikChainTargets, self.i3dMappings) |
167 | |
168 | spec.lastRaycastPosition = {0, 0, 0, 0} |
169 | spec.lastRaycastHit = false |
170 | |
171 | if self.isClient then |
172 | spec.cutterEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.pushHandTool.effect", self.components, self, self.i3dMappings) |
173 | end |
174 | |
175 | spec.customChainLimits = {} |
176 | self.xmlFile:iterate("vehicle.pushHandTool.customChainLimits.customChainLimit", function(index, key) |
177 | local entry = {} |
178 | entry.chainId = self.xmlFile:getValue(key .. "#chainId") |
179 | entry.nodeIndex = self.xmlFile:getValue(key .. "#nodeIndex") |
180 | |
181 | if entry.chainId ~= nil and entry.nodeIndex ~= nil then |
182 | entry.minRx = self.xmlFile:getValue(key .. "#minRx") |
183 | entry.maxRx = self.xmlFile:getValue(key .. "#maxRx") |
184 | entry.minRy = self.xmlFile:getValue(key .. "#minRy") |
185 | entry.maxRy = self.xmlFile:getValue(key .. "#maxRy") |
186 | entry.minRz = self.xmlFile:getValue(key .. "#minRz") |
187 | entry.maxRz = self.xmlFile:getValue(key .. "#maxRz") |
188 | entry.damping = self.xmlFile:getValue(key .. "#damping") |
189 | entry.localLimits = self.xmlFile:getValue(key .. "#localLimits") |
190 | |
191 | table.insert(spec.customChainLimits, entry) |
192 | end |
193 | end) |
194 | |
195 | spec.effectDirtyFlag = self:getNextDirtyFlag() |
196 | |
197 | spec.raycastsValid = true |
198 | spec.lastSmoothSpeed = 0 |
199 | |
200 | self:setTestAreaRequirements(FruitType.GRASS, nil, false) |
201 | |
202 | spec.postAnimationCallback = addPostAnimationCallback(self.postAnimationUpdate, self) |
203 | end |
219 | function PushHandTool:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
220 | local spec = self.spec_pushHandTool |
221 | |
222 | if self:getIsTurnedOn() and self:getLastSpeed() > 0.5 then |
223 | local currentTestAreaMinX, currentTestAreaMaxX, testAreaMinX, testAreaMaxX = self:getTestAreaWidthByWorkAreaIndex(1) |
224 | |
225 | local reset = false |
226 | if currentTestAreaMinX == math.huge and currentTestAreaMaxX == -math.huge then |
227 | currentTestAreaMinX = 0 |
228 | currentTestAreaMaxX = 0 |
229 | reset = true |
230 | end |
231 | |
232 | if spec.movingDirection > 0 then |
233 | currentTestAreaMinX = currentTestAreaMinX * -1 |
234 | currentTestAreaMaxX = currentTestAreaMaxX * -1 |
235 | if currentTestAreaMaxX < currentTestAreaMinX then |
236 | local t = currentTestAreaMinX |
237 | currentTestAreaMinX = currentTestAreaMaxX |
238 | currentTestAreaMaxX = t |
239 | end |
240 | end |
241 | |
242 | if self.isClient then |
243 | local inputFruitType, inputGrowthState = FruitType.UNKNOWN, 3 |
244 | if self.spec_mower ~= nil then |
245 | local specMower = self.spec_mower |
246 | if g_time - specMower.workAreaParameters.lastCutTime < 500 then |
247 | inputFruitType = specMower.workAreaParameters.lastInputFruitType |
248 | inputGrowthState = specMower.workAreaParameters.lastInputGrowthState |
249 | end |
250 | end |
251 | |
252 | if inputFruitType ~= nil and inputFruitType ~= FruitType.UNKNOWN then |
253 | g_effectManager:setFruitType(spec.cutterEffects, inputFruitType, inputGrowthState) |
254 | g_effectManager:setMinMaxWidth(spec.cutterEffects, currentTestAreaMinX, currentTestAreaMaxX, currentTestAreaMinX / testAreaMinX, currentTestAreaMaxX / testAreaMaxX, reset) |
255 | g_effectManager:startEffects(spec.cutterEffects) |
256 | spec.effectsAreRunning = not reset |
257 | else |
258 | if spec.effectsAreRunning then |
259 | g_effectManager:stopEffects(spec.cutterEffects) |
260 | spec.effectsAreRunning = false |
261 | end |
262 | end |
263 | end |
264 | else |
265 | if spec.effectsAreRunning then |
266 | g_effectManager:stopEffects(spec.cutterEffects) |
267 | spec.effectsAreRunning = false |
268 | end |
269 | end |
270 | |
271 | local lastSpeed = self.lastSignedSpeed * 1000.0 |
272 | |
273 | local avgSpeed = 0 |
274 | local numWheels = 0 |
275 | for _, wheel in pairs(spec.backWheels) do |
276 | if wheel.netInfo.xDriveSpeed ~= nil then |
277 | local wheelSpeed = MathUtil.rpmToMps(wheel.netInfo.xDriveSpeed / (2*math.pi) * 60, wheel.radius) * 1000 |
278 | avgSpeed = avgSpeed + wheelSpeed |
279 | numWheels = numWheels + 1 |
280 | end |
281 | end |
282 | |
283 | if numWheels > 0 then |
284 | lastSpeed = avgSpeed / numWheels |
285 | end |
286 | |
287 | spec.lastSmoothSpeed = spec.lastSmoothSpeed * 0.9 + lastSpeed * 0.1 |
288 | |
289 | spec.animationParameters.smoothedForwardVelocity.value = spec.lastSmoothSpeed |
290 | spec.animationParameters.absSmoothedForwardVelocity.value = math.abs(spec.lastSmoothSpeed) |
291 | spec.animationParameters.leftRightWeight.value = self.rotatedTime |
292 | |
293 | spec.animationParameters.accelerate.value = self:getAccelerationAxis() > 0 |
294 | |
295 | if self:getIsEntered() or self:getIsControlled() then |
296 | local character = self:getVehicleCharacter() |
297 | if character ~= nil and character.animationCharsetId ~= nil and character.animationPlayer ~= nil then |
298 | for _, parameter in pairs(spec.animationParameters) do |
299 | if parameter.type == 0 then |
300 | setConditionalAnimationBoolValue(character.animationPlayer, parameter.id, parameter.value) |
301 | elseif parameter.type == 1 then |
302 | setConditionalAnimationFloatValue(character.animationPlayer, parameter.id, parameter.value) |
303 | end |
304 | end |
305 | |
306 | setConditionalAnimationSpecificParameterIds(character.animationPlayer, spec.animationParameters.absSmoothedForwardVelocity.id, 0) |
307 | |
308 | updateConditionalAnimation(character.animationPlayer, dt) |
309 | |
310 | --local x,y,z = getWorldTranslation(self.rootNode) |
311 | --conditionalAnimationDebugDraw(character.animationPlayer, x,y,z) |
312 | end |
313 | end |
314 | |
315 | |
316 | if spec.raycastNode1 ~= nil and spec.raycastNode2 ~= nil and spec.playerNode ~= nil and #spec.frontWheels >= 1 and #spec.backWheels >= 1 then |
317 | local x1, y1, z1 = self:getRaycastPosition(spec.raycastNode1) |
318 | local x2, y2, z2 = self:getRaycastPosition(spec.raycastNode2) |
319 | --#debug drawDebugLine(x1, y1, z1, 0, 1, 0, x2, y2, z2, 0, 1, 0) |
320 | |
321 | if x1 ~= nil and x2 ~= nil then |
322 | local tx, ty, tz = (x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5 |
323 | setWorldTranslation(spec.playerTargetNode, tx, ty, tz) |
324 | |
325 | local dirX, dirY, dirZ = x1-x2, y1-y2, z1-z2 |
326 | dirX, dirY, dirZ = MathUtil.vector3Normalize(dirX, dirY, dirZ) |
327 | |
328 | -- smoothly blend Y direction when raycasts is hitting a small step |
329 | if spec.lastYDirection == nil then |
330 | spec.lastYDirection = dirY |
331 | else |
332 | dirY = spec.lastYDirection * 0.9 + dirY * 0.1 |
333 | spec.lastYDirection = dirY |
334 | end |
335 | |
336 | I3DUtil.setWorldDirection(spec.playerTargetNode, dirX, dirY, dirZ, 0, 1, 0) |
337 | |
338 | --#debug drawDebugLine(tx, ty, tz, 0, 1, 0, tx+dirX*4, ty+dirY*4, tz+dirZ*4, 0, 1, 0) |
339 | --#debug DebugUtil.drawDebugNode(spec.playerTargetNode, "", false) |
340 | |
341 | if spec.lastWorldTrans == nil then |
342 | spec.lastWorldTrans = {getWorldTranslation(spec.playerNode)} |
343 | end |
344 | local cx, cy, cz = spec.lastWorldTrans[1],spec.lastWorldTrans[2], spec.lastWorldTrans[3] |
345 | |
346 | local smoothFactor = 0.5 - math.abs(math.min(self.rotatedTime / 0.5), 1) * 0.15 |
347 | local moveX, moveY, moveZ = (cx - tx) * smoothFactor, (cy - ty) * smoothFactor, (cz - tz) * smoothFactor |
348 | |
349 | local newX, newY, newZ = cx - moveX, cy - moveY, cz - moveZ |
350 | setWorldTranslation(spec.playerNode, newX, newY, newZ) |
351 | |
352 | spec.lastWorldTrans[1], spec.lastWorldTrans[2], spec.lastWorldTrans[3] = newX, newY, newZ |
353 | |
354 | local direction = self.movingDirection |
355 | if direction == 0 then |
356 | direction = 1 |
357 | end |
358 | |
359 | -- use direction from last player point to the current target node |
360 | tx, ty, tz = localToWorld(spec.playerTargetNode, 0, 0, 0.2 * direction) |
361 | local dirY2, _ |
362 | dirX, dirY2, dirZ = tx-newX, ty-newY, tz-newZ |
363 | dirX, _, dirZ = MathUtil.vector3Normalize(dirX, dirY2, dirZ) |
364 | if direction < 0 then |
365 | dirX, dirZ = -dirX, -dirZ |
366 | end |
367 | |
368 | -- calculate direction of the tool based on the wheels |
369 | local fcx, fcy, fcz = 0, 0, 0 |
370 | local numFrontWheels = #spec.frontWheels |
371 | for i=1, numFrontWheels do |
372 | local wheel = spec.frontWheels[i] |
373 | |
374 | local wx, wy, wz = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z |
375 | wy = wy - wheel.radius |
376 | wx = wx + wheel.xOffset |
377 | wx, wy, wz = localToWorld(wheel.node, wx,wy,wz) |
378 | |
379 | fcx, fcy, fcz = fcx + wx, fcy + wy, fcz + wz |
380 | end |
381 | fcx, fcy, fcz = fcx / numFrontWheels, fcy / numFrontWheels, fcz / numFrontWheels |
382 | |
383 | local bcx, bcy, bcz = 0, 0, 0 |
384 | local numBackWheels = #spec.backWheels |
385 | for i=1, numBackWheels do |
386 | local wheel = spec.backWheels[i] |
387 | |
388 | local wx, wy, wz = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z |
389 | wy = wy - wheel.radius |
390 | wx = wx + wheel.xOffset |
391 | wx, wy, wz = localToWorld(wheel.node, wx,wy,wz) |
392 | |
393 | bcx, bcy, bcz = bcx + wx, bcy + wy, bcz + wz |
394 | end |
395 | bcx, bcy, bcz = bcx / numBackWheels, bcy / numBackWheels, bcz / numBackWheels |
396 | |
397 | local wDirX, wDirY, wDirZ = bcx - fcx, bcy - fcy, bcz - fcz |
398 | _, wDirY, _ = MathUtil.vector3Normalize(wDirX, wDirY, wDirZ) |
399 | |
400 | -- allow player offset of 8.5° to tool in Y |
401 | local dir = wDirY < 0 and 1 or -1 |
402 | dirY = wDirY + math.min(0.15, math.abs(wDirY)) * dir |
403 | |
404 | -- move the up vector towards the world y so we have some side adjustment |
405 | local upX, upY, upZ = localDirectionToWorld(self.rootNode, 0, 1, 0) |
406 | upY = upY + 0.5 |
407 | upX, upY, upZ = MathUtil.vector3Normalize(upX, upY, upZ) |
408 | |
409 | I3DUtil.setWorldDirection(spec.playerNode, dirX, dirY, dirZ, upX, upY, upZ) |
410 | |
411 | --#debug drawDebugLine(tx, ty, tz, 0, 0, 1, tx+dirX*4, ty+dirY*4, tz+dirZ*4, 0, 0, 1) |
412 | --#debug DebugUtil.drawDebugNode(spec.playerNode, "n", false) |
413 | |
414 | spec.raycastsValid = true |
415 | else |
416 | if spec.raycastsValid then |
417 | if self:getIsEntered() then |
418 | spec.raycastsValid = false |
419 | local character = self:getVehicleCharacter() |
420 | if character ~= nil then |
421 | character:setCharacterVisibility(false) |
422 | end |
423 | |
424 | -- force camera update -> will select the exterior one while raycast is invalid |
425 | self:setActiveCameraIndex(self.spec_enterable.camIndex) |
426 | end |
427 | end |
428 | end |
429 | end |
430 | end |