/** * @brief Slides a door * @note The new door state must already be set * @param[in,out] door The entity of the inline model. The aabb of this bmodel will get updated * in this function to reflect the new door position in the world * @sa LET_SlideDoor */ static void Door_SlidingUse (edict_t *door) { const bool open = door->doorState == STATE_OPENED; vec3_t moveAngles, moveDir, distanceVec; int distance; /* get the movement angle vector - a negative speed value will close the door*/ GET_SLIDING_DOOR_SHIFT_VECTOR(door->dir, open ? 1 : -1, moveAngles); /* get the direction vector from the movement angles that were set on the entity */ AngleVectors(moveAngles, moveDir, NULL, NULL); moveDir[0] = fabsf(moveDir[0]); moveDir[1] = fabsf(moveDir[1]); moveDir[2] = fabsf(moveDir[2]); /* calculate the distance from the movement angles and the entity size. This is the * distance the door has to slide to fully open or close */ distance = DotProduct(moveDir, door->size); /* the door is moved in one step on the server side - lerping is not needed here - so we * perform the scalar multiplication with the distance the door must move in order to * fully close/open */ VectorMul(distance, moveAngles, distanceVec); /* set the updated position. The bounding boxes that are used for tracing must be * shifted when the door state changes. As the mins and maxs of the aabb are absolute * world coordinates in the map we have to translate the position by the above * calculated movement vector */ VectorAdd(door->origin, distanceVec, door->origin); /* calc new model position */ // gi.SetInlineModelOrientation(door->model, door->origin, door->angles); /* move the model out of the way */ }
/** * @brief Grahm-Schmidt orthogonalization * @param[out] out Orthogonalized vector * @param[in] in Reference vector */ void Orthogonalize (vec3_t out, const vec3_t in) { vec3_t tmp; VectorMul(DotProduct(out, in), in, tmp); VectorSubtract(out, tmp, out); VectorNormalizeFast(out); }
int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s objfilename[s]\n", argv[0]); return -1; } XYZ minP, maxP; int i; for (i = 1; i < argc; i++) { OBJ_STRUCT *obj; // = loadObj(argv[argc -i], "null", 1, 1, 1, 1); char *ext = argv[argc-i] + strlen(argv[argc-i]) - 3; if (!strncmp(ext, "obj", 3)) { obj = loadObj(argv[argc-i], "null", 1,1,1,1); } else if (!strncmp(ext, "stl", 3)) { obj = loadObjFromSTL(argv[argc-i], "null", 1,1,1,1); } else { fprintf(stderr, "Cannot load file '%s'.\n", argv[argc-i]); exit(-1); } if (i == 1) { minP = obj->minP; maxP = obj->maxP; } else { if (obj->minP.x < minP.x) { minP.x = obj->minP.x; } if (obj->minP.y < minP.y) { minP.y = obj->minP.y; } if (obj->minP.z < minP.z) { minP.z = obj->minP.z; } if (obj->maxP.x > maxP.x) { maxP.x = obj->maxP.x; } if (obj->maxP.y > maxP.y) { maxP.y = obj->maxP.y; } if (obj->maxP.z > maxP.z) { maxP.z = obj->maxP.z; } } } // offset all model parts XYZ mid = VectorAdd(minP, maxP); mid = VectorMul(mid, 0.5); fprintf(stderr, "OBJ vertex range: (%f,%f,%f) -> (%f,%f,%f)\n", minP.x, minP.y, minP.z, maxP.x, maxP.y, maxP.z); fprintf(stderr, "OBJ vertex midpoint: (%f,%f,%f)\n", mid.x, mid.y, mid.z); return 0; }
/** * @brief Slides a door * * @note Though doors, sliding doors need a very different handling: * because it's movement is animated (unlike the rotating door), * the final position that is used to calculate the routing data * is set once the animation finished (because this recalculation * might be very expensive). * * @param[in,out] le The local entity of the inline model * @param[in] speed The speed to slide with - a negative value to close the door * @sa Door_SlidingUse */ void LET_SlideDoor (le_t* le, int speed) { vec3_t moveAngles, moveDir; /* get the movement angle vector */ GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, speed, moveAngles); /* this origin is only an offset to the absolute mins/maxs for rendering */ VectorAdd(le->origin, moveAngles, le->origin); /* get the direction vector from the movement angles that were set on the entity */ AngleVectors(moveAngles, moveDir, nullptr, nullptr); moveDir[0] = fabsf(moveDir[0]); moveDir[1] = fabsf(moveDir[1]); moveDir[2] = fabsf(moveDir[2]); /* calculate the distance from the movement angles and the entity size */ const int distance = DotProduct(moveDir, le->size); bool endPos = false; if (speed > 0) { /* check whether the distance the door may slide is slided already * - if so, stop the movement of the door */ if (fabs(le->origin[le->dir & 3]) >= distance) endPos = true; } else { /* the sliding door has not origin set - except when it is opened. This door type is no * origin brush based bmodel entity. So whenever the origin vector is not the zero vector, * the door is opened. */ if (VectorEmpty(le->origin)) endPos = true; } if (endPos) { vec3_t distanceVec; /* the door finished its move - either close or open, so make sure to recalc the routing * data and set the mins/maxs for the inline brush model */ cBspModel_t* model = CM_InlineModel(cl.mapTiles, le->inlineModelName); assert(model); /* we need the angles vector normalized */ GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, (speed < 0) ? -1 : 1, moveAngles); /* the bounding box of the door is updated in one step - here is no lerping needed */ VectorMul(distance, moveAngles, distanceVec); model->cbmBox.shift(distanceVec); CL_RecalcRouting(le); /* reset the think function as the movement finished */ LE_SetThink(le, nullptr); } else le->thinkDelay = 1000; }
void update() { // Redraw screen now, please, and call again in 15ms. glutPostRedisplay(); glutTimerFunc(15, updateI, 0); // Input is win32 only HWND window = GetActiveWindow(); if(window == GetForegroundWindow()) { POINT p; GetCursorPos(&p); ScreenToClient(window, &p); glutWarpPointer(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2); float angleX = (p.x - (WINDOW_WIDTH / 2)) * CAM_ROTSPEED; Quaternion rotX = RotationQuaternion(-angleX, MakeVector(0, 1, 0)); camera.front = TransformVector(RotationMatrixFromQuaternion(rotX), camera.front); camera.elevation += (p.y - (WINDOW_HEIGHT / 2)) * CAM_ROTSPEED; camera.elevation = max(-0.9f, min(camera.elevation, 0.9f)); if(GetAsyncKeyState('W') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(camera.front, CAM_MOVESPEED)); } if(GetAsyncKeyState('S') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(camera.front, -CAM_MOVESPEED)); } if(GetAsyncKeyState('E') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(camera.up, CAM_MOVESPEED)); } if(GetAsyncKeyState('Q') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(camera.up, -CAM_MOVESPEED)); } if(GetAsyncKeyState('D') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(VectorCross(camera.front, camera.up), CAM_MOVESPEED)); } if(GetAsyncKeyState('A') != 0) { camera.pos = VectorAdd(camera.pos, VectorMul(VectorCross(camera.front, camera.up), -CAM_MOVESPEED)); } } }
/** * @brief Calculates normals and tangents for all frames and does vertex merging based on smoothness * @param mesh The mesh to calculate normals for * @param nFrames How many frames the mesh has * @param smoothness How aggressively should normals be smoothed; value is compared with dotproduct of vectors to decide if they should be merged * @sa R_ModCalcNormalsAndTangents */ void R_ModCalcUniqueNormalsAndTangents (mAliasMesh_t *mesh, int nFrames, float smoothness) { int i, j; vec3_t triangleNormals[MAX_ALIAS_TRIS]; vec3_t triangleTangents[MAX_ALIAS_TRIS]; vec3_t triangleBitangents[MAX_ALIAS_TRIS]; const mAliasVertex_t *vertexes = mesh->vertexes; mAliasCoord_t *stcoords = mesh->stcoords; mAliasVertex_t *newVertexes; mAliasComplexVertex_t tmpVertexes[MAX_ALIAS_VERTS]; vec3_t tmpBitangents[MAX_ALIAS_VERTS]; mAliasCoord_t *newStcoords; const int numIndexes = mesh->num_tris * 3; const int32_t *indexArray = mesh->indexes; int32_t *newIndexArray; int indRemap[MAX_ALIAS_VERTS]; int sharedTris[MAX_ALIAS_VERTS]; int numVerts = 0; newIndexArray = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * numIndexes, vid_modelPool, 0); /* calculate per-triangle surface normals */ for (i = 0, j = 0; i < numIndexes; i += 3, j++) { vec3_t dir1, dir2; vec2_t dir1uv, dir2uv; /* calculate two mostly perpendicular edge directions */ VectorSubtract(vertexes[indexArray[i + 0]].point, vertexes[indexArray[i + 1]].point, dir1); VectorSubtract(vertexes[indexArray[i + 2]].point, vertexes[indexArray[i + 1]].point, dir2); Vector2Subtract(stcoords[indexArray[i + 0]], stcoords[indexArray[i + 1]], dir1uv); Vector2Subtract(stcoords[indexArray[i + 2]], stcoords[indexArray[i + 1]], dir2uv); /* we have two edge directions, we can calculate a third vector from * them, which is the direction of the surface normal */ CrossProduct(dir1, dir2, triangleNormals[j]); /* then we use the texture coordinates to calculate a tangent space */ if ((dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]) != 0.0) { const float frac = 1.0 / (dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } else { const float frac = 1.0 / (0.00001); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); } /* normalize */ VectorNormalizeFast(triangleNormals[j]); VectorNormalizeFast(triangleTangents[j]); VectorNormalizeFast(triangleBitangents[j]); Orthogonalize(triangleTangents[j], triangleBitangents[j]); } /* do smoothing */ for (i = 0; i < numIndexes; i++) { const int idx = (i - i % 3) / 3; VectorCopy(triangleNormals[idx], tmpVertexes[i].normal); VectorCopy(triangleTangents[idx], tmpVertexes[i].tangent); VectorCopy(triangleBitangents[idx], tmpBitangents[i]); for (j = 0; j < numIndexes; j++) { const int idx2 = (j - j % 3) / 3; /* don't add a vertex with itself */ if (j == i) continue; /* only average normals if vertices have the same position * and the normals aren't too far apart to start with */ if (VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && DotProduct(triangleNormals[idx], triangleNormals[idx2]) > smoothness) { /* average the normals */ VectorAdd(tmpVertexes[i].normal, triangleNormals[idx2], tmpVertexes[i].normal); /* if the tangents match as well, average them too. * Note that having matching normals without matching tangents happens * when the order of vertices in two triangles sharing the vertex * in question is different. This happens quite frequently if the * modeler does not go out of their way to avoid it. */ if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && DotProduct(triangleTangents[idx], triangleTangents[idx2]) > smoothness && DotProduct(triangleBitangents[idx], triangleBitangents[idx2]) > smoothness) { /* average the tangents */ VectorAdd(tmpVertexes[i].tangent, triangleTangents[idx2], tmpVertexes[i].tangent); VectorAdd(tmpBitangents[i], triangleBitangents[idx2], tmpBitangents[i]); } } } VectorNormalizeFast(tmpVertexes[i].normal); VectorNormalizeFast(tmpVertexes[i].tangent); VectorNormalizeFast(tmpBitangents[i]); } /* assume all vertices are unique until proven otherwise */ for (i = 0; i < numIndexes; i++) indRemap[i] = -1; /* merge vertices that have become identical */ for (i = 0; i < numIndexes; i++) { vec3_t n, b, t, v; if (indRemap[i] != -1) continue; for (j = i + 1; j < numIndexes; j++) { if (Vector2Equal(stcoords[indexArray[i]], stcoords[indexArray[j]]) && VectorEqual(vertexes[indexArray[i]].point, vertexes[indexArray[j]].point) && (DotProduct(tmpVertexes[i].normal, tmpVertexes[j].normal) > smoothness) && (DotProduct(tmpVertexes[i].tangent, tmpVertexes[j].tangent) > smoothness)) { indRemap[j] = i; newIndexArray[j] = numVerts; } } VectorCopy(tmpVertexes[i].normal, n); VectorCopy(tmpVertexes[i].tangent, t); VectorCopy(tmpBitangents[i], b); /* normalization here does shared-vertex smoothing */ VectorNormalizeFast(n); VectorNormalizeFast(t); VectorNormalizeFast(b); /* Grahm-Schmidt orthogonalization */ VectorMul(DotProduct(t, n), n, v); VectorSubtract(t, v, t); VectorNormalizeFast(t); /* calculate handedness */ CrossProduct(n, t, v); tmpVertexes[i].tangent[3] = (DotProduct(v, b) < 0.0) ? -1.0 : 1.0; VectorCopy(n, tmpVertexes[i].normal); VectorCopy(t, tmpVertexes[i].tangent); newIndexArray[i] = numVerts++; indRemap[i] = i; } for (i = 0; i < numVerts; i++) sharedTris[i] = 0; for (i = 0; i < numIndexes; i++) sharedTris[newIndexArray[i]]++; /* set up reverse-index that maps Vertex objects to a list of triangle verts */ mesh->revIndexes = (mIndexList_t *)Mem_PoolAlloc(sizeof(mIndexList_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numVerts; i++) { mesh->revIndexes[i].length = 0; mesh->revIndexes[i].list = (int32_t *)Mem_PoolAlloc(sizeof(int32_t) * sharedTris[i], vid_modelPool, 0); } /* merge identical vertexes, storing only unique ones */ newVertexes = (mAliasVertex_t *)Mem_PoolAlloc(sizeof(mAliasVertex_t) * numVerts * nFrames, vid_modelPool, 0); newStcoords = (mAliasCoord_t *)Mem_PoolAlloc(sizeof(mAliasCoord_t) * numVerts, vid_modelPool, 0); for (i = 0; i < numIndexes; i++) { const int idx = indexArray[indRemap[i]]; const int idx2 = newIndexArray[i]; /* add vertex to new vertex array */ VectorCopy(vertexes[idx].point, newVertexes[idx2].point); Vector2Copy(stcoords[idx], newStcoords[idx2]); mesh->revIndexes[idx2].list[mesh->revIndexes[idx2].length++] = i; } /* copy over the points from successive frames */ for (i = 1; i < nFrames; i++) { for (j = 0; j < numIndexes; j++) { const int idx = indexArray[indRemap[j]] + (mesh->num_verts * i); const int idx2 = newIndexArray[j] + (numVerts * i); VectorCopy(vertexes[idx].point, newVertexes[idx2].point); } } /* copy new arrays back into original mesh */ Mem_Free(mesh->stcoords); Mem_Free(mesh->indexes); Mem_Free(mesh->vertexes); mesh->num_verts = numVerts; mesh->vertexes = newVertexes; mesh->stcoords = newStcoords; mesh->indexes = newIndexArray; }
/** * @brief Calculates a per-vertex tangentspace basis and stores it in GL arrays attached to the mesh * @param mesh The mesh to calculate normals for * @param framenum The animation frame to calculate normals for * @param translate The frame translation for the given animation frame * @param backlerp Whether to store the results in the GL arrays for the previous keyframe or the next keyframe * @sa R_ModCalcUniqueNormalsAndTangents */ static void R_ModCalcNormalsAndTangents (mAliasMesh_t *mesh, int framenum, const vec3_t translate, qboolean backlerp) { int i, j; mAliasVertex_t *vertexes = &mesh->vertexes[framenum * mesh->num_verts]; mAliasCoord_t *stcoords = mesh->stcoords; const int numIndexes = mesh->num_tris * 3; const int32_t *indexArray = mesh->indexes; vec3_t triangleNormals[MAX_ALIAS_TRIS]; vec3_t triangleTangents[MAX_ALIAS_TRIS]; vec3_t triangleBitangents[MAX_ALIAS_TRIS]; float *texcoords, *verts, *normals, *tangents; /* set up array pointers for either the previous keyframe or the next keyframe */ texcoords = mesh->texcoords; if (backlerp) { verts = mesh->verts; normals = mesh->normals; tangents = mesh->tangents; } else { verts = mesh->next_verts; normals = mesh->next_normals; tangents = mesh->next_tangents; } /* calculate per-triangle surface normals and tangents*/ for (i = 0, j = 0; i < numIndexes; i += 3, j++) { vec3_t dir1, dir2; vec2_t dir1uv, dir2uv; /* calculate two mostly perpendicular edge directions */ VectorSubtract(vertexes[indexArray[i + 0]].point, vertexes[indexArray[i + 1]].point, dir1); VectorSubtract(vertexes[indexArray[i + 2]].point, vertexes[indexArray[i + 1]].point, dir2); Vector2Subtract(stcoords[indexArray[i + 0]], stcoords[indexArray[i + 1]], dir1uv); Vector2Subtract(stcoords[indexArray[i + 2]], stcoords[indexArray[i + 1]], dir2uv); /* we have two edge directions, we can calculate a third vector from * them, which is the direction of the surface normal */ CrossProduct(dir1, dir2, triangleNormals[j]); /* normalize */ VectorNormalizeFast(triangleNormals[j]); /* then we use the texture coordinates to calculate a tangent space */ if ((dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]) != 0.0) { const float frac = 1.0 / (dir1uv[1] * dir2uv[0] - dir1uv[0] * dir2uv[1]); vec3_t tmp1, tmp2; /* calculate tangent */ VectorMul(-1.0 * dir2uv[1] * frac, dir1, tmp1); VectorMul(dir1uv[1] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleTangents[j]); /* calculate bitangent */ VectorMul(-1.0 * dir2uv[0] * frac, dir1, tmp1); VectorMul(dir1uv[0] * frac, dir2, tmp2); VectorAdd(tmp1, tmp2, triangleBitangents[j]); /* normalize */ VectorNormalizeFast(triangleTangents[j]); VectorNormalizeFast(triangleBitangents[j]); } else { VectorClear(triangleTangents[j]); VectorClear(triangleBitangents[j]); } } /* for each vertex */ for (i = 0; i < mesh->num_verts; i++) { vec3_t n, b, v; vec4_t t; const int len = mesh->revIndexes[i].length; const int32_t *list = mesh->revIndexes[i].list; VectorClear(n); VectorClear(t); VectorClear(b); /* for each vertex that got mapped to this one (ie. for each triangle this vertex is a part of) */ for (j = 0; j < len; j++) { const int32_t idx = list[j] / 3; VectorAdd(n, triangleNormals[idx], n); VectorAdd(t, triangleTangents[idx], t); VectorAdd(b, triangleBitangents[idx], b); } /* normalization here does shared-vertex smoothing */ VectorNormalizeFast(n); VectorNormalizeFast(t); VectorNormalizeFast(b); /* Grahm-Schmidt orthogonalization */ Orthogonalize(t, n); /* calculate handedness */ CrossProduct(n, t, v); t[3] = (DotProduct(v, b) < 0.0) ? -1.0 : 1.0; /* copy this vertex's info to all the right places in the arrays */ for (j = 0; j < len; j++) { const int32_t idx = list[j]; const int meshIndex = mesh->indexes[list[j]]; Vector2Copy(stcoords[meshIndex], (texcoords + (2 * idx))); VectorAdd(vertexes[meshIndex].point, translate, (verts + (3 * idx))); VectorCopy(n, (normals + (3 * idx))); Vector4Copy(t, (tangents + (4 * idx))); } } }